diff --git a/src/vs/editor/common/config/editorOptions.ts b/src/vs/editor/common/config/editorOptions.ts index a6e7322d41c18..acc23078daea8 100644 --- a/src/vs/editor/common/config/editorOptions.ts +++ b/src/vs/editor/common/config/editorOptions.ts @@ -496,7 +496,7 @@ export interface IEditorOptions { * Enable quick suggestions (shadow suggestions) * Defaults to true. */ - quickSuggestions?: boolean | IQuickSuggestionsOptions; + quickSuggestions?: boolean | QuickSuggestionsValue | IQuickSuggestionsOptions; /** * Quick suggestions show delay (in ms) * Defaults to 10 (ms) @@ -3712,7 +3712,7 @@ class PlaceholderOption extends BaseEditorOption { +class EditorQuickSuggestions extends BaseEditorOption { public override readonly defaultValue: InternalQuickSuggestionsOptions; @@ -3743,32 +3743,45 @@ class EditorQuickSuggestions extends BaseEditorOption apply same value to all token types + const allowedValues: QuickSuggestionsValue[] = ['on', 'inline', 'off', 'offWhenInlineCompletions']; + const validated = stringSet(input as QuickSuggestionsValue, this.defaultValue.other, allowedValues); + return { comments: validated, strings: validated, other: validated }; + } if (!input || typeof input !== 'object') { - // invalid object + // invalid input return this.defaultValue; } const { other, comments, strings } = (input); - const allowedValues: QuickSuggestionsValue[] = ['on', 'inline', 'off']; + const allowedValues: QuickSuggestionsValue[] = ['on', 'inline', 'off', 'offWhenInlineCompletions']; let validatedOther: QuickSuggestionsValue; let validatedComments: QuickSuggestionsValue; let validatedStrings: QuickSuggestionsValue; diff --git a/src/vs/editor/contrib/suggest/browser/suggestModel.ts b/src/vs/editor/contrib/suggest/browser/suggestModel.ts index fcf97af35a620..30c1276d5c4fd 100644 --- a/src/vs/editor/contrib/suggest/browser/suggestModel.ts +++ b/src/vs/editor/contrib/suggest/browser/suggestModel.ts @@ -415,7 +415,10 @@ export class SuggestModel implements IDisposable { const lineTokens = model.tokenization.getLineTokens(pos.lineNumber); const tokenType = lineTokens.getStandardTokenType(lineTokens.findTokenIndexAtOffset(Math.max(pos.column - 1 - 1, 0))); if (QuickSuggestionsOptions.valueFor(config, tokenType) !== 'on') { - return; + if (QuickSuggestionsOptions.valueFor(config, tokenType) !== 'offWhenInlineCompletions' + || (this._languageFeaturesService.inlineCompletionsProvider.has(model) && this._editor.getOption(EditorOption.inlineSuggest).enabled)) { + return; + } } } diff --git a/src/vs/editor/contrib/suggest/test/browser/suggestModel.test.ts b/src/vs/editor/contrib/suggest/test/browser/suggestModel.test.ts index dccb55ef44162..ff465706c0c28 100644 --- a/src/vs/editor/contrib/suggest/test/browser/suggestModel.test.ts +++ b/src/vs/editor/contrib/suggest/test/browser/suggestModel.test.ts @@ -15,7 +15,7 @@ import { Selection } from '../../../../common/core/selection.js'; import { Handler } from '../../../../common/editorCommon.js'; import { ITextModel } from '../../../../common/model.js'; import { TextModel } from '../../../../common/model/textModel.js'; -import { CompletionItemKind, CompletionItemProvider, CompletionList, CompletionTriggerKind, EncodedTokenizationResult, IState, TokenizationRegistry } from '../../../../common/languages.js'; +import { CompletionItemKind, CompletionItemProvider, CompletionList, CompletionTriggerKind, EncodedTokenizationResult, InlineCompletionsProvider, IState, TokenizationRegistry } from '../../../../common/languages.js'; import { MetadataConsts } from '../../../../common/encodedTokenAttributes.js'; import { ILanguageConfigurationService } from '../../../../common/languages/languageConfigurationRegistry.js'; import { NullState } from '../../../../common/languages/nullTokenize.js'; @@ -41,6 +41,7 @@ import { IInstantiationService } from '../../../../../platform/instantiation/com import { getSnippetSuggestSupport, setSnippetSuggestSupport } from '../../browser/suggest.js'; import { IEnvironmentService } from '../../../../../platform/environment/common/environment.js'; import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../base/test/common/utils.js'; +import { runWithFakedTimers } from '../../../../../base/test/common/timeTravelScheduler.js'; function createMockEditor(model: TextModel, languageFeaturesService: ILanguageFeaturesService): ITestCodeEditor { @@ -1228,4 +1229,142 @@ suite('SuggestModel - TriggerAndCancelOracle', function () { }); }); + + test('offWhenInlineCompletions - suppresses quick suggest when inline provider exists', function () { + + disposables.add(registry.register({ scheme: 'test' }, alwaysSomethingSupport)); + + // Register a dummy inline completions provider + const inlineProvider: InlineCompletionsProvider = { + provideInlineCompletions: () => ({ items: [] }), + disposeInlineCompletions: () => { } + }; + disposables.add(languageFeaturesService.inlineCompletionsProvider.register({ scheme: 'test' }, inlineProvider)); + + return withOracle((suggestOracle, editor) => { + editor.updateOptions({ quickSuggestions: { comments: 'off', strings: 'off', other: 'offWhenInlineCompletions' } }); + + return new Promise((resolve, reject) => { + const unexpectedSuggestSub = suggestOracle.onDidSuggest(() => { + unexpectedSuggestSub.dispose(); + reject(new Error('Quick suggestions should not have been triggered')); + }); + + editor.setPosition({ lineNumber: 1, column: 4 }); + editor.trigger('keyboard', Handler.Type, { text: 'd' }); + + // Wait for the quick suggest delay to pass without triggering + setTimeout(() => { + unexpectedSuggestSub.dispose(); + resolve(); + }, 200); + }); + }); + }); + + test('offWhenInlineCompletions - allows quick suggest when no inline provider exists', function () { + + disposables.add(registry.register({ scheme: 'test' }, alwaysSomethingSupport)); + + // No inline completions provider registered for 'test' scheme + + return withOracle((suggestOracle, editor) => { + editor.updateOptions({ quickSuggestions: { comments: 'off', strings: 'off', other: 'offWhenInlineCompletions' } }); + + return assertEvent(suggestOracle.onDidSuggest, () => { + editor.setPosition({ lineNumber: 1, column: 4 }); + editor.trigger('keyboard', Handler.Type, { text: 'd' }); + }, suggestEvent => { + assert.strictEqual(suggestEvent.triggerOptions.auto, true); + assert.strictEqual(suggestEvent.completionModel.items.length, 1); + }); + }); + }); + + test('offWhenInlineCompletions - allows quick suggest when inlineSuggest is disabled', function () { + return runWithFakedTimers({ useFakeTimers: true }, () => { + disposables.add(registry.register({ scheme: 'test' }, alwaysSomethingSupport)); + + // Register a dummy inline completions provider + const inlineProvider: InlineCompletionsProvider = { + provideInlineCompletions: () => ({ items: [] }), + disposeInlineCompletions: () => { } + }; + disposables.add(languageFeaturesService.inlineCompletionsProvider.register({ scheme: 'test' }, inlineProvider)); + + return withOracle((suggestOracle, editor) => { + editor.updateOptions({ + quickSuggestions: { comments: 'off', strings: 'off', other: 'offWhenInlineCompletions' }, + inlineSuggest: { enabled: false } + }); + + return assertEvent(suggestOracle.onDidSuggest, () => { + editor.setPosition({ lineNumber: 1, column: 4 }); + editor.trigger('keyboard', Handler.Type, { text: 'd' }); + }, suggestEvent => { + assert.strictEqual(suggestEvent.triggerOptions.auto, true); + assert.strictEqual(suggestEvent.completionModel.items.length, 1); + }); + }); + }); + }); + + test('string shorthand - "off" disables quick suggestions for all token types', function () { + return runWithFakedTimers({ useFakeTimers: true }, () => { + + disposables.add(registry.register({ scheme: 'test' }, alwaysSomethingSupport)); + + return withOracle((suggestOracle, editor) => { + // Use string shorthand instead of object form + editor.updateOptions({ quickSuggestions: 'off' }); + + return new Promise((resolve, reject) => { + const sub = suggestOracle.onDidSuggest(() => { + sub.dispose(); + reject(new Error('Quick suggestions should have been suppressed by string shorthand "off"')); + }); + + editor.setPosition({ lineNumber: 1, column: 4 }); + editor.trigger('keyboard', Handler.Type, { text: 'd' }); + + setTimeout(() => { + sub.dispose(); + resolve(); + }, 200); + }); + }); + }); + }); + + test('string shorthand - "offWhenInlineCompletions" suppresses when inline provider exists', function () { + return runWithFakedTimers({ useFakeTimers: true }, () => { + disposables.add(registry.register({ scheme: 'test' }, alwaysSomethingSupport)); + + const inlineProvider: InlineCompletionsProvider = { + provideInlineCompletions: () => ({ items: [] }), + disposeInlineCompletions: () => { } + }; + disposables.add(languageFeaturesService.inlineCompletionsProvider.register({ scheme: 'test' }, inlineProvider)); + + return withOracle((suggestOracle, editor) => { + // Use string shorthand — applies to all token types + editor.updateOptions({ quickSuggestions: 'offWhenInlineCompletions' }); + + return new Promise((resolve, reject) => { + const sub = suggestOracle.onDidSuggest(() => { + sub.dispose(); + reject(new Error('Quick suggestions should have been suppressed by offWhenInlineCompletions shorthand')); + }); + + editor.setPosition({ lineNumber: 1, column: 4 }); + editor.trigger('keyboard', Handler.Type, { text: 'd' }); + + setTimeout(() => { + sub.dispose(); + resolve(); + }, 200); + }); + }); + }); + }); }); diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts index 22641b4e01a3a..be805ae83dfbc 100644 --- a/src/vs/monaco.d.ts +++ b/src/vs/monaco.d.ts @@ -3645,7 +3645,7 @@ declare namespace monaco.editor { * Enable quick suggestions (shadow suggestions) * Defaults to true. */ - quickSuggestions?: boolean | IQuickSuggestionsOptions; + quickSuggestions?: boolean | QuickSuggestionsValue | IQuickSuggestionsOptions; /** * Quick suggestions show delay (in ms) * Defaults to 10 (ms) @@ -4587,7 +4587,7 @@ declare namespace monaco.editor { cycle?: boolean; } - export type QuickSuggestionsValue = 'on' | 'inline' | 'off'; + export type QuickSuggestionsValue = 'on' | 'inline' | 'off' | 'offWhenInlineCompletions'; /** * Configuration options for quick suggestions @@ -5374,7 +5374,7 @@ declare namespace monaco.editor { showUnused: IEditorOption; showDeprecated: IEditorOption; inlayHints: IEditorOption>>; - snippetSuggestions: IEditorOption; + snippetSuggestions: IEditorOption; smartSelect: IEditorOption>>; smoothScrolling: IEditorOption; stopRenderingLineAfter: IEditorOption;