mirror of
https://github.com/microsoft/vscode.git
synced 2026-07-10 08:45:56 +01:00
Enhance quick suggestions with inline completions (#300371)
* Enhance quick suggestions behavior with inline completions: allow triggering when inline provider returns no results * Improve inline completions handling: suppress suggestions when inline completions are active * CCR
This commit is contained in:
@@ -3772,7 +3772,7 @@ class EditorQuickSuggestions extends BaseEditorOption<EditorOption.quickSuggesti
|
||||
{
|
||||
type: 'string',
|
||||
enum: ['on', 'inline', 'off', 'offWhenInlineCompletions'],
|
||||
enumDescriptions: [nls.localize('on', "Quick suggestions show inside the suggest widget"), nls.localize('inline', "Quick suggestions show as ghost text"), nls.localize('off', "Quick suggestions are disabled"), nls.localize('offWhenInlineCompletions', "Quick suggestions are disabled when an inline completion provider is available")]
|
||||
enumDescriptions: [nls.localize('on', "Quick suggestions show inside the suggest widget"), nls.localize('inline', "Quick suggestions show as ghost text"), nls.localize('off', "Quick suggestions are disabled"), nls.localize('offWhenInlineCompletions', "Quick suggestions are disabled when inline completions are showing")]
|
||||
}
|
||||
];
|
||||
super(EditorOption.quickSuggestions, 'quickSuggestions', defaults, {
|
||||
@@ -3781,7 +3781,7 @@ class EditorQuickSuggestions extends BaseEditorOption<EditorOption.quickSuggesti
|
||||
{
|
||||
type: 'string',
|
||||
enum: ['on', 'inline', 'off', 'offWhenInlineCompletions'],
|
||||
enumDescriptions: [nls.localize('quickSuggestions.topLevel.on', "Quick suggestions are enabled for all token types"), nls.localize('quickSuggestions.topLevel.inline', "Quick suggestions show as ghost text for all token types"), nls.localize('quickSuggestions.topLevel.off', "Quick suggestions are disabled for all token types"), nls.localize('quickSuggestions.topLevel.offWhenInlineCompletions', "Quick suggestions are disabled for all token types when an inline completion provider is available")]
|
||||
enumDescriptions: [nls.localize('quickSuggestions.topLevel.on', "Quick suggestions are enabled for all token types"), nls.localize('quickSuggestions.topLevel.inline', "Quick suggestions show as ghost text for all token types"), nls.localize('quickSuggestions.topLevel.off', "Quick suggestions are disabled for all token types"), nls.localize('quickSuggestions.topLevel.offWhenInlineCompletions', "Quick suggestions are disabled for all token types when inline completions are showing")]
|
||||
},
|
||||
{
|
||||
type: 'object',
|
||||
|
||||
@@ -20,6 +20,7 @@ import { CompletionItem } from '../../../suggest/browser/suggest.js';
|
||||
import { SuggestController } from '../../../suggest/browser/suggestController.js';
|
||||
import { ObservableCodeEditor } from '../../../../browser/observableCodeEditor.js';
|
||||
import { observableFromEvent } from '../../../../../base/common/observable.js';
|
||||
import { EditorOption } from '../../../../common/config/editorOptions.js';
|
||||
|
||||
export class SuggestWidgetAdaptor extends Disposable {
|
||||
private isSuggestWidgetVisible: boolean = false;
|
||||
@@ -149,6 +150,17 @@ export class SuggestWidgetAdaptor extends Disposable {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// When offWhenInlineCompletions is active, don't expose the selected
|
||||
// suggest item to the inline completions model so that it does not
|
||||
// trigger an inline completion request while the suggest widget is open
|
||||
const quickSuggestions = this.editor.getOption(EditorOption.quickSuggestions);
|
||||
if (typeof quickSuggestions === 'object'
|
||||
&& (quickSuggestions.other === 'offWhenInlineCompletions'
|
||||
|| quickSuggestions.comments === 'offWhenInlineCompletions'
|
||||
|| quickSuggestions.strings === 'offWhenInlineCompletions')) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const focusedItem = suggestController.widget.value.getFocusedItem();
|
||||
const position = this.editor.getPosition();
|
||||
const model = this.editor.getModel();
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { TimeoutTimer } from '../../../../base/common/async.js';
|
||||
import { TimeoutTimer, disposableTimeout } from '../../../../base/common/async.js';
|
||||
import { CancellationTokenSource } from '../../../../base/common/cancellation.js';
|
||||
import { onUnexpectedError } from '../../../../base/common/errors.js';
|
||||
import { Emitter, Event } from '../../../../base/common/event.js';
|
||||
@@ -30,8 +30,10 @@ import { ILanguageFeaturesService } from '../../../common/services/languageFeatu
|
||||
import { FuzzyScoreOptions } from '../../../../base/common/filters.js';
|
||||
import { assertType } from '../../../../base/common/types.js';
|
||||
import { InlineCompletionContextKeys } from '../../inlineCompletions/browser/controller/inlineCompletionContextKeys.js';
|
||||
import { getInlineCompletionsController } from '../../inlineCompletions/browser/controller/common.js';
|
||||
import { SnippetController2 } from '../../snippet/browser/snippetController2.js';
|
||||
import { IEnvironmentService } from '../../../../platform/environment/common/environment.js';
|
||||
import { autorun } from '../../../../base/common/observable.js';
|
||||
|
||||
export interface ICancelEvent {
|
||||
readonly retrigger: boolean;
|
||||
@@ -134,6 +136,7 @@ export class SuggestModel implements IDisposable {
|
||||
private readonly _toDispose = new DisposableStore();
|
||||
private readonly _triggerCharacterListener = new DisposableStore();
|
||||
private readonly _triggerQuickSuggest = new TimeoutTimer();
|
||||
private _waitForInlineCompletions: DisposableStore | undefined;
|
||||
|
||||
private _triggerState: SuggestTriggerOptions | undefined = undefined;
|
||||
private _requestToken?: CancellationTokenSource;
|
||||
@@ -209,6 +212,7 @@ export class SuggestModel implements IDisposable {
|
||||
dispose(): void {
|
||||
dispose(this._triggerCharacterListener);
|
||||
dispose([this._onDidCancel, this._onDidSuggest, this._onDidTrigger, this._triggerQuickSuggest]);
|
||||
this._waitForInlineCompletions?.dispose();
|
||||
this._toDispose.dispose();
|
||||
this._completionDisposables.dispose();
|
||||
this.cancel();
|
||||
@@ -310,8 +314,11 @@ export class SuggestModel implements IDisposable {
|
||||
}
|
||||
|
||||
cancel(retrigger: boolean = false): void {
|
||||
this._triggerQuickSuggest.cancel();
|
||||
this._waitForInlineCompletions?.dispose();
|
||||
this._waitForInlineCompletions = undefined;
|
||||
|
||||
if (this._triggerState !== undefined) {
|
||||
this._triggerQuickSuggest.cancel();
|
||||
this._requestToken?.cancel();
|
||||
this._requestToken = undefined;
|
||||
this._triggerState = undefined;
|
||||
@@ -391,6 +398,10 @@ export class SuggestModel implements IDisposable {
|
||||
|
||||
this.cancel();
|
||||
|
||||
// Cancel any in-flight wait for inline completions from a previous cycle
|
||||
this._waitForInlineCompletions?.dispose();
|
||||
this._waitForInlineCompletions = undefined;
|
||||
|
||||
this._triggerQuickSuggest.cancelAndSet(() => {
|
||||
if (this._triggerState !== undefined) {
|
||||
return;
|
||||
@@ -409,16 +420,19 @@ export class SuggestModel implements IDisposable {
|
||||
return;
|
||||
}
|
||||
|
||||
let waitForInlineCompletions = false;
|
||||
if (!QuickSuggestionsOptions.isAllOn(config)) {
|
||||
// Check the type of the token that triggered this
|
||||
model.tokenization.tokenizeIfCheap(pos.lineNumber);
|
||||
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') {
|
||||
if (QuickSuggestionsOptions.valueFor(config, tokenType) !== 'offWhenInlineCompletions'
|
||||
|| (this._languageFeaturesService.inlineCompletionsProvider.has(model) && this._editor.getOption(EditorOption.inlineSuggest).enabled)) {
|
||||
return;
|
||||
}
|
||||
const value = QuickSuggestionsOptions.valueFor(config, tokenType);
|
||||
if (value === 'off' || value === 'inline') {
|
||||
return;
|
||||
}
|
||||
if (value === 'offWhenInlineCompletions') {
|
||||
waitForInlineCompletions = this._languageFeaturesService.inlineCompletionsProvider.has(model)
|
||||
&& this._editor.getOption(EditorOption.inlineSuggest).enabled;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -431,12 +445,73 @@ export class SuggestModel implements IDisposable {
|
||||
return;
|
||||
}
|
||||
|
||||
// we made it till here -> trigger now
|
||||
this.trigger({ auto: true });
|
||||
if (waitForInlineCompletions) {
|
||||
// Wait for inline completions to resolve before deciding
|
||||
this._waitForInlineCompletionsAndTrigger(model, pos);
|
||||
} else {
|
||||
this.trigger({ auto: true });
|
||||
}
|
||||
|
||||
}, this._editor.getOption(EditorOption.quickSuggestionsDelay));
|
||||
}
|
||||
|
||||
private _waitForInlineCompletionsAndTrigger(initialModel: ITextModel, initialPosition: Position): void {
|
||||
const initialModelVersion = initialModel.getVersionId();
|
||||
const inlineController = getInlineCompletionsController(this._editor);
|
||||
const inlineModel = inlineController?.model.get();
|
||||
if (!inlineModel) {
|
||||
this.trigger({ auto: true });
|
||||
return;
|
||||
}
|
||||
|
||||
const state = inlineModel.state.get();
|
||||
if (state?.inlineSuggestion) {
|
||||
// Inline completions are already showing - suppress
|
||||
return;
|
||||
}
|
||||
|
||||
const store = new DisposableStore();
|
||||
this._waitForInlineCompletions = store;
|
||||
|
||||
const triggerAndCleanUp = (doTrigger: boolean) => {
|
||||
store.dispose();
|
||||
if (this._waitForInlineCompletions === store) {
|
||||
this._waitForInlineCompletions = undefined;
|
||||
}
|
||||
if (this._triggerState !== undefined) {
|
||||
return;
|
||||
}
|
||||
if (!doTrigger) {
|
||||
return;
|
||||
}
|
||||
const currentModel = this._editor.getModel();
|
||||
const currentPosition = this._editor.getPosition();
|
||||
if (currentModel === initialModel
|
||||
&& currentModel.getVersionId() === initialModelVersion
|
||||
&& currentPosition?.equals(initialPosition)
|
||||
&& this._editor.hasWidgetFocus()
|
||||
) {
|
||||
this.trigger({ auto: true });
|
||||
}
|
||||
};
|
||||
|
||||
// Race: observe inline completions state vs 750ms timeout
|
||||
disposableTimeout(() => {
|
||||
triggerAndCleanUp(true);
|
||||
inlineModel.stop('automatic');
|
||||
}, 750, store);
|
||||
|
||||
store.add(autorun(reader => {
|
||||
const status = inlineModel.status.read(reader);
|
||||
const currentState = inlineModel.state.read(reader);
|
||||
if (!currentState && status === 'loading') {
|
||||
// Still loading
|
||||
return;
|
||||
}
|
||||
triggerAndCleanUp(!currentState);
|
||||
}));
|
||||
}
|
||||
|
||||
private _refilterCompletionItems(): void {
|
||||
assertType(this._editor.hasModel());
|
||||
assertType(this._triggerState !== undefined);
|
||||
|
||||
@@ -25,7 +25,7 @@ import { SuggestController } from '../../browser/suggestController.js';
|
||||
import { ISuggestMemoryService } from '../../browser/suggestMemory.js';
|
||||
import { LineContext, SuggestModel } from '../../browser/suggestModel.js';
|
||||
import { ISelectedSuggestion } from '../../browser/suggestWidget.js';
|
||||
import { createTestCodeEditor, ITestCodeEditor } from '../../../../test/browser/testCodeEditor.js';
|
||||
import { createTestCodeEditor, ITestCodeEditor, withAsyncTestCodeEditor } from '../../../../test/browser/testCodeEditor.js';
|
||||
import { createModelServices, createTextModel, instantiateTextModel } from '../../../../test/common/testTextModel.js';
|
||||
import { ServiceCollection } from '../../../../../platform/instantiation/common/serviceCollection.js';
|
||||
import { IKeybindingService } from '../../../../../platform/keybinding/common/keybinding.js';
|
||||
@@ -42,6 +42,15 @@ import { getSnippetSuggestSupport, setSnippetSuggestSupport } from '../../browse
|
||||
import { IEnvironmentService } from '../../../../../platform/environment/common/environment.js';
|
||||
import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../base/test/common/utils.js';
|
||||
import { runWithFakedTimers } from '../../../../../base/test/common/timeTravelScheduler.js';
|
||||
import { timeout } from '../../../../../base/common/async.js';
|
||||
import { InlineCompletionsController } from '../../../inlineCompletions/browser/controller/inlineCompletionsController.js';
|
||||
import { InlineSuggestionsView } from '../../../inlineCompletions/browser/view/inlineSuggestionsView.js';
|
||||
import { IAccessibilitySignalService } from '../../../../../platform/accessibilitySignal/browser/accessibilitySignalService.js';
|
||||
import { IMenuService, IMenu } from '../../../../../platform/actions/common/actions.js';
|
||||
import { ILogService, NullLogService } from '../../../../../platform/log/common/log.js';
|
||||
import { IEditorWorkerService } from '../../../../common/services/editorWorker.js';
|
||||
import { IDefaultAccountService } from '../../../../../platform/defaultAccount/common/defaultAccount.js';
|
||||
import { ModifierKeyEmitter } from '../../../../../base/browser/dom.js';
|
||||
|
||||
|
||||
function createMockEditor(model: TextModel, languageFeaturesService: ILanguageFeaturesService): ITestCodeEditor {
|
||||
@@ -1230,11 +1239,11 @@ suite('SuggestModel - TriggerAndCancelOracle', function () {
|
||||
});
|
||||
});
|
||||
|
||||
test('offWhenInlineCompletions - suppresses quick suggest when inline provider exists', function () {
|
||||
test('offWhenInlineCompletions - allows quick suggest when inline provider returns empty results', function () {
|
||||
|
||||
disposables.add(registry.register({ scheme: 'test' }, alwaysSomethingSupport));
|
||||
|
||||
// Register a dummy inline completions provider
|
||||
// Register a dummy inline completions provider that returns no items
|
||||
const inlineProvider: InlineCompletionsProvider = {
|
||||
provideInlineCompletions: () => ({ items: [] }),
|
||||
disposeInlineCompletions: () => { }
|
||||
@@ -1244,20 +1253,12 @@ suite('SuggestModel - TriggerAndCancelOracle', function () {
|
||||
return withOracle((suggestOracle, editor) => {
|
||||
editor.updateOptions({ quickSuggestions: { comments: 'off', strings: 'off', other: 'offWhenInlineCompletions' } });
|
||||
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
const unexpectedSuggestSub = suggestOracle.onDidSuggest(() => {
|
||||
unexpectedSuggestSub.dispose();
|
||||
reject(new Error('Quick suggestions should not have been triggered'));
|
||||
});
|
||||
|
||||
// Without an InlineCompletionsController, the fallback triggers immediately
|
||||
return assertEvent(suggestOracle.onDidSuggest, () => {
|
||||
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);
|
||||
}, suggestEvent => {
|
||||
assert.strictEqual(suggestEvent.triggerOptions.auto, true);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1336,7 +1337,7 @@ suite('SuggestModel - TriggerAndCancelOracle', function () {
|
||||
});
|
||||
});
|
||||
|
||||
test('string shorthand - "offWhenInlineCompletions" suppresses when inline provider exists', function () {
|
||||
test('string shorthand - "offWhenInlineCompletions" allows quick suggest when inline provider returns empty', function () {
|
||||
return runWithFakedTimers({ useFakeTimers: true }, () => {
|
||||
disposables.add(registry.register({ scheme: 'test' }, alwaysSomethingSupport));
|
||||
|
||||
@@ -1347,24 +1348,202 @@ suite('SuggestModel - TriggerAndCancelOracle', function () {
|
||||
disposables.add(languageFeaturesService.inlineCompletionsProvider.register({ scheme: 'test' }, inlineProvider));
|
||||
|
||||
return withOracle((suggestOracle, editor) => {
|
||||
// Use string shorthand — applies to all token types
|
||||
// Use string shorthand - applies to all token types
|
||||
editor.updateOptions({ quickSuggestions: 'offWhenInlineCompletions' });
|
||||
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
const sub = suggestOracle.onDidSuggest(() => {
|
||||
sub.dispose();
|
||||
reject(new Error('Quick suggestions should have been suppressed by offWhenInlineCompletions shorthand'));
|
||||
});
|
||||
|
||||
// Without InlineCompletionsController, the fallback triggers immediately
|
||||
return assertEvent(suggestOracle.onDidSuggest, () => {
|
||||
editor.setPosition({ lineNumber: 1, column: 4 });
|
||||
editor.trigger('keyboard', Handler.Type, { text: 'd' });
|
||||
|
||||
setTimeout(() => {
|
||||
sub.dispose();
|
||||
resolve();
|
||||
}, 200);
|
||||
}, suggestEvent => {
|
||||
assert.strictEqual(suggestEvent.triggerOptions.auto, true);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
suite('SuggestModel - offWhenInlineCompletions with InlineCompletionsController', function () {
|
||||
|
||||
ensureNoDisposablesAreLeakedInTestSuite();
|
||||
|
||||
const completionProvider: CompletionItemProvider = {
|
||||
_debugDisplayName: 'test',
|
||||
provideCompletionItems(doc, pos): CompletionList {
|
||||
const wordUntil = doc.getWordUntilPosition(pos);
|
||||
return {
|
||||
incomplete: false,
|
||||
suggestions: [{
|
||||
label: doc.getWordUntilPosition(pos).word,
|
||||
kind: CompletionItemKind.Property,
|
||||
insertText: 'foofoo',
|
||||
range: new Range(pos.lineNumber, wordUntil.startColumn, pos.lineNumber, wordUntil.endColumn)
|
||||
}]
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
async function withSuggestModelAndInlineCompletions(
|
||||
text: string,
|
||||
inlineProvider: InlineCompletionsProvider,
|
||||
callback: (suggestModel: SuggestModel, editor: ITestCodeEditor) => Promise<void>,
|
||||
): Promise<void> {
|
||||
await runWithFakedTimers({ useFakeTimers: true }, async () => {
|
||||
const disposableStore = new DisposableStore();
|
||||
try {
|
||||
const languageFeaturesService = new LanguageFeaturesService();
|
||||
disposableStore.add(languageFeaturesService.completionProvider.register({ pattern: '**' }, completionProvider));
|
||||
disposableStore.add(languageFeaturesService.inlineCompletionsProvider.register({ pattern: '**' }, inlineProvider));
|
||||
|
||||
const serviceCollection = new ServiceCollection(
|
||||
[ILanguageFeaturesService, languageFeaturesService],
|
||||
[ITelemetryService, NullTelemetryService],
|
||||
[ILogService, new NullLogService()],
|
||||
[IStorageService, disposableStore.add(new InMemoryStorageService())],
|
||||
[IKeybindingService, new MockKeybindingService()],
|
||||
[IEditorWorkerService, new class extends mock<IEditorWorkerService>() {
|
||||
override computeWordRanges() {
|
||||
return Promise.resolve({});
|
||||
}
|
||||
}],
|
||||
[ISuggestMemoryService, new class extends mock<ISuggestMemoryService>() {
|
||||
override memorize(): void { }
|
||||
override select(): number { return 0; }
|
||||
}],
|
||||
[IMenuService, new class extends mock<IMenuService>() {
|
||||
override createMenu() {
|
||||
return new class extends mock<IMenu>() {
|
||||
override onDidChange = Event.None;
|
||||
override dispose() { }
|
||||
};
|
||||
}
|
||||
}],
|
||||
[ILabelService, new class extends mock<ILabelService>() { }],
|
||||
[IWorkspaceContextService, new class extends mock<IWorkspaceContextService>() { }],
|
||||
[IEnvironmentService, new class extends mock<IEnvironmentService>() {
|
||||
override isBuilt: boolean = true;
|
||||
override isExtensionDevelopment: boolean = false;
|
||||
}],
|
||||
[IAccessibilitySignalService, new class extends mock<IAccessibilitySignalService>() {
|
||||
override async playSignal() { }
|
||||
override isSoundEnabled() { return false; }
|
||||
}],
|
||||
[IDefaultAccountService, new class extends mock<IDefaultAccountService>() {
|
||||
override onDidChangeDefaultAccount = Event.None;
|
||||
override getDefaultAccount = async () => null;
|
||||
override setDefaultAccountProvider = () => { };
|
||||
}],
|
||||
);
|
||||
|
||||
await withAsyncTestCodeEditor(text, { serviceCollection }, async (editor, _editorViewModel, instantiationService) => {
|
||||
instantiationService.stubInstance(InlineSuggestionsView, {
|
||||
dispose: () => { }
|
||||
});
|
||||
editor.registerAndInstantiateContribution(SnippetController2.ID, SnippetController2);
|
||||
editor.registerAndInstantiateContribution(InlineCompletionsController.ID, InlineCompletionsController);
|
||||
|
||||
editor.hasWidgetFocus = () => true;
|
||||
editor.updateOptions({
|
||||
quickSuggestions: { comments: 'off', strings: 'off', other: 'offWhenInlineCompletions' },
|
||||
});
|
||||
|
||||
const suggestModel = disposableStore.add(
|
||||
editor.invokeWithinContext(accessor => accessor.get(IInstantiationService).createInstance(SuggestModel, editor))
|
||||
);
|
||||
|
||||
await callback(suggestModel, editor);
|
||||
});
|
||||
} finally {
|
||||
disposableStore.dispose();
|
||||
ModifierKeyEmitter.disposeInstance();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
test('suppresses quick suggest when inline completions are showing ghost text', async function () {
|
||||
const inlineProvider: InlineCompletionsProvider = {
|
||||
provideInlineCompletions: (model, pos) => {
|
||||
// Return a completion that extends the current word - must be visible at cursor
|
||||
const word = model.getWordAtPosition(pos);
|
||||
if (!word) { return { items: [] }; }
|
||||
return {
|
||||
items: [{
|
||||
insertText: word.word + 'Suffix',
|
||||
range: new Range(pos.lineNumber, word.startColumn, pos.lineNumber, word.endColumn),
|
||||
}]
|
||||
};
|
||||
},
|
||||
disposeInlineCompletions: () => { }
|
||||
};
|
||||
|
||||
await withSuggestModelAndInlineCompletions('abc def', inlineProvider, async (suggestModel, editor) => {
|
||||
let didSuggest = false;
|
||||
const sub = suggestModel.onDidSuggest(() => { didSuggest = true; });
|
||||
|
||||
editor.setPosition({ lineNumber: 1, column: 4 });
|
||||
editor.trigger('keyboard', Handler.Type, { text: 'd' });
|
||||
|
||||
await timeout(200);
|
||||
|
||||
sub.dispose();
|
||||
assert.strictEqual(didSuggest, false, 'Quick suggestions should have been suppressed when inline completions are showing');
|
||||
});
|
||||
});
|
||||
|
||||
test('allows quick suggest when inline completions resolve with no results', async function () {
|
||||
const inlineProvider: InlineCompletionsProvider = {
|
||||
provideInlineCompletions: () => ({ items: [] }),
|
||||
disposeInlineCompletions: () => { }
|
||||
};
|
||||
|
||||
await withSuggestModelAndInlineCompletions('abc def', inlineProvider, async (suggestModel, editor) => {
|
||||
let didSuggest = false;
|
||||
const sub = suggestModel.onDidSuggest(e => {
|
||||
didSuggest = true;
|
||||
assert.strictEqual(e.triggerOptions.auto, true);
|
||||
});
|
||||
|
||||
editor.setPosition({ lineNumber: 1, column: 4 });
|
||||
editor.trigger('keyboard', Handler.Type, { text: 'd' });
|
||||
|
||||
await timeout(200);
|
||||
|
||||
sub.dispose();
|
||||
assert.strictEqual(didSuggest, true, 'Quick suggestions should have been triggered after inline completions resolved empty');
|
||||
});
|
||||
});
|
||||
|
||||
test('allows quick suggest when inlineSuggest is disabled even with provider', async function () {
|
||||
const inlineProvider: InlineCompletionsProvider = {
|
||||
provideInlineCompletions: (model, pos) => {
|
||||
const word = model.getWordAtPosition(pos);
|
||||
if (!word) { return { items: [] }; }
|
||||
return {
|
||||
items: [{
|
||||
insertText: word.word + 'Suffix',
|
||||
range: new Range(pos.lineNumber, word.startColumn, pos.lineNumber, word.endColumn),
|
||||
}]
|
||||
};
|
||||
},
|
||||
disposeInlineCompletions: () => { }
|
||||
};
|
||||
|
||||
await withSuggestModelAndInlineCompletions('abc def', inlineProvider, async (suggestModel, editor) => {
|
||||
editor.updateOptions({ inlineSuggest: { enabled: false } });
|
||||
|
||||
let didSuggest = false;
|
||||
const sub = suggestModel.onDidSuggest(e => {
|
||||
didSuggest = true;
|
||||
assert.strictEqual(e.triggerOptions.auto, true);
|
||||
});
|
||||
|
||||
editor.setPosition({ lineNumber: 1, column: 4 });
|
||||
editor.trigger('keyboard', Handler.Type, { text: 'd' });
|
||||
|
||||
await timeout(200);
|
||||
|
||||
sub.dispose();
|
||||
assert.strictEqual(didSuggest, true, 'Quick suggestions should have been triggered when inlineSuggest is disabled');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user