diff --git a/docs/customize/deep-dives/autocomplete.mdx b/docs/customize/deep-dives/autocomplete.mdx index 87a4ede2450..091b71283ab 100644 --- a/docs/customize/deep-dives/autocomplete.mdx +++ b/docs/customize/deep-dives/autocomplete.mdx @@ -195,6 +195,16 @@ In VS Code, if you don't want to be shown suggestions automatically you can: 4. Press the key combination you want to use to trigger suggestions (e.g. cmd/ctrl + space) 5. Now whenever you want to see a suggestion, you can press your key binding (e.g. cmd/ctrl + space) to trigger suggestions manually +The setting is evaluated for the active document, so you can also configure it for specific languages with VS Code's language-scoped settings. For example: + +```json +"[python]": { + "editor.inlineSuggest.enabled": false +} +``` + +Continue honors the manual trigger while automatic inline suggestions are disabled. + ### Shortcut for Accepting One Line at a Time in Autocomplete This is a built-in feature of VS Code, but it's just a bit hidden. Follow these settings to reassign the keyboard shortcuts in VS Code: diff --git a/extensions/vscode/src/autocomplete/__tests__/ContinueCompletionProvider.vitest.ts b/extensions/vscode/src/autocomplete/__tests__/ContinueCompletionProvider.vitest.ts index 9d6e816c69c..48e2164da5c 100644 --- a/extensions/vscode/src/autocomplete/__tests__/ContinueCompletionProvider.vitest.ts +++ b/extensions/vscode/src/autocomplete/__tests__/ContinueCompletionProvider.vitest.ts @@ -46,9 +46,140 @@ beforeEach(() => { ); (vscode.window as any).activeTextEditor = null; + setInlineSuggestEnabled(true); }); describe("ContinueCompletionProvider triggering logic", () => { + it("does not request automatic completions when inline suggestions are disabled", async () => { + const document = createDocument(); + setActiveEditor(document); + setInlineSuggestEnabled(false); + + const provider = buildProvider(); + + const result = await provider.provideInlineCompletionItems( + document, + createPosition(), + createContext(), + createToken(), + ); + + expect(result).toBeNull(); + expect(vscode.workspace.getConfiguration).toHaveBeenCalledWith( + "editor.inlineSuggest", + document, + ); + expect(mockNextEditProvider.startChain).not.toHaveBeenCalled(); + expect( + mockNextEditProvider.provideInlineCompletionItems, + ).not.toHaveBeenCalled(); + }); + + it("keeps manual completions available when inline suggestions are disabled", async () => { + const document = createDocument(); + setActiveEditor(document); + setInlineSuggestEnabled(false); + + const provider = buildProvider(); + + const manualResult = await provider.provideInlineCompletionItems( + document, + createPosition(), + createContext(vscode.InlineCompletionTriggerKind.Invoke), + createToken(), + ); + + const automaticResult = await provider.provideInlineCompletionItems( + document, + createPosition(), + createContext(), + createToken(), + ); + + expect(manualResult).not.toBeNull(); + expect(automaticResult).toBeNull(); + expect(mockNextEditProvider.startChain).toHaveBeenCalledTimes(1); + expect( + mockNextEditProvider.provideInlineCompletionItems, + ).toHaveBeenCalledTimes(1); + }); + + it("uses a language override when it disables inline suggestions", async () => { + const document = createDocument(undefined, "python"); + setActiveEditor(document); + setInlineSuggestConfiguration(true, { python: false }); + + const provider = buildProvider(); + const result = await provider.provideInlineCompletionItems( + document, + createPosition(), + createContext(), + createToken(), + ); + + expect(result).toBeNull(); + expect(vscode.workspace.getConfiguration).toHaveBeenCalledWith( + "editor.inlineSuggest", + document, + ); + expect(mockNextEditProvider.startChain).not.toHaveBeenCalled(); + }); + + it("uses a language override when it enables inline suggestions", async () => { + const document = createDocument(undefined, "python"); + setActiveEditor(document); + setInlineSuggestConfiguration(false, { python: true }); + + const provider = buildProvider(); + const result = await provider.provideInlineCompletionItems( + document, + createPosition(), + createContext(), + createToken(), + ); + + expect(result).not.toBeNull(); + expect(vscode.workspace.getConfiguration).toHaveBeenCalledWith( + "editor.inlineSuggest", + document, + ); + expect(mockNextEditProvider.startChain).toHaveBeenCalledTimes(1); + }); + + it("passes trigger kind to regular autocomplete and allows enabled automatic requests", async () => { + const document = createDocument(); + setActiveEditor(document); + setInlineSuggestEnabled(true); + + const provider = buildProvider({ activateNextEdit: false }); + const completionProvider = (provider as any).completionProvider; + completionProvider.provideInlineCompletionItems.mockResolvedValue( + mockOutcome, + ); + + const manualResult = await provider.provideInlineCompletionItems( + document, + createPosition(), + createContext(vscode.InlineCompletionTriggerKind.Invoke), + createToken(), + ); + const automaticResult = await provider.provideInlineCompletionItems( + document, + createPosition(), + createContext(), + createToken(), + ); + + expect(manualResult).not.toBeNull(); + expect(automaticResult).not.toBeNull(); + expect( + completionProvider.provideInlineCompletionItems, + ).toHaveBeenNthCalledWith(1, expect.anything(), expect.anything(), true); + expect( + completionProvider.provideInlineCompletionItems, + ).toHaveBeenNthCalledWith(2, expect.anything(), expect.anything(), false); + }); + it("starts a new chain when none exists", async () => { const document = createDocument(); setActiveEditor(document); @@ -158,7 +289,9 @@ describe("ContinueCompletionProvider triggering logic", () => { }); }); -function buildProvider(options: { usingFullFileDiff?: boolean } = {}) { +function buildProvider( + options: { usingFullFileDiff?: boolean; activateNextEdit?: boolean } = {}, +) { const usingFullFileDiff = options.usingFullFileDiff ?? true; const configHandler = { loadConfig: vi.fn(async () => ({ @@ -175,16 +308,20 @@ function buildProvider(options: { usingFullFileDiff?: boolean } = {}) { webviewProtocol, usingFullFileDiff, ); - provider.activateNextEdit(); + if (options.activateNextEdit ?? true) { + provider.activateNextEdit(); + } return provider; } function createDocument( text = "function example() {\n return true;\n}", + languageId = "typescript", ): vscode.TextDocument { const lines = text.split("\n"); return { uri: vscode.Uri.parse("file:///test"), + languageId, isUntitled: false, getText: (range?: any) => { if (!range) { @@ -220,9 +357,11 @@ function createDocument( } as unknown as vscode.TextDocument; } -function createContext(): any { +function createContext( + triggerKind = vscode.InlineCompletionTriggerKind.Automatic, +): any { return { - triggerKind: (vscode.InlineCompletionTriggerKind as any).Automatic, + triggerKind, selectedCompletionInfo: undefined, }; } @@ -238,6 +377,21 @@ function createToken(): any { }; } +function setInlineSuggestEnabled(enabled: boolean) { + setInlineSuggestConfiguration(enabled); +} + +function setInlineSuggestConfiguration( + globalEnabled: boolean, + languageOverrides: Record = {}, +) { + (vscode.workspace.getConfiguration as any).mockImplementation( + (_section: string, scope: any) => ({ + get: vi.fn(() => languageOverrides[scope?.languageId] ?? globalEnabled), + }), + ); +} + function setActiveEditor(document: any, cursor = createPosition()) { const selection = { active: cursor, anchor: cursor }; (vscode.window as any).activeTextEditor = { diff --git a/extensions/vscode/src/autocomplete/completionProvider.ts b/extensions/vscode/src/autocomplete/completionProvider.ts index 498f971d05d..06badabe843 100644 --- a/extensions/vscode/src/autocomplete/completionProvider.ts +++ b/extensions/vscode/src/autocomplete/completionProvider.ts @@ -171,6 +171,20 @@ export class ContinueCompletionProvider return null; } + // VS Code still asks providers for automatic completions after an explicit + // invocation, even when inline suggestions are disabled. Only honor those + // explicit invocations in that mode. Passing the document as the scope + // ensures that language-scoped editor settings are respected. + const inlineSuggestEnabled = vscode.workspace + .getConfiguration("editor.inlineSuggest", document) + .get("enabled", true); + if ( + inlineSuggestEnabled === false && + context.triggerKind === vscode.InlineCompletionTriggerKind.Automatic + ) { + return null; + } + if (document.uri.scheme === "vscode-scm") { return null; }