Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/editor/contrib/suggest/browser/suggestModel.ts
4797 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 { TimeoutTimer } from '../../../../base/common/async.js';
7
import { CancellationTokenSource } from '../../../../base/common/cancellation.js';
8
import { onUnexpectedError } from '../../../../base/common/errors.js';
9
import { Emitter, Event } from '../../../../base/common/event.js';
10
import { DisposableStore, dispose, IDisposable } from '../../../../base/common/lifecycle.js';
11
import { getLeadingWhitespace, isHighSurrogate, isLowSurrogate } from '../../../../base/common/strings.js';
12
import { ICodeEditor } from '../../../browser/editorBrowser.js';
13
import { EditorOption } from '../../../common/config/editorOptions.js';
14
import { CursorChangeReason, ICursorSelectionChangedEvent } from '../../../common/cursorEvents.js';
15
import { IPosition, Position } from '../../../common/core/position.js';
16
import { Selection } from '../../../common/core/selection.js';
17
import { ITextModel } from '../../../common/model.js';
18
import { CompletionContext, CompletionItemKind, CompletionItemProvider, CompletionTriggerKind } from '../../../common/languages.js';
19
import { IEditorWorkerService } from '../../../common/services/editorWorker.js';
20
import { WordDistance } from './wordDistance.js';
21
import { IClipboardService } from '../../../../platform/clipboard/common/clipboardService.js';
22
import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js';
23
import { IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js';
24
import { ILogService } from '../../../../platform/log/common/log.js';
25
import { ITelemetryService } from '../../../../platform/telemetry/common/telemetry.js';
26
import { CompletionModel } from './completionModel.js';
27
import { CompletionDurations, CompletionItem, CompletionOptions, getSnippetSuggestSupport, provideSuggestionItems, QuickSuggestionsOptions, SnippetSortOrder } from './suggest.js';
28
import { IWordAtPosition } from '../../../common/core/wordHelper.js';
29
import { ILanguageFeaturesService } from '../../../common/services/languageFeatures.js';
30
import { FuzzyScoreOptions } from '../../../../base/common/filters.js';
31
import { assertType } from '../../../../base/common/types.js';
32
import { InlineCompletionContextKeys } from '../../inlineCompletions/browser/controller/inlineCompletionContextKeys.js';
33
import { SnippetController2 } from '../../snippet/browser/snippetController2.js';
34
import { IEnvironmentService } from '../../../../platform/environment/common/environment.js';
35
36
export interface ICancelEvent {
37
readonly retrigger: boolean;
38
}
39
40
export interface ITriggerEvent {
41
readonly auto: boolean;
42
readonly shy: boolean;
43
readonly position: IPosition;
44
}
45
46
export interface ISuggestEvent {
47
readonly completionModel: CompletionModel;
48
readonly isFrozen: boolean;
49
readonly triggerOptions: SuggestTriggerOptions;
50
}
51
52
export interface SuggestTriggerOptions {
53
readonly auto: boolean;
54
readonly shy?: boolean;
55
readonly refilter?: boolean;
56
readonly retrigger?: boolean;
57
readonly triggerKind?: CompletionTriggerKind;
58
readonly triggerCharacter?: string;
59
readonly clipboardText?: string;
60
completionOptions?: Partial<CompletionOptions>;
61
}
62
63
export class LineContext {
64
65
static shouldAutoTrigger(editor: ICodeEditor): boolean {
66
if (!editor.hasModel()) {
67
return false;
68
}
69
const model = editor.getModel();
70
const pos = editor.getPosition();
71
model.tokenization.tokenizeIfCheap(pos.lineNumber);
72
73
const word = model.getWordAtPosition(pos);
74
if (!word) {
75
return false;
76
}
77
if (word.endColumn !== pos.column &&
78
word.startColumn + 1 !== pos.column /* after typing a single character before a word */) {
79
return false;
80
}
81
if (!isNaN(Number(word.word))) {
82
return false;
83
}
84
return true;
85
}
86
87
readonly lineNumber: number;
88
readonly column: number;
89
readonly leadingLineContent: string;
90
readonly leadingWord: IWordAtPosition;
91
readonly triggerOptions: SuggestTriggerOptions;
92
93
constructor(model: ITextModel, position: Position, triggerOptions: SuggestTriggerOptions) {
94
this.leadingLineContent = model.getLineContent(position.lineNumber).substr(0, position.column - 1);
95
this.leadingWord = model.getWordUntilPosition(position);
96
this.lineNumber = position.lineNumber;
97
this.column = position.column;
98
this.triggerOptions = triggerOptions;
99
}
100
}
101
102
export const enum State {
103
Idle = 0,
104
Manual = 1,
105
Auto = 2
106
}
107
108
function canShowQuickSuggest(editor: ICodeEditor, contextKeyService: IContextKeyService, configurationService: IConfigurationService): boolean {
109
if (!Boolean(contextKeyService.getContextKeyValue(InlineCompletionContextKeys.inlineSuggestionVisible.key))) {
110
// Allow if there is no inline suggestion.
111
return true;
112
}
113
const suppressSuggestions = contextKeyService.getContextKeyValue<boolean | undefined>(InlineCompletionContextKeys.suppressSuggestions.key);
114
if (suppressSuggestions !== undefined) {
115
return !suppressSuggestions;
116
}
117
return !editor.getOption(EditorOption.inlineSuggest).suppressSuggestions;
118
}
119
120
function canShowSuggestOnTriggerCharacters(editor: ICodeEditor, contextKeyService: IContextKeyService, configurationService: IConfigurationService): boolean {
121
if (!Boolean(contextKeyService.getContextKeyValue('inlineSuggestionVisible'))) {
122
// Allow if there is no inline suggestion.
123
return true;
124
}
125
const suppressSuggestions = contextKeyService.getContextKeyValue<boolean | undefined>(InlineCompletionContextKeys.suppressSuggestions.key);
126
if (suppressSuggestions !== undefined) {
127
return !suppressSuggestions;
128
}
129
return !editor.getOption(EditorOption.inlineSuggest).suppressSuggestions;
130
}
131
132
export class SuggestModel implements IDisposable {
133
134
private readonly _toDispose = new DisposableStore();
135
private readonly _triggerCharacterListener = new DisposableStore();
136
private readonly _triggerQuickSuggest = new TimeoutTimer();
137
138
private _triggerState: SuggestTriggerOptions | undefined = undefined;
139
private _requestToken?: CancellationTokenSource;
140
private _context?: LineContext;
141
private _currentSelection: Selection;
142
143
private _completionModel: CompletionModel | undefined;
144
private readonly _completionDisposables = new DisposableStore();
145
private readonly _onDidCancel = new Emitter<ICancelEvent>();
146
private readonly _onDidTrigger = new Emitter<ITriggerEvent>();
147
private readonly _onDidSuggest = new Emitter<ISuggestEvent>();
148
149
readonly onDidCancel: Event<ICancelEvent> = this._onDidCancel.event;
150
readonly onDidTrigger: Event<ITriggerEvent> = this._onDidTrigger.event;
151
readonly onDidSuggest: Event<ISuggestEvent> = this._onDidSuggest.event;
152
153
constructor(
154
private readonly _editor: ICodeEditor,
155
@IEditorWorkerService private readonly _editorWorkerService: IEditorWorkerService,
156
@IClipboardService private readonly _clipboardService: IClipboardService,
157
@ITelemetryService private readonly _telemetryService: ITelemetryService,
158
@ILogService private readonly _logService: ILogService,
159
@IContextKeyService private readonly _contextKeyService: IContextKeyService,
160
@IConfigurationService private readonly _configurationService: IConfigurationService,
161
@ILanguageFeaturesService private readonly _languageFeaturesService: ILanguageFeaturesService,
162
@IEnvironmentService private readonly _envService: IEnvironmentService,
163
) {
164
this._currentSelection = this._editor.getSelection() || new Selection(1, 1, 1, 1);
165
166
// wire up various listeners
167
this._toDispose.add(this._editor.onDidChangeModel(() => {
168
this._updateTriggerCharacters();
169
this.cancel();
170
}));
171
this._toDispose.add(this._editor.onDidChangeModelLanguage(() => {
172
this._updateTriggerCharacters();
173
this.cancel();
174
}));
175
this._toDispose.add(this._editor.onDidChangeConfiguration(() => {
176
this._updateTriggerCharacters();
177
}));
178
this._toDispose.add(this._languageFeaturesService.completionProvider.onDidChange(() => {
179
this._updateTriggerCharacters();
180
this._updateActiveSuggestSession();
181
}));
182
183
let editorIsComposing = false;
184
this._toDispose.add(this._editor.onDidCompositionStart(() => {
185
editorIsComposing = true;
186
}));
187
this._toDispose.add(this._editor.onDidCompositionEnd(() => {
188
editorIsComposing = false;
189
this._onCompositionEnd();
190
}));
191
this._toDispose.add(this._editor.onDidChangeCursorSelection(e => {
192
// only trigger suggest when the editor isn't composing a character
193
if (!editorIsComposing) {
194
this._onCursorChange(e);
195
}
196
}));
197
this._toDispose.add(this._editor.onDidChangeModelContent(() => {
198
// only filter completions when the editor isn't composing a character
199
// allow-any-unicode-next-line
200
// e.g. ¨ + u makes ü but just ¨ cannot be used for filtering
201
if (!editorIsComposing && this._triggerState !== undefined) {
202
this._refilterCompletionItems();
203
}
204
}));
205
206
this._updateTriggerCharacters();
207
}
208
209
dispose(): void {
210
dispose(this._triggerCharacterListener);
211
dispose([this._onDidCancel, this._onDidSuggest, this._onDidTrigger, this._triggerQuickSuggest]);
212
this._toDispose.dispose();
213
this._completionDisposables.dispose();
214
this.cancel();
215
}
216
217
private _updateTriggerCharacters(): void {
218
this._triggerCharacterListener.clear();
219
220
if (this._editor.getOption(EditorOption.readOnly)
221
|| !this._editor.hasModel()
222
|| !this._editor.getOption(EditorOption.suggestOnTriggerCharacters)) {
223
224
return;
225
}
226
227
const supportsByTriggerCharacter = new Map<string, Set<CompletionItemProvider>>();
228
for (const support of this._languageFeaturesService.completionProvider.all(this._editor.getModel())) {
229
for (const ch of support.triggerCharacters || []) {
230
let set = supportsByTriggerCharacter.get(ch);
231
if (!set) {
232
set = new Set();
233
const suggestSupport = getSnippetSuggestSupport();
234
if (suggestSupport) {
235
set.add(suggestSupport);
236
}
237
supportsByTriggerCharacter.set(ch, set);
238
}
239
set.add(support);
240
}
241
}
242
243
244
const checkTriggerCharacter = (text?: string) => {
245
246
if (!canShowSuggestOnTriggerCharacters(this._editor, this._contextKeyService, this._configurationService)) {
247
return;
248
}
249
250
if (LineContext.shouldAutoTrigger(this._editor)) {
251
// don't trigger by trigger characters when this is a case for quick suggest
252
return;
253
}
254
255
if (!text) {
256
// came here from the compositionEnd-event
257
const position = this._editor.getPosition()!;
258
const model = this._editor.getModel()!;
259
text = model.getLineContent(position.lineNumber).substr(0, position.column - 1);
260
}
261
262
let lastChar = '';
263
if (isLowSurrogate(text.charCodeAt(text.length - 1))) {
264
if (isHighSurrogate(text.charCodeAt(text.length - 2))) {
265
lastChar = text.substr(text.length - 2);
266
}
267
} else {
268
lastChar = text.charAt(text.length - 1);
269
}
270
271
const supports = supportsByTriggerCharacter.get(lastChar);
272
if (supports) {
273
274
// keep existing items that where not computed by the
275
// supports/providers that want to trigger now
276
const providerItemsToReuse = new Map<CompletionItemProvider, CompletionItem[]>();
277
if (this._completionModel) {
278
for (const [provider, items] of this._completionModel.getItemsByProvider()) {
279
if (!supports.has(provider)) {
280
providerItemsToReuse.set(provider, items);
281
}
282
}
283
}
284
285
this.trigger({
286
auto: true,
287
triggerKind: CompletionTriggerKind.TriggerCharacter,
288
triggerCharacter: lastChar,
289
retrigger: Boolean(this._completionModel),
290
clipboardText: this._completionModel?.clipboardText,
291
completionOptions: { providerFilter: supports, providerItemsToReuse }
292
});
293
}
294
};
295
296
this._triggerCharacterListener.add(this._editor.onDidType(checkTriggerCharacter));
297
this._triggerCharacterListener.add(this._editor.onDidCompositionEnd(() => checkTriggerCharacter()));
298
}
299
300
// --- trigger/retrigger/cancel suggest
301
302
get state(): State {
303
if (!this._triggerState) {
304
return State.Idle;
305
} else if (!this._triggerState.auto) {
306
return State.Manual;
307
} else {
308
return State.Auto;
309
}
310
}
311
312
cancel(retrigger: boolean = false): void {
313
if (this._triggerState !== undefined) {
314
this._triggerQuickSuggest.cancel();
315
this._requestToken?.cancel();
316
this._requestToken = undefined;
317
this._triggerState = undefined;
318
this._completionModel = undefined;
319
this._context = undefined;
320
this._onDidCancel.fire({ retrigger });
321
}
322
}
323
324
clear() {
325
this._completionDisposables.clear();
326
}
327
328
private _updateActiveSuggestSession(): void {
329
if (this._triggerState !== undefined) {
330
if (!this._editor.hasModel() || !this._languageFeaturesService.completionProvider.has(this._editor.getModel())) {
331
this.cancel();
332
} else {
333
this.trigger({ auto: this._triggerState.auto, retrigger: true });
334
}
335
}
336
}
337
338
private _onCursorChange(e: ICursorSelectionChangedEvent): void {
339
340
if (!this._editor.hasModel()) {
341
return;
342
}
343
344
const prevSelection = this._currentSelection;
345
this._currentSelection = this._editor.getSelection();
346
347
if (!e.selection.isEmpty()
348
|| (e.reason !== CursorChangeReason.NotSet && e.reason !== CursorChangeReason.Explicit)
349
|| (e.source !== 'keyboard' && e.source !== 'deleteLeft')
350
) {
351
// Early exit if nothing needs to be done!
352
// Leave some form of early exit check here if you wish to continue being a cursor position change listener ;)
353
this.cancel();
354
return;
355
}
356
357
358
if (this._triggerState === undefined && e.reason === CursorChangeReason.NotSet) {
359
if (prevSelection.containsRange(this._currentSelection) || prevSelection.getEndPosition().isBeforeOrEqual(this._currentSelection.getPosition())) {
360
// cursor did move RIGHT due to typing -> trigger quick suggest
361
this._doTriggerQuickSuggest();
362
}
363
364
} else if (this._triggerState !== undefined && e.reason === CursorChangeReason.Explicit) {
365
// suggest is active and something like cursor keys are used to move
366
// the cursor. this means we can refilter at the new position
367
this._refilterCompletionItems();
368
}
369
}
370
371
private _onCompositionEnd(): void {
372
// trigger or refilter when composition ends
373
if (this._triggerState === undefined) {
374
this._doTriggerQuickSuggest();
375
} else {
376
this._refilterCompletionItems();
377
}
378
}
379
380
private _doTriggerQuickSuggest(): void {
381
382
if (QuickSuggestionsOptions.isAllOff(this._editor.getOption(EditorOption.quickSuggestions))) {
383
// not enabled
384
return;
385
}
386
387
if (this._editor.getOption(EditorOption.suggest).snippetsPreventQuickSuggestions && SnippetController2.get(this._editor)?.isInSnippet()) {
388
// no quick suggestion when in snippet mode
389
return;
390
}
391
392
this.cancel();
393
394
this._triggerQuickSuggest.cancelAndSet(() => {
395
if (this._triggerState !== undefined) {
396
return;
397
}
398
if (!LineContext.shouldAutoTrigger(this._editor)) {
399
return;
400
}
401
if (!this._editor.hasModel() || !this._editor.hasWidgetFocus()) {
402
return;
403
}
404
const model = this._editor.getModel();
405
const pos = this._editor.getPosition();
406
// validate enabled now
407
const config = this._editor.getOption(EditorOption.quickSuggestions);
408
if (QuickSuggestionsOptions.isAllOff(config)) {
409
return;
410
}
411
412
if (!QuickSuggestionsOptions.isAllOn(config)) {
413
// Check the type of the token that triggered this
414
model.tokenization.tokenizeIfCheap(pos.lineNumber);
415
const lineTokens = model.tokenization.getLineTokens(pos.lineNumber);
416
const tokenType = lineTokens.getStandardTokenType(lineTokens.findTokenIndexAtOffset(Math.max(pos.column - 1 - 1, 0)));
417
if (QuickSuggestionsOptions.valueFor(config, tokenType) !== 'on') {
418
return;
419
}
420
}
421
422
if (!canShowQuickSuggest(this._editor, this._contextKeyService, this._configurationService)) {
423
// do not trigger quick suggestions if inline suggestions are shown
424
return;
425
}
426
427
if (!this._languageFeaturesService.completionProvider.has(model)) {
428
return;
429
}
430
431
// we made it till here -> trigger now
432
this.trigger({ auto: true });
433
434
}, this._editor.getOption(EditorOption.quickSuggestionsDelay));
435
}
436
437
private _refilterCompletionItems(): void {
438
assertType(this._editor.hasModel());
439
assertType(this._triggerState !== undefined);
440
441
const model = this._editor.getModel();
442
const position = this._editor.getPosition();
443
const ctx = new LineContext(model, position, { ...this._triggerState, refilter: true });
444
this._onNewContext(ctx);
445
}
446
447
trigger(options: SuggestTriggerOptions): void {
448
if (!this._editor.hasModel()) {
449
return;
450
}
451
452
const model = this._editor.getModel();
453
const ctx = new LineContext(model, this._editor.getPosition(), options);
454
455
// Cancel previous requests, change state & update UI
456
this.cancel(options.retrigger);
457
this._triggerState = options;
458
this._onDidTrigger.fire({ auto: options.auto, shy: options.shy ?? false, position: this._editor.getPosition() });
459
460
// Capture context when request was sent
461
this._context = ctx;
462
463
// Build context for request
464
let suggestCtx: CompletionContext = { triggerKind: options.triggerKind ?? CompletionTriggerKind.Invoke };
465
if (options.triggerCharacter) {
466
suggestCtx = {
467
triggerKind: CompletionTriggerKind.TriggerCharacter,
468
triggerCharacter: options.triggerCharacter
469
};
470
}
471
472
this._requestToken = new CancellationTokenSource();
473
474
// kind filter and snippet sort rules
475
const snippetSuggestions = this._editor.getOption(EditorOption.snippetSuggestions);
476
let snippetSortOrder = SnippetSortOrder.Inline;
477
switch (snippetSuggestions) {
478
case 'top':
479
snippetSortOrder = SnippetSortOrder.Top;
480
break;
481
// ↓ that's the default anyways...
482
// case 'inline':
483
// snippetSortOrder = SnippetSortOrder.Inline;
484
// break;
485
case 'bottom':
486
snippetSortOrder = SnippetSortOrder.Bottom;
487
break;
488
}
489
490
const { itemKind: itemKindFilter, showDeprecated } = SuggestModel.createSuggestFilter(this._editor);
491
const completionOptions = new CompletionOptions(snippetSortOrder, options.completionOptions?.kindFilter ?? itemKindFilter, options.completionOptions?.providerFilter, options.completionOptions?.providerItemsToReuse, showDeprecated);
492
const wordDistance = WordDistance.create(this._editorWorkerService, this._editor);
493
494
const completions = provideSuggestionItems(
495
this._languageFeaturesService.completionProvider,
496
model,
497
this._editor.getPosition(),
498
completionOptions,
499
suggestCtx,
500
this._requestToken.token
501
);
502
503
Promise.all([completions, wordDistance]).then(async ([completions, wordDistance]) => {
504
505
this._requestToken?.dispose();
506
507
if (!this._editor.hasModel()) {
508
completions.disposable.dispose();
509
return;
510
}
511
512
let clipboardText = options?.clipboardText;
513
if (!clipboardText && completions.needsClipboard) {
514
clipboardText = await this._clipboardService.readText();
515
}
516
517
if (this._triggerState === undefined) {
518
completions.disposable.dispose();
519
return;
520
}
521
522
const model = this._editor.getModel();
523
// const items = completions.items;
524
525
// if (existing) {
526
// const cmpFn = getSuggestionComparator(snippetSortOrder);
527
// items = items.concat(existing.items).sort(cmpFn);
528
// }
529
530
const ctx = new LineContext(model, this._editor.getPosition(), options);
531
const fuzzySearchOptions = {
532
...FuzzyScoreOptions.default,
533
firstMatchCanBeWeak: !this._editor.getOption(EditorOption.suggest).matchOnWordStartOnly
534
};
535
this._completionModel = new CompletionModel(completions.items, this._context!.column, {
536
leadingLineContent: ctx.leadingLineContent,
537
characterCountDelta: ctx.column - this._context!.column
538
},
539
wordDistance,
540
this._editor.getOption(EditorOption.suggest),
541
this._editor.getOption(EditorOption.snippetSuggestions),
542
fuzzySearchOptions,
543
clipboardText
544
);
545
546
// store containers so that they can be disposed later
547
this._completionDisposables.add(completions.disposable);
548
549
this._onNewContext(ctx);
550
551
// finally report telemetry about durations
552
this._reportDurationsTelemetry(completions.durations);
553
554
// report invalid completions by source
555
if (!this._envService.isBuilt || this._envService.isExtensionDevelopment) {
556
for (const item of completions.items) {
557
if (item.isInvalid) {
558
this._logService.warn(`[suggest] did IGNORE invalid completion item from ${item.provider._debugDisplayName}`, item.completion);
559
}
560
}
561
}
562
563
}).catch(onUnexpectedError);
564
}
565
566
/**
567
* Report durations telemetry with a 1% sampling rate.
568
* The telemetry is reported only if a random number between 0 and 100 is less than or equal to 1.
569
*/
570
private _reportDurationsTelemetry(durations: CompletionDurations): void {
571
if (Math.random() > 0.0001) { // 0.01%
572
return;
573
}
574
575
setTimeout(() => {
576
type Durations = { data: string };
577
type DurationsClassification = {
578
owner: 'jrieken';
579
comment: 'Completions performance numbers';
580
data: { comment: 'Durations per source and overall'; classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth' };
581
};
582
this._telemetryService.publicLog2<Durations, DurationsClassification>('suggest.durations.json', { data: JSON.stringify(durations) });
583
this._logService.debug('suggest.durations.json', durations);
584
});
585
}
586
587
static createSuggestFilter(editor: ICodeEditor): { itemKind: Set<CompletionItemKind>; showDeprecated: boolean } {
588
// kind filter and snippet sort rules
589
const result = new Set<CompletionItemKind>();
590
591
// snippet setting
592
const snippetSuggestions = editor.getOption(EditorOption.snippetSuggestions);
593
if (snippetSuggestions === 'none') {
594
result.add(CompletionItemKind.Snippet);
595
}
596
597
// type setting
598
const suggestOptions = editor.getOption(EditorOption.suggest);
599
if (!suggestOptions.showMethods) { result.add(CompletionItemKind.Method); }
600
if (!suggestOptions.showFunctions) { result.add(CompletionItemKind.Function); }
601
if (!suggestOptions.showConstructors) { result.add(CompletionItemKind.Constructor); }
602
if (!suggestOptions.showFields) { result.add(CompletionItemKind.Field); }
603
if (!suggestOptions.showVariables) { result.add(CompletionItemKind.Variable); }
604
if (!suggestOptions.showClasses) { result.add(CompletionItemKind.Class); }
605
if (!suggestOptions.showStructs) { result.add(CompletionItemKind.Struct); }
606
if (!suggestOptions.showInterfaces) { result.add(CompletionItemKind.Interface); }
607
if (!suggestOptions.showModules) { result.add(CompletionItemKind.Module); }
608
if (!suggestOptions.showProperties) { result.add(CompletionItemKind.Property); }
609
if (!suggestOptions.showEvents) { result.add(CompletionItemKind.Event); }
610
if (!suggestOptions.showOperators) { result.add(CompletionItemKind.Operator); }
611
if (!suggestOptions.showUnits) { result.add(CompletionItemKind.Unit); }
612
if (!suggestOptions.showValues) { result.add(CompletionItemKind.Value); }
613
if (!suggestOptions.showConstants) { result.add(CompletionItemKind.Constant); }
614
if (!suggestOptions.showEnums) { result.add(CompletionItemKind.Enum); }
615
if (!suggestOptions.showEnumMembers) { result.add(CompletionItemKind.EnumMember); }
616
if (!suggestOptions.showKeywords) { result.add(CompletionItemKind.Keyword); }
617
if (!suggestOptions.showWords) { result.add(CompletionItemKind.Text); }
618
if (!suggestOptions.showColors) { result.add(CompletionItemKind.Color); }
619
if (!suggestOptions.showFiles) { result.add(CompletionItemKind.File); }
620
if (!suggestOptions.showReferences) { result.add(CompletionItemKind.Reference); }
621
if (!suggestOptions.showColors) { result.add(CompletionItemKind.Customcolor); }
622
if (!suggestOptions.showFolders) { result.add(CompletionItemKind.Folder); }
623
if (!suggestOptions.showTypeParameters) { result.add(CompletionItemKind.TypeParameter); }
624
if (!suggestOptions.showSnippets) { result.add(CompletionItemKind.Snippet); }
625
if (!suggestOptions.showUsers) { result.add(CompletionItemKind.User); }
626
if (!suggestOptions.showIssues) { result.add(CompletionItemKind.Issue); }
627
628
return { itemKind: result, showDeprecated: suggestOptions.showDeprecated };
629
}
630
631
private _onNewContext(ctx: LineContext): void {
632
633
if (!this._context) {
634
// happens when 24x7 IntelliSense is enabled and still in its delay
635
return;
636
}
637
638
if (ctx.lineNumber !== this._context.lineNumber) {
639
// e.g. happens when pressing Enter while IntelliSense is computed
640
this.cancel();
641
return;
642
}
643
644
if (getLeadingWhitespace(ctx.leadingLineContent) !== getLeadingWhitespace(this._context.leadingLineContent)) {
645
// cancel IntelliSense when line start changes
646
// happens when the current word gets outdented
647
this.cancel();
648
return;
649
}
650
651
if (ctx.column < this._context.column) {
652
// typed -> moved cursor LEFT -> retrigger if still on a word
653
if (ctx.leadingWord.word) {
654
this.trigger({ auto: this._context.triggerOptions.auto, retrigger: true });
655
} else {
656
this.cancel();
657
}
658
return;
659
}
660
661
if (!this._completionModel) {
662
// happens when IntelliSense is not yet computed
663
return;
664
}
665
666
if (ctx.leadingWord.word.length !== 0 && ctx.leadingWord.startColumn > this._context.leadingWord.startColumn) {
667
// started a new word while IntelliSense shows -> retrigger but reuse all items that we currently have
668
const shouldAutoTrigger = LineContext.shouldAutoTrigger(this._editor);
669
if (shouldAutoTrigger && this._context) {
670
// shouldAutoTrigger forces tokenization, which can cause pending cursor change events to be emitted, which can cause
671
// suggestions to be cancelled, which causes `this._context` to be undefined
672
const map = this._completionModel.getItemsByProvider();
673
this.trigger({
674
auto: this._context.triggerOptions.auto,
675
retrigger: true,
676
clipboardText: this._completionModel.clipboardText,
677
completionOptions: { providerItemsToReuse: map }
678
});
679
}
680
return;
681
}
682
683
if (ctx.column > this._context.column && this._completionModel.getIncompleteProvider().size > 0 && ctx.leadingWord.word.length !== 0) {
684
// typed -> moved cursor RIGHT & incomple model & still on a word -> retrigger
685
686
const providerItemsToReuse = new Map<CompletionItemProvider, CompletionItem[]>();
687
const providerFilter = new Set<CompletionItemProvider>();
688
for (const [provider, items] of this._completionModel.getItemsByProvider()) {
689
if (items.length > 0 && items[0].container.incomplete) {
690
providerFilter.add(provider);
691
} else {
692
providerItemsToReuse.set(provider, items);
693
}
694
}
695
696
this.trigger({
697
auto: this._context.triggerOptions.auto,
698
triggerKind: CompletionTriggerKind.TriggerForIncompleteCompletions,
699
retrigger: true,
700
clipboardText: this._completionModel.clipboardText,
701
completionOptions: { providerFilter, providerItemsToReuse }
702
});
703
704
} else {
705
// typed -> moved cursor RIGHT -> update UI
706
const oldLineContext = this._completionModel.lineContext;
707
let isFrozen = false;
708
709
this._completionModel.lineContext = {
710
leadingLineContent: ctx.leadingLineContent,
711
characterCountDelta: ctx.column - this._context.column
712
};
713
714
if (this._completionModel.items.length === 0) {
715
716
const shouldAutoTrigger = LineContext.shouldAutoTrigger(this._editor);
717
if (!this._context) {
718
// shouldAutoTrigger forces tokenization, which can cause pending cursor change events to be emitted, which can cause
719
// suggestions to be cancelled, which causes `this._context` to be undefined
720
this.cancel();
721
return;
722
}
723
724
if (shouldAutoTrigger && this._context.leadingWord.endColumn < ctx.leadingWord.startColumn) {
725
// retrigger when heading into a new word
726
this.trigger({ auto: this._context.triggerOptions.auto, retrigger: true });
727
return;
728
}
729
730
if (!this._context.triggerOptions.auto) {
731
// freeze when IntelliSense was manually requested
732
this._completionModel.lineContext = oldLineContext;
733
isFrozen = this._completionModel.items.length > 0;
734
735
if (isFrozen && ctx.leadingWord.word.length === 0) {
736
// there were results before but now there aren't
737
// and also we are not on a word anymore -> cancel
738
this.cancel();
739
return;
740
}
741
742
} else {
743
// nothing left
744
this.cancel();
745
return;
746
}
747
}
748
749
this._onDidSuggest.fire({
750
completionModel: this._completionModel,
751
triggerOptions: ctx.triggerOptions,
752
isFrozen,
753
});
754
}
755
}
756
}
757
758