diff --git a/src/vs/editor/contrib/inlineCompletions/browser/ghostTextModel.ts b/src/vs/editor/contrib/inlineCompletions/browser/ghostTextModel.ts index 58df06c72c3..469328725f7 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/ghostTextModel.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/ghostTextModel.ts @@ -147,8 +147,8 @@ export class SharedInlineCompletionCache extends Disposable { triggerKind: InlineCompletionTriggerKind ) { this.cache.value = new SynchronizedInlineCompletionsCache( - editor, completionsSource, + editor, () => this.onDidChangeEmitter.fire(), triggerKind ); diff --git a/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel.ts b/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel.ts index 969623548bb..dc589f2806e 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel.ts @@ -252,6 +252,8 @@ export class InlineCompletionsSession extends BaseGhostTextWidgetModel { })); this._register(this.editor.onDidChangeModelContent((e) => { + // Call this in case `onDidChangeModelContent` calls us first. + this.cache.value?.updateRanges(); this.updateFilteredInlineCompletions(); this.scheduleAutomaticUpdate(); })); @@ -273,8 +275,17 @@ export class InlineCompletionsSession extends BaseGhostTextWidgetModel { const model = this.editor.getModel(); this.filteredCompletions = this.cache.value.completions.filter(c => { - const originalValue = model.getValueInRange(c.synchronizedRange); - const matches = matchesSubString(originalValue, c.inlineCompletion.filterText); + let originalValue = model.getValueInRange(c.synchronizedRange); + let filterText = c.inlineCompletion.filterText; + + const indent = model.getLineIndentColumn(c.synchronizedRange.startLineNumber); + if (c.synchronizedRange.startColumn <= indent) { + // Remove indentation + originalValue = originalValue.trimStart(); + filterText = filterText.trimStart(); + } + + const matches = matchesSubString(originalValue, filterText); return matches; }); } @@ -517,9 +528,9 @@ export class SynchronizedInlineCompletionsCache extends Disposable { public readonly completions: readonly CachedInlineCompletion[]; constructor( - editor: IActiveCodeEditor, completionsSource: TrackedInlineCompletions, - onChange: () => void, + private readonly editor: IActiveCodeEditor, + private readonly onChange: () => void, public readonly triggerKind: InlineCompletionTriggerKind, ) { super(); @@ -540,26 +551,30 @@ export class SynchronizedInlineCompletionsCache extends Disposable { this.completions = completionsSource.items.map((c, idx) => new CachedInlineCompletion(c, decorationIds[idx])); this._register(editor.onDidChangeModelContent(() => { - let hasChanged = false; - const model = editor.getModel(); - for (const c of this.completions) { - const newRange = model.getDecorationRange(c.decorationId); - if (!newRange) { - onUnexpectedError(new Error('Decoration has no range')); - continue; - } - if (!c.synchronizedRange.equalsRange(newRange)) { - hasChanged = true; - c.synchronizedRange = newRange; - } - } - if (hasChanged) { - onChange(); - } + this.updateRanges(); })); this._register(completionsSource); } + + public updateRanges() { + let hasChanged = false; + const model = this.editor.getModel(); + for (const c of this.completions) { + const newRange = model.getDecorationRange(c.decorationId); + if (!newRange) { + onUnexpectedError(new Error('Decoration has no range')); + continue; + } + if (!c.synchronizedRange.equalsRange(newRange)) { + hasChanged = true; + c.synchronizedRange = newRange; + } + } + if (hasChanged) { + this.onChange(); + } + } } class CachedInlineCompletion { diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts index 8f5298bcd7e..0632c0cc47e 100644 --- a/src/vs/monaco.d.ts +++ b/src/vs/monaco.d.ts @@ -6263,7 +6263,6 @@ declare namespace monaco.languages { } export interface InlineCompletion { - readonly filterText?: string; /** * The text to insert. * If the text contains a line break, the range must end at the end of a line. @@ -6275,6 +6274,11 @@ declare namespace monaco.languages { readonly insertText: string | { snippet: string; }; + /** + * A text that is used to decide if this inline completion should be shown. + * An inline completion is shown if the text to replace is a subword of the filter text. + */ + readonly filterText?: string; /** * The range to replace. * Must begin and end on the same line. diff --git a/src/vs/workbench/api/common/extHostLanguageFeatures.ts b/src/vs/workbench/api/common/extHostLanguageFeatures.ts index 9d601d2413f..ab6dbb99bac 100644 --- a/src/vs/workbench/api/common/extHostLanguageFeatures.ts +++ b/src/vs/workbench/api/common/extHostLanguageFeatures.ts @@ -1175,11 +1175,9 @@ class InlineCompletionAdapterNew { } const insertText = item.insertText; - if (insertText === undefined) { - throw new Error('text or insertText must be defined'); - } return ({ insertText: typeof insertText === 'string' ? insertText : { snippet: insertText.value }, + filterText: item.filterText, range: item.range ? typeConvert.Range.from(item.range) : undefined, command, idx: idx, diff --git a/src/vscode-dts/vscode.proposed.inlineCompletionsNew.d.ts b/src/vscode-dts/vscode.proposed.inlineCompletionsNew.d.ts index 9556b4455d1..9abe1555ee0 100644 --- a/src/vscode-dts/vscode.proposed.inlineCompletionsNew.d.ts +++ b/src/vscode-dts/vscode.proposed.inlineCompletionsNew.d.ts @@ -104,6 +104,12 @@ declare module 'vscode' { */ insertText: string | SnippetString; + /** + * A text that is used to decide if this inline completion should be shown. + * An inline completion is shown if the text to replace is a subword of the filter text. + */ + filterText?: string; + /** * The range to replace. * Must begin and end on the same line.