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
5263 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
if (QuickSuggestionsOptions.valueFor(config, tokenType) !== 'offWhenInlineCompletions'
419
|| (this._languageFeaturesService.inlineCompletionsProvider.has(model) && this._editor.getOption(EditorOption.inlineSuggest).enabled)) {
420
return;
421
}
422
}
423
}
424
425
if (!canShowQuickSuggest(this._editor, this._contextKeyService, this._configurationService)) {
426
// do not trigger quick suggestions if inline suggestions are shown
427
return;
428
}
429
430
if (!this._languageFeaturesService.completionProvider.has(model)) {
431
return;
432
}
433
434
// we made it till here -> trigger now
435
this.trigger({ auto: true });
436
437
}, this._editor.getOption(EditorOption.quickSuggestionsDelay));
438
}
439
440
private _refilterCompletionItems(): void {
441
assertType(this._editor.hasModel());
442
assertType(this._triggerState !== undefined);
443
444
const model = this._editor.getModel();
445
const position = this._editor.getPosition();
446
const ctx = new LineContext(model, position, { ...this._triggerState, refilter: true });
447
this._onNewContext(ctx);
448
}
449
450
trigger(options: SuggestTriggerOptions): void {
451
if (!this._editor.hasModel()) {
452
return;
453
}
454
455
const model = this._editor.getModel();
456
const ctx = new LineContext(model, this._editor.getPosition(), options);
457
458
// Cancel previous requests, change state & update UI
459
this.cancel(options.retrigger);
460
this._triggerState = options;
461
this._onDidTrigger.fire({ auto: options.auto, shy: options.shy ?? false, position: this._editor.getPosition() });
462
463
// Capture context when request was sent
464
this._context = ctx;
465
466
// Build context for request
467
let suggestCtx: CompletionContext = { triggerKind: options.triggerKind ?? CompletionTriggerKind.Invoke };
468
if (options.triggerCharacter) {
469
suggestCtx = {
470
triggerKind: CompletionTriggerKind.TriggerCharacter,
471
triggerCharacter: options.triggerCharacter
472
};
473
}
474
475
this._requestToken = new CancellationTokenSource();
476
477
// kind filter and snippet sort rules
478
const snippetSuggestions = this._editor.getOption(EditorOption.snippetSuggestions);
479
let snippetSortOrder = SnippetSortOrder.Inline;
480
switch (snippetSuggestions) {
481
case 'top':
482
snippetSortOrder = SnippetSortOrder.Top;
483
break;
484
// ↓ that's the default anyways...
485
// case 'inline':
486
// snippetSortOrder = SnippetSortOrder.Inline;
487
// break;
488
case 'bottom':
489
snippetSortOrder = SnippetSortOrder.Bottom;
490
break;
491
}
492
493
const { itemKind: itemKindFilter, showDeprecated } = SuggestModel.createSuggestFilter(this._editor);
494
const completionOptions = new CompletionOptions(snippetSortOrder, options.completionOptions?.kindFilter ?? itemKindFilter, options.completionOptions?.providerFilter, options.completionOptions?.providerItemsToReuse, showDeprecated);
495
const wordDistance = WordDistance.create(this._editorWorkerService, this._editor);
496
497
const completions = provideSuggestionItems(
498
this._languageFeaturesService.completionProvider,
499
model,
500
this._editor.getPosition(),
501
completionOptions,
502
suggestCtx,
503
this._requestToken.token
504
);
505
506
Promise.all([completions, wordDistance]).then(async ([completions, wordDistance]) => {
507
508
this._requestToken?.dispose();
509
510
if (!this._editor.hasModel()) {
511
completions.disposable.dispose();
512
return;
513
}
514
515
let clipboardText = options?.clipboardText;
516
if (!clipboardText && completions.needsClipboard) {
517
clipboardText = await this._clipboardService.readText();
518
}
519
520
if (this._triggerState === undefined) {
521
completions.disposable.dispose();
522
return;
523
}
524
525
const model = this._editor.getModel();
526
// const items = completions.items;
527
528
// if (existing) {
529
// const cmpFn = getSuggestionComparator(snippetSortOrder);
530
// items = items.concat(existing.items).sort(cmpFn);
531
// }
532
533
const ctx = new LineContext(model, this._editor.getPosition(), options);
534
const fuzzySearchOptions = {
535
...FuzzyScoreOptions.default,
536
firstMatchCanBeWeak: !this._editor.getOption(EditorOption.suggest).matchOnWordStartOnly
537
};
538
this._completionModel = new CompletionModel(completions.items, this._context!.column, {
539
leadingLineContent: ctx.leadingLineContent,
540
characterCountDelta: ctx.column - this._context!.column
541
},
542
wordDistance,
543
this._editor.getOption(EditorOption.suggest),
544
this._editor.getOption(EditorOption.snippetSuggestions),
545
fuzzySearchOptions,
546
clipboardText
547
);
548
549
// store containers so that they can be disposed later
550
this._completionDisposables.add(completions.disposable);
551
552
this._onNewContext(ctx);
553
554
// finally report telemetry about durations
555
this._reportDurationsTelemetry(completions.durations);
556
557
// report invalid completions by source
558
if (!this._envService.isBuilt || this._envService.isExtensionDevelopment) {
559
for (const item of completions.items) {
560
if (item.isInvalid) {
561
this._logService.warn(`[suggest] did IGNORE invalid completion item from ${item.provider._debugDisplayName}`, item.completion);
562
}
563
}
564
}
565
566
}).catch(onUnexpectedError);
567
}
568
569
/**
570
* Report durations telemetry with a 1% sampling rate.
571
* The telemetry is reported only if a random number between 0 and 100 is less than or equal to 1.
572
*/
573
private _reportDurationsTelemetry(durations: CompletionDurations): void {
574
if (Math.random() > 0.0001) { // 0.01%
575
return;
576
}
577
578
setTimeout(() => {
579
type Durations = { data: string };
580
type DurationsClassification = {
581
owner: 'jrieken';
582
comment: 'Completions performance numbers';
583
data: { comment: 'Durations per source and overall'; classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth' };
584
};
585
this._telemetryService.publicLog2<Durations, DurationsClassification>('suggest.durations.json', { data: JSON.stringify(durations) });
586
this._logService.debug('suggest.durations.json', durations);
587
});
588
}
589
590
static createSuggestFilter(editor: ICodeEditor): { itemKind: Set<CompletionItemKind>; showDeprecated: boolean } {
591
// kind filter and snippet sort rules
592
const result = new Set<CompletionItemKind>();
593
594
// snippet setting
595
const snippetSuggestions = editor.getOption(EditorOption.snippetSuggestions);
596
if (snippetSuggestions === 'none') {
597
result.add(CompletionItemKind.Snippet);
598
}
599
600
// type setting
601
const suggestOptions = editor.getOption(EditorOption.suggest);
602
if (!suggestOptions.showMethods) { result.add(CompletionItemKind.Method); }
603
if (!suggestOptions.showFunctions) { result.add(CompletionItemKind.Function); }
604
if (!suggestOptions.showConstructors) { result.add(CompletionItemKind.Constructor); }
605
if (!suggestOptions.showFields) { result.add(CompletionItemKind.Field); }
606
if (!suggestOptions.showVariables) { result.add(CompletionItemKind.Variable); }
607
if (!suggestOptions.showClasses) { result.add(CompletionItemKind.Class); }
608
if (!suggestOptions.showStructs) { result.add(CompletionItemKind.Struct); }
609
if (!suggestOptions.showInterfaces) { result.add(CompletionItemKind.Interface); }
610
if (!suggestOptions.showModules) { result.add(CompletionItemKind.Module); }
611
if (!suggestOptions.showProperties) { result.add(CompletionItemKind.Property); }
612
if (!suggestOptions.showEvents) { result.add(CompletionItemKind.Event); }
613
if (!suggestOptions.showOperators) { result.add(CompletionItemKind.Operator); }
614
if (!suggestOptions.showUnits) { result.add(CompletionItemKind.Unit); }
615
if (!suggestOptions.showValues) { result.add(CompletionItemKind.Value); }
616
if (!suggestOptions.showConstants) { result.add(CompletionItemKind.Constant); }
617
if (!suggestOptions.showEnums) { result.add(CompletionItemKind.Enum); }
618
if (!suggestOptions.showEnumMembers) { result.add(CompletionItemKind.EnumMember); }
619
if (!suggestOptions.showKeywords) { result.add(CompletionItemKind.Keyword); }
620
if (!suggestOptions.showWords) { result.add(CompletionItemKind.Text); }
621
if (!suggestOptions.showColors) { result.add(CompletionItemKind.Color); }
622
if (!suggestOptions.showFiles) { result.add(CompletionItemKind.File); }
623
if (!suggestOptions.showReferences) { result.add(CompletionItemKind.Reference); }
624
if (!suggestOptions.showColors) { result.add(CompletionItemKind.Customcolor); }
625
if (!suggestOptions.showFolders) { result.add(CompletionItemKind.Folder); }
626
if (!suggestOptions.showTypeParameters) { result.add(CompletionItemKind.TypeParameter); }
627
if (!suggestOptions.showSnippets) { result.add(CompletionItemKind.Snippet); }
628
if (!suggestOptions.showUsers) { result.add(CompletionItemKind.User); }
629
if (!suggestOptions.showIssues) { result.add(CompletionItemKind.Issue); }
630
631
return { itemKind: result, showDeprecated: suggestOptions.showDeprecated };
632
}
633
634
private _onNewContext(ctx: LineContext): void {
635
636
if (!this._context) {
637
// happens when 24x7 IntelliSense is enabled and still in its delay
638
return;
639
}
640
641
if (ctx.lineNumber !== this._context.lineNumber) {
642
// e.g. happens when pressing Enter while IntelliSense is computed
643
this.cancel();
644
return;
645
}
646
647
if (getLeadingWhitespace(ctx.leadingLineContent) !== getLeadingWhitespace(this._context.leadingLineContent)) {
648
// cancel IntelliSense when line start changes
649
// happens when the current word gets outdented
650
this.cancel();
651
return;
652
}
653
654
if (ctx.column < this._context.column) {
655
// typed -> moved cursor LEFT -> retrigger if still on a word
656
if (ctx.leadingWord.word) {
657
this.trigger({ auto: this._context.triggerOptions.auto, retrigger: true });
658
} else {
659
this.cancel();
660
}
661
return;
662
}
663
664
if (!this._completionModel) {
665
// happens when IntelliSense is not yet computed
666
return;
667
}
668
669
if (ctx.leadingWord.word.length !== 0 && ctx.leadingWord.startColumn > this._context.leadingWord.startColumn) {
670
// started a new word while IntelliSense shows -> retrigger but reuse all items that we currently have
671
const shouldAutoTrigger = LineContext.shouldAutoTrigger(this._editor);
672
if (shouldAutoTrigger && this._context) {
673
// shouldAutoTrigger forces tokenization, which can cause pending cursor change events to be emitted, which can cause
674
// suggestions to be cancelled, which causes `this._context` to be undefined
675
const map = this._completionModel.getItemsByProvider();
676
this.trigger({
677
auto: this._context.triggerOptions.auto,
678
retrigger: true,
679
clipboardText: this._completionModel.clipboardText,
680
completionOptions: { providerItemsToReuse: map }
681
});
682
}
683
return;
684
}
685
686
if (ctx.column > this._context.column && this._completionModel.getIncompleteProvider().size > 0 && ctx.leadingWord.word.length !== 0) {
687
// typed -> moved cursor RIGHT & incomple model & still on a word -> retrigger
688
689
const providerItemsToReuse = new Map<CompletionItemProvider, CompletionItem[]>();
690
const providerFilter = new Set<CompletionItemProvider>();
691
for (const [provider, items] of this._completionModel.getItemsByProvider()) {
692
if (items.length > 0 && items[0].container.incomplete) {
693
providerFilter.add(provider);
694
} else {
695
providerItemsToReuse.set(provider, items);
696
}
697
}
698
699
this.trigger({
700
auto: this._context.triggerOptions.auto,
701
triggerKind: CompletionTriggerKind.TriggerForIncompleteCompletions,
702
retrigger: true,
703
clipboardText: this._completionModel.clipboardText,
704
completionOptions: { providerFilter, providerItemsToReuse }
705
});
706
707
} else {
708
// typed -> moved cursor RIGHT -> update UI
709
const oldLineContext = this._completionModel.lineContext;
710
let isFrozen = false;
711
712
this._completionModel.lineContext = {
713
leadingLineContent: ctx.leadingLineContent,
714
characterCountDelta: ctx.column - this._context.column
715
};
716
717
if (this._completionModel.items.length === 0) {
718
719
const shouldAutoTrigger = LineContext.shouldAutoTrigger(this._editor);
720
if (!this._context) {
721
// shouldAutoTrigger forces tokenization, which can cause pending cursor change events to be emitted, which can cause
722
// suggestions to be cancelled, which causes `this._context` to be undefined
723
this.cancel();
724
return;
725
}
726
727
if (shouldAutoTrigger && this._context.leadingWord.endColumn < ctx.leadingWord.startColumn) {
728
// retrigger when heading into a new word
729
this.trigger({ auto: this._context.triggerOptions.auto, retrigger: true });
730
return;
731
}
732
733
if (!this._context.triggerOptions.auto) {
734
// freeze when IntelliSense was manually requested
735
this._completionModel.lineContext = oldLineContext;
736
isFrozen = this._completionModel.items.length > 0;
737
738
if (isFrozen && ctx.leadingWord.word.length === 0) {
739
// there were results before but now there aren't
740
// and also we are not on a word anymore -> cancel
741
this.cancel();
742
return;
743
}
744
745
} else {
746
// nothing left
747
this.cancel();
748
return;
749
}
750
}
751
752
this._onDidSuggest.fire({
753
completionModel: this._completionModel,
754
triggerOptions: ctx.triggerOptions,
755
isFrozen,
756
});
757
}
758
}
759
}
760
761