Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/editor/contrib/inlineCompletions/test/browser/suggestWidgetModel.test.ts
4798 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import { timeout } from '../../../../../base/common/async.js';
7
import { Event } from '../../../../../base/common/event.js';
8
import { DisposableStore } from '../../../../../base/common/lifecycle.js';
9
import { mock } from '../../../../../base/test/common/mock.js';
10
import { runWithFakedTimers } from '../../../../../base/test/common/timeTravelScheduler.js';
11
import { Range } from '../../../../common/core/range.js';
12
import { CompletionItemKind, CompletionItemProvider } from '../../../../common/languages.js';
13
import { IEditorWorkerService } from '../../../../common/services/editorWorker.js';
14
import { ViewModel } from '../../../../common/viewModel/viewModelImpl.js';
15
import { GhostTextContext } from './utils.js';
16
import { SnippetController2 } from '../../../snippet/browser/snippetController2.js';
17
import { SuggestController } from '../../../suggest/browser/suggestController.js';
18
import { ISuggestMemoryService } from '../../../suggest/browser/suggestMemory.js';
19
import { ITestCodeEditor, TestCodeEditorInstantiationOptions, withAsyncTestCodeEditor } from '../../../../test/browser/testCodeEditor.js';
20
import { IMenu, IMenuService } from '../../../../../platform/actions/common/actions.js';
21
import { ServiceCollection } from '../../../../../platform/instantiation/common/serviceCollection.js';
22
import { IKeybindingService } from '../../../../../platform/keybinding/common/keybinding.js';
23
import { MockKeybindingService } from '../../../../../platform/keybinding/test/common/mockKeybindingService.js';
24
import { ILogService, NullLogService } from '../../../../../platform/log/common/log.js';
25
import { InMemoryStorageService, IStorageService } from '../../../../../platform/storage/common/storage.js';
26
import { ITelemetryService } from '../../../../../platform/telemetry/common/telemetry.js';
27
import { NullTelemetryService } from '../../../../../platform/telemetry/common/telemetryUtils.js';
28
import assert from 'assert';
29
import { ILabelService } from '../../../../../platform/label/common/label.js';
30
import { IWorkspaceContextService } from '../../../../../platform/workspace/common/workspace.js';
31
import { LanguageFeaturesService } from '../../../../common/services/languageFeaturesService.js';
32
import { ILanguageFeaturesService } from '../../../../common/services/languageFeatures.js';
33
import { InlineCompletionsModel } from '../../browser/model/inlineCompletionsModel.js';
34
import { InlineCompletionsController } from '../../browser/controller/inlineCompletionsController.js';
35
import { autorun } from '../../../../../base/common/observable.js';
36
import { setUnexpectedErrorHandler } from '../../../../../base/common/errors.js';
37
import { IAccessibilitySignalService } from '../../../../../platform/accessibilitySignal/browser/accessibilitySignalService.js';
38
import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../base/test/common/utils.js';
39
import { IDefaultAccountService } from '../../../../../platform/defaultAccount/common/defaultAccount.js';
40
import { ModifierKeyEmitter } from '../../../../../base/browser/dom.js';
41
import { InlineSuggestionsView } from '../../browser/view/inlineSuggestionsView.js';
42
43
suite('Suggest Widget Model', () => {
44
ensureNoDisposablesAreLeakedInTestSuite();
45
46
setup(() => {
47
setUnexpectedErrorHandler(function (err) {
48
throw err;
49
});
50
});
51
52
// This test is skipped because the fix for this causes https://github.com/microsoft/vscode/issues/166023
53
test.skip('Active', async () => {
54
await withAsyncTestCodeEditorAndInlineCompletionsModel('',
55
{ fakeClock: true, provider, },
56
async ({ editor, editorViewModel, context, model }) => {
57
let last: boolean | undefined = undefined;
58
const history = new Array<boolean>();
59
const d = autorun(reader => {
60
/** @description debug */
61
const selectedSuggestItem = !!model.debugGetSelectedSuggestItem().read(reader);
62
if (last !== selectedSuggestItem) {
63
last = selectedSuggestItem;
64
history.push(last);
65
}
66
});
67
68
context.keyboardType('h');
69
const suggestController = (editor.getContribution(SuggestController.ID) as SuggestController);
70
suggestController.triggerSuggest();
71
await timeout(1000);
72
assert.deepStrictEqual(history.splice(0), [false, true]);
73
74
context.keyboardType('.');
75
await timeout(1000);
76
77
// No flicker here
78
assert.deepStrictEqual(history.splice(0), []);
79
suggestController.cancelSuggestWidget();
80
await timeout(1000);
81
82
assert.deepStrictEqual(history.splice(0), [false]);
83
84
d.dispose();
85
}
86
);
87
});
88
89
test('Ghost Text', async () => {
90
await withAsyncTestCodeEditorAndInlineCompletionsModel('',
91
{ fakeClock: true, provider, suggest: { preview: true } },
92
async ({ editor, editorViewModel, context, model }) => {
93
context.keyboardType('h');
94
const suggestController = (editor.getContribution(SuggestController.ID) as SuggestController);
95
suggestController.triggerSuggest();
96
await timeout(1000);
97
assert.deepStrictEqual(context.getAndClearViewStates(), ['', 'h[ello]']);
98
99
context.keyboardType('.');
100
await timeout(1000);
101
assert.deepStrictEqual(context.getAndClearViewStates(), ['h', 'hello.[hello]']);
102
103
suggestController.cancelSuggestWidget();
104
105
await timeout(1000);
106
assert.deepStrictEqual(context.getAndClearViewStates(), ['hello.']);
107
}
108
);
109
});
110
});
111
112
const provider: CompletionItemProvider = {
113
_debugDisplayName: 'test',
114
triggerCharacters: ['.'],
115
async provideCompletionItems(model, pos) {
116
const word = model.getWordAtPosition(pos);
117
const range = word
118
? { startLineNumber: 1, startColumn: word.startColumn, endLineNumber: 1, endColumn: word.endColumn }
119
: Range.fromPositions(pos);
120
121
return {
122
suggestions: [{
123
insertText: 'hello',
124
kind: CompletionItemKind.Text,
125
label: 'hello',
126
range,
127
commitCharacters: ['.'],
128
}]
129
};
130
},
131
};
132
133
async function withAsyncTestCodeEditorAndInlineCompletionsModel(
134
text: string,
135
options: TestCodeEditorInstantiationOptions & { provider?: CompletionItemProvider; fakeClock?: boolean; serviceCollection?: never },
136
callback: (args: { editor: ITestCodeEditor; editorViewModel: ViewModel; model: InlineCompletionsModel; context: GhostTextContext }) => Promise<void>
137
): Promise<void> {
138
await runWithFakedTimers({ useFakeTimers: options.fakeClock }, async () => {
139
const disposableStore = new DisposableStore();
140
141
try {
142
const serviceCollection = new ServiceCollection(
143
[ITelemetryService, NullTelemetryService],
144
[ILogService, new NullLogService()],
145
[IStorageService, disposableStore.add(new InMemoryStorageService())],
146
[IKeybindingService, new MockKeybindingService()],
147
[IEditorWorkerService, new class extends mock<IEditorWorkerService>() {
148
override computeWordRanges() {
149
return Promise.resolve({});
150
}
151
}],
152
[ISuggestMemoryService, new class extends mock<ISuggestMemoryService>() {
153
override memorize(): void { }
154
override select(): number { return 0; }
155
}],
156
[IMenuService, new class extends mock<IMenuService>() {
157
override createMenu() {
158
return new class extends mock<IMenu>() {
159
override onDidChange = Event.None;
160
override dispose() { }
161
};
162
}
163
}],
164
[ILabelService, new class extends mock<ILabelService>() { }],
165
[IWorkspaceContextService, new class extends mock<IWorkspaceContextService>() { }],
166
// eslint-disable-next-line local/code-no-any-casts
167
[IAccessibilitySignalService, {
168
playSignal: async () => { },
169
isSoundEnabled(signal: unknown) { return false; },
170
} as any],
171
[IDefaultAccountService, new class extends mock<IDefaultAccountService>() {
172
override onDidChangeDefaultAccount = Event.None;
173
override getDefaultAccount = async () => null;
174
override setDefaultAccount = () => { };
175
}],
176
);
177
178
if (options.provider) {
179
const languageFeaturesService = new LanguageFeaturesService();
180
serviceCollection.set(ILanguageFeaturesService, languageFeaturesService);
181
disposableStore.add(languageFeaturesService.completionProvider.register({ pattern: '**' }, options.provider));
182
}
183
184
await withAsyncTestCodeEditor(text, { ...options, serviceCollection }, async (editor, editorViewModel, instantiationService) => {
185
instantiationService.stubInstance(InlineSuggestionsView, {
186
dispose: () => { }
187
});
188
editor.registerAndInstantiateContribution(SnippetController2.ID, SnippetController2);
189
editor.registerAndInstantiateContribution(SuggestController.ID, SuggestController);
190
editor.registerAndInstantiateContribution(InlineCompletionsController.ID, InlineCompletionsController);
191
const model = InlineCompletionsController.get(editor)?.model.get()!;
192
193
const context = new GhostTextContext(model, editor);
194
await callback({ editor, editorViewModel, model, context });
195
context.dispose();
196
});
197
} finally {
198
disposableStore.dispose();
199
ModifierKeyEmitter.disposeInstance();
200
}
201
});
202
}
203
204