Path: blob/main/src/vs/editor/contrib/suggest/browser/suggestModel.ts
5263 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import { TimeoutTimer } from '../../../../base/common/async.js';6import { CancellationTokenSource } from '../../../../base/common/cancellation.js';7import { onUnexpectedError } from '../../../../base/common/errors.js';8import { Emitter, Event } from '../../../../base/common/event.js';9import { DisposableStore, dispose, IDisposable } from '../../../../base/common/lifecycle.js';10import { getLeadingWhitespace, isHighSurrogate, isLowSurrogate } from '../../../../base/common/strings.js';11import { ICodeEditor } from '../../../browser/editorBrowser.js';12import { EditorOption } from '../../../common/config/editorOptions.js';13import { CursorChangeReason, ICursorSelectionChangedEvent } from '../../../common/cursorEvents.js';14import { IPosition, Position } from '../../../common/core/position.js';15import { Selection } from '../../../common/core/selection.js';16import { ITextModel } from '../../../common/model.js';17import { CompletionContext, CompletionItemKind, CompletionItemProvider, CompletionTriggerKind } from '../../../common/languages.js';18import { IEditorWorkerService } from '../../../common/services/editorWorker.js';19import { WordDistance } from './wordDistance.js';20import { IClipboardService } from '../../../../platform/clipboard/common/clipboardService.js';21import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js';22import { IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js';23import { ILogService } from '../../../../platform/log/common/log.js';24import { ITelemetryService } from '../../../../platform/telemetry/common/telemetry.js';25import { CompletionModel } from './completionModel.js';26import { CompletionDurations, CompletionItem, CompletionOptions, getSnippetSuggestSupport, provideSuggestionItems, QuickSuggestionsOptions, SnippetSortOrder } from './suggest.js';27import { IWordAtPosition } from '../../../common/core/wordHelper.js';28import { ILanguageFeaturesService } from '../../../common/services/languageFeatures.js';29import { FuzzyScoreOptions } from '../../../../base/common/filters.js';30import { assertType } from '../../../../base/common/types.js';31import { InlineCompletionContextKeys } from '../../inlineCompletions/browser/controller/inlineCompletionContextKeys.js';32import { SnippetController2 } from '../../snippet/browser/snippetController2.js';33import { IEnvironmentService } from '../../../../platform/environment/common/environment.js';3435export interface ICancelEvent {36readonly retrigger: boolean;37}3839export interface ITriggerEvent {40readonly auto: boolean;41readonly shy: boolean;42readonly position: IPosition;43}4445export interface ISuggestEvent {46readonly completionModel: CompletionModel;47readonly isFrozen: boolean;48readonly triggerOptions: SuggestTriggerOptions;49}5051export interface SuggestTriggerOptions {52readonly auto: boolean;53readonly shy?: boolean;54readonly refilter?: boolean;55readonly retrigger?: boolean;56readonly triggerKind?: CompletionTriggerKind;57readonly triggerCharacter?: string;58readonly clipboardText?: string;59completionOptions?: Partial<CompletionOptions>;60}6162export class LineContext {6364static shouldAutoTrigger(editor: ICodeEditor): boolean {65if (!editor.hasModel()) {66return false;67}68const model = editor.getModel();69const pos = editor.getPosition();70model.tokenization.tokenizeIfCheap(pos.lineNumber);7172const word = model.getWordAtPosition(pos);73if (!word) {74return false;75}76if (word.endColumn !== pos.column &&77word.startColumn + 1 !== pos.column /* after typing a single character before a word */) {78return false;79}80if (!isNaN(Number(word.word))) {81return false;82}83return true;84}8586readonly lineNumber: number;87readonly column: number;88readonly leadingLineContent: string;89readonly leadingWord: IWordAtPosition;90readonly triggerOptions: SuggestTriggerOptions;9192constructor(model: ITextModel, position: Position, triggerOptions: SuggestTriggerOptions) {93this.leadingLineContent = model.getLineContent(position.lineNumber).substr(0, position.column - 1);94this.leadingWord = model.getWordUntilPosition(position);95this.lineNumber = position.lineNumber;96this.column = position.column;97this.triggerOptions = triggerOptions;98}99}100101export const enum State {102Idle = 0,103Manual = 1,104Auto = 2105}106107function canShowQuickSuggest(editor: ICodeEditor, contextKeyService: IContextKeyService, configurationService: IConfigurationService): boolean {108if (!Boolean(contextKeyService.getContextKeyValue(InlineCompletionContextKeys.inlineSuggestionVisible.key))) {109// Allow if there is no inline suggestion.110return true;111}112const suppressSuggestions = contextKeyService.getContextKeyValue<boolean | undefined>(InlineCompletionContextKeys.suppressSuggestions.key);113if (suppressSuggestions !== undefined) {114return !suppressSuggestions;115}116return !editor.getOption(EditorOption.inlineSuggest).suppressSuggestions;117}118119function canShowSuggestOnTriggerCharacters(editor: ICodeEditor, contextKeyService: IContextKeyService, configurationService: IConfigurationService): boolean {120if (!Boolean(contextKeyService.getContextKeyValue('inlineSuggestionVisible'))) {121// Allow if there is no inline suggestion.122return true;123}124const suppressSuggestions = contextKeyService.getContextKeyValue<boolean | undefined>(InlineCompletionContextKeys.suppressSuggestions.key);125if (suppressSuggestions !== undefined) {126return !suppressSuggestions;127}128return !editor.getOption(EditorOption.inlineSuggest).suppressSuggestions;129}130131export class SuggestModel implements IDisposable {132133private readonly _toDispose = new DisposableStore();134private readonly _triggerCharacterListener = new DisposableStore();135private readonly _triggerQuickSuggest = new TimeoutTimer();136137private _triggerState: SuggestTriggerOptions | undefined = undefined;138private _requestToken?: CancellationTokenSource;139private _context?: LineContext;140private _currentSelection: Selection;141142private _completionModel: CompletionModel | undefined;143private readonly _completionDisposables = new DisposableStore();144private readonly _onDidCancel = new Emitter<ICancelEvent>();145private readonly _onDidTrigger = new Emitter<ITriggerEvent>();146private readonly _onDidSuggest = new Emitter<ISuggestEvent>();147148readonly onDidCancel: Event<ICancelEvent> = this._onDidCancel.event;149readonly onDidTrigger: Event<ITriggerEvent> = this._onDidTrigger.event;150readonly onDidSuggest: Event<ISuggestEvent> = this._onDidSuggest.event;151152constructor(153private readonly _editor: ICodeEditor,154@IEditorWorkerService private readonly _editorWorkerService: IEditorWorkerService,155@IClipboardService private readonly _clipboardService: IClipboardService,156@ITelemetryService private readonly _telemetryService: ITelemetryService,157@ILogService private readonly _logService: ILogService,158@IContextKeyService private readonly _contextKeyService: IContextKeyService,159@IConfigurationService private readonly _configurationService: IConfigurationService,160@ILanguageFeaturesService private readonly _languageFeaturesService: ILanguageFeaturesService,161@IEnvironmentService private readonly _envService: IEnvironmentService,162) {163this._currentSelection = this._editor.getSelection() || new Selection(1, 1, 1, 1);164165// wire up various listeners166this._toDispose.add(this._editor.onDidChangeModel(() => {167this._updateTriggerCharacters();168this.cancel();169}));170this._toDispose.add(this._editor.onDidChangeModelLanguage(() => {171this._updateTriggerCharacters();172this.cancel();173}));174this._toDispose.add(this._editor.onDidChangeConfiguration(() => {175this._updateTriggerCharacters();176}));177this._toDispose.add(this._languageFeaturesService.completionProvider.onDidChange(() => {178this._updateTriggerCharacters();179this._updateActiveSuggestSession();180}));181182let editorIsComposing = false;183this._toDispose.add(this._editor.onDidCompositionStart(() => {184editorIsComposing = true;185}));186this._toDispose.add(this._editor.onDidCompositionEnd(() => {187editorIsComposing = false;188this._onCompositionEnd();189}));190this._toDispose.add(this._editor.onDidChangeCursorSelection(e => {191// only trigger suggest when the editor isn't composing a character192if (!editorIsComposing) {193this._onCursorChange(e);194}195}));196this._toDispose.add(this._editor.onDidChangeModelContent(() => {197// only filter completions when the editor isn't composing a character198// allow-any-unicode-next-line199// e.g. ¨ + u makes ü but just ¨ cannot be used for filtering200if (!editorIsComposing && this._triggerState !== undefined) {201this._refilterCompletionItems();202}203}));204205this._updateTriggerCharacters();206}207208dispose(): void {209dispose(this._triggerCharacterListener);210dispose([this._onDidCancel, this._onDidSuggest, this._onDidTrigger, this._triggerQuickSuggest]);211this._toDispose.dispose();212this._completionDisposables.dispose();213this.cancel();214}215216private _updateTriggerCharacters(): void {217this._triggerCharacterListener.clear();218219if (this._editor.getOption(EditorOption.readOnly)220|| !this._editor.hasModel()221|| !this._editor.getOption(EditorOption.suggestOnTriggerCharacters)) {222223return;224}225226const supportsByTriggerCharacter = new Map<string, Set<CompletionItemProvider>>();227for (const support of this._languageFeaturesService.completionProvider.all(this._editor.getModel())) {228for (const ch of support.triggerCharacters || []) {229let set = supportsByTriggerCharacter.get(ch);230if (!set) {231set = new Set();232const suggestSupport = getSnippetSuggestSupport();233if (suggestSupport) {234set.add(suggestSupport);235}236supportsByTriggerCharacter.set(ch, set);237}238set.add(support);239}240}241242243const checkTriggerCharacter = (text?: string) => {244245if (!canShowSuggestOnTriggerCharacters(this._editor, this._contextKeyService, this._configurationService)) {246return;247}248249if (LineContext.shouldAutoTrigger(this._editor)) {250// don't trigger by trigger characters when this is a case for quick suggest251return;252}253254if (!text) {255// came here from the compositionEnd-event256const position = this._editor.getPosition()!;257const model = this._editor.getModel()!;258text = model.getLineContent(position.lineNumber).substr(0, position.column - 1);259}260261let lastChar = '';262if (isLowSurrogate(text.charCodeAt(text.length - 1))) {263if (isHighSurrogate(text.charCodeAt(text.length - 2))) {264lastChar = text.substr(text.length - 2);265}266} else {267lastChar = text.charAt(text.length - 1);268}269270const supports = supportsByTriggerCharacter.get(lastChar);271if (supports) {272273// keep existing items that where not computed by the274// supports/providers that want to trigger now275const providerItemsToReuse = new Map<CompletionItemProvider, CompletionItem[]>();276if (this._completionModel) {277for (const [provider, items] of this._completionModel.getItemsByProvider()) {278if (!supports.has(provider)) {279providerItemsToReuse.set(provider, items);280}281}282}283284this.trigger({285auto: true,286triggerKind: CompletionTriggerKind.TriggerCharacter,287triggerCharacter: lastChar,288retrigger: Boolean(this._completionModel),289clipboardText: this._completionModel?.clipboardText,290completionOptions: { providerFilter: supports, providerItemsToReuse }291});292}293};294295this._triggerCharacterListener.add(this._editor.onDidType(checkTriggerCharacter));296this._triggerCharacterListener.add(this._editor.onDidCompositionEnd(() => checkTriggerCharacter()));297}298299// --- trigger/retrigger/cancel suggest300301get state(): State {302if (!this._triggerState) {303return State.Idle;304} else if (!this._triggerState.auto) {305return State.Manual;306} else {307return State.Auto;308}309}310311cancel(retrigger: boolean = false): void {312if (this._triggerState !== undefined) {313this._triggerQuickSuggest.cancel();314this._requestToken?.cancel();315this._requestToken = undefined;316this._triggerState = undefined;317this._completionModel = undefined;318this._context = undefined;319this._onDidCancel.fire({ retrigger });320}321}322323clear() {324this._completionDisposables.clear();325}326327private _updateActiveSuggestSession(): void {328if (this._triggerState !== undefined) {329if (!this._editor.hasModel() || !this._languageFeaturesService.completionProvider.has(this._editor.getModel())) {330this.cancel();331} else {332this.trigger({ auto: this._triggerState.auto, retrigger: true });333}334}335}336337private _onCursorChange(e: ICursorSelectionChangedEvent): void {338339if (!this._editor.hasModel()) {340return;341}342343const prevSelection = this._currentSelection;344this._currentSelection = this._editor.getSelection();345346if (!e.selection.isEmpty()347|| (e.reason !== CursorChangeReason.NotSet && e.reason !== CursorChangeReason.Explicit)348|| (e.source !== 'keyboard' && e.source !== 'deleteLeft')349) {350// Early exit if nothing needs to be done!351// Leave some form of early exit check here if you wish to continue being a cursor position change listener ;)352this.cancel();353return;354}355356357if (this._triggerState === undefined && e.reason === CursorChangeReason.NotSet) {358if (prevSelection.containsRange(this._currentSelection) || prevSelection.getEndPosition().isBeforeOrEqual(this._currentSelection.getPosition())) {359// cursor did move RIGHT due to typing -> trigger quick suggest360this._doTriggerQuickSuggest();361}362363} else if (this._triggerState !== undefined && e.reason === CursorChangeReason.Explicit) {364// suggest is active and something like cursor keys are used to move365// the cursor. this means we can refilter at the new position366this._refilterCompletionItems();367}368}369370private _onCompositionEnd(): void {371// trigger or refilter when composition ends372if (this._triggerState === undefined) {373this._doTriggerQuickSuggest();374} else {375this._refilterCompletionItems();376}377}378379private _doTriggerQuickSuggest(): void {380381if (QuickSuggestionsOptions.isAllOff(this._editor.getOption(EditorOption.quickSuggestions))) {382// not enabled383return;384}385386if (this._editor.getOption(EditorOption.suggest).snippetsPreventQuickSuggestions && SnippetController2.get(this._editor)?.isInSnippet()) {387// no quick suggestion when in snippet mode388return;389}390391this.cancel();392393this._triggerQuickSuggest.cancelAndSet(() => {394if (this._triggerState !== undefined) {395return;396}397if (!LineContext.shouldAutoTrigger(this._editor)) {398return;399}400if (!this._editor.hasModel() || !this._editor.hasWidgetFocus()) {401return;402}403const model = this._editor.getModel();404const pos = this._editor.getPosition();405// validate enabled now406const config = this._editor.getOption(EditorOption.quickSuggestions);407if (QuickSuggestionsOptions.isAllOff(config)) {408return;409}410411if (!QuickSuggestionsOptions.isAllOn(config)) {412// Check the type of the token that triggered this413model.tokenization.tokenizeIfCheap(pos.lineNumber);414const lineTokens = model.tokenization.getLineTokens(pos.lineNumber);415const tokenType = lineTokens.getStandardTokenType(lineTokens.findTokenIndexAtOffset(Math.max(pos.column - 1 - 1, 0)));416if (QuickSuggestionsOptions.valueFor(config, tokenType) !== 'on') {417if (QuickSuggestionsOptions.valueFor(config, tokenType) !== 'offWhenInlineCompletions'418|| (this._languageFeaturesService.inlineCompletionsProvider.has(model) && this._editor.getOption(EditorOption.inlineSuggest).enabled)) {419return;420}421}422}423424if (!canShowQuickSuggest(this._editor, this._contextKeyService, this._configurationService)) {425// do not trigger quick suggestions if inline suggestions are shown426return;427}428429if (!this._languageFeaturesService.completionProvider.has(model)) {430return;431}432433// we made it till here -> trigger now434this.trigger({ auto: true });435436}, this._editor.getOption(EditorOption.quickSuggestionsDelay));437}438439private _refilterCompletionItems(): void {440assertType(this._editor.hasModel());441assertType(this._triggerState !== undefined);442443const model = this._editor.getModel();444const position = this._editor.getPosition();445const ctx = new LineContext(model, position, { ...this._triggerState, refilter: true });446this._onNewContext(ctx);447}448449trigger(options: SuggestTriggerOptions): void {450if (!this._editor.hasModel()) {451return;452}453454const model = this._editor.getModel();455const ctx = new LineContext(model, this._editor.getPosition(), options);456457// Cancel previous requests, change state & update UI458this.cancel(options.retrigger);459this._triggerState = options;460this._onDidTrigger.fire({ auto: options.auto, shy: options.shy ?? false, position: this._editor.getPosition() });461462// Capture context when request was sent463this._context = ctx;464465// Build context for request466let suggestCtx: CompletionContext = { triggerKind: options.triggerKind ?? CompletionTriggerKind.Invoke };467if (options.triggerCharacter) {468suggestCtx = {469triggerKind: CompletionTriggerKind.TriggerCharacter,470triggerCharacter: options.triggerCharacter471};472}473474this._requestToken = new CancellationTokenSource();475476// kind filter and snippet sort rules477const snippetSuggestions = this._editor.getOption(EditorOption.snippetSuggestions);478let snippetSortOrder = SnippetSortOrder.Inline;479switch (snippetSuggestions) {480case 'top':481snippetSortOrder = SnippetSortOrder.Top;482break;483// ↓ that's the default anyways...484// case 'inline':485// snippetSortOrder = SnippetSortOrder.Inline;486// break;487case 'bottom':488snippetSortOrder = SnippetSortOrder.Bottom;489break;490}491492const { itemKind: itemKindFilter, showDeprecated } = SuggestModel.createSuggestFilter(this._editor);493const completionOptions = new CompletionOptions(snippetSortOrder, options.completionOptions?.kindFilter ?? itemKindFilter, options.completionOptions?.providerFilter, options.completionOptions?.providerItemsToReuse, showDeprecated);494const wordDistance = WordDistance.create(this._editorWorkerService, this._editor);495496const completions = provideSuggestionItems(497this._languageFeaturesService.completionProvider,498model,499this._editor.getPosition(),500completionOptions,501suggestCtx,502this._requestToken.token503);504505Promise.all([completions, wordDistance]).then(async ([completions, wordDistance]) => {506507this._requestToken?.dispose();508509if (!this._editor.hasModel()) {510completions.disposable.dispose();511return;512}513514let clipboardText = options?.clipboardText;515if (!clipboardText && completions.needsClipboard) {516clipboardText = await this._clipboardService.readText();517}518519if (this._triggerState === undefined) {520completions.disposable.dispose();521return;522}523524const model = this._editor.getModel();525// const items = completions.items;526527// if (existing) {528// const cmpFn = getSuggestionComparator(snippetSortOrder);529// items = items.concat(existing.items).sort(cmpFn);530// }531532const ctx = new LineContext(model, this._editor.getPosition(), options);533const fuzzySearchOptions = {534...FuzzyScoreOptions.default,535firstMatchCanBeWeak: !this._editor.getOption(EditorOption.suggest).matchOnWordStartOnly536};537this._completionModel = new CompletionModel(completions.items, this._context!.column, {538leadingLineContent: ctx.leadingLineContent,539characterCountDelta: ctx.column - this._context!.column540},541wordDistance,542this._editor.getOption(EditorOption.suggest),543this._editor.getOption(EditorOption.snippetSuggestions),544fuzzySearchOptions,545clipboardText546);547548// store containers so that they can be disposed later549this._completionDisposables.add(completions.disposable);550551this._onNewContext(ctx);552553// finally report telemetry about durations554this._reportDurationsTelemetry(completions.durations);555556// report invalid completions by source557if (!this._envService.isBuilt || this._envService.isExtensionDevelopment) {558for (const item of completions.items) {559if (item.isInvalid) {560this._logService.warn(`[suggest] did IGNORE invalid completion item from ${item.provider._debugDisplayName}`, item.completion);561}562}563}564565}).catch(onUnexpectedError);566}567568/**569* Report durations telemetry with a 1% sampling rate.570* The telemetry is reported only if a random number between 0 and 100 is less than or equal to 1.571*/572private _reportDurationsTelemetry(durations: CompletionDurations): void {573if (Math.random() > 0.0001) { // 0.01%574return;575}576577setTimeout(() => {578type Durations = { data: string };579type DurationsClassification = {580owner: 'jrieken';581comment: 'Completions performance numbers';582data: { comment: 'Durations per source and overall'; classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth' };583};584this._telemetryService.publicLog2<Durations, DurationsClassification>('suggest.durations.json', { data: JSON.stringify(durations) });585this._logService.debug('suggest.durations.json', durations);586});587}588589static createSuggestFilter(editor: ICodeEditor): { itemKind: Set<CompletionItemKind>; showDeprecated: boolean } {590// kind filter and snippet sort rules591const result = new Set<CompletionItemKind>();592593// snippet setting594const snippetSuggestions = editor.getOption(EditorOption.snippetSuggestions);595if (snippetSuggestions === 'none') {596result.add(CompletionItemKind.Snippet);597}598599// type setting600const suggestOptions = editor.getOption(EditorOption.suggest);601if (!suggestOptions.showMethods) { result.add(CompletionItemKind.Method); }602if (!suggestOptions.showFunctions) { result.add(CompletionItemKind.Function); }603if (!suggestOptions.showConstructors) { result.add(CompletionItemKind.Constructor); }604if (!suggestOptions.showFields) { result.add(CompletionItemKind.Field); }605if (!suggestOptions.showVariables) { result.add(CompletionItemKind.Variable); }606if (!suggestOptions.showClasses) { result.add(CompletionItemKind.Class); }607if (!suggestOptions.showStructs) { result.add(CompletionItemKind.Struct); }608if (!suggestOptions.showInterfaces) { result.add(CompletionItemKind.Interface); }609if (!suggestOptions.showModules) { result.add(CompletionItemKind.Module); }610if (!suggestOptions.showProperties) { result.add(CompletionItemKind.Property); }611if (!suggestOptions.showEvents) { result.add(CompletionItemKind.Event); }612if (!suggestOptions.showOperators) { result.add(CompletionItemKind.Operator); }613if (!suggestOptions.showUnits) { result.add(CompletionItemKind.Unit); }614if (!suggestOptions.showValues) { result.add(CompletionItemKind.Value); }615if (!suggestOptions.showConstants) { result.add(CompletionItemKind.Constant); }616if (!suggestOptions.showEnums) { result.add(CompletionItemKind.Enum); }617if (!suggestOptions.showEnumMembers) { result.add(CompletionItemKind.EnumMember); }618if (!suggestOptions.showKeywords) { result.add(CompletionItemKind.Keyword); }619if (!suggestOptions.showWords) { result.add(CompletionItemKind.Text); }620if (!suggestOptions.showColors) { result.add(CompletionItemKind.Color); }621if (!suggestOptions.showFiles) { result.add(CompletionItemKind.File); }622if (!suggestOptions.showReferences) { result.add(CompletionItemKind.Reference); }623if (!suggestOptions.showColors) { result.add(CompletionItemKind.Customcolor); }624if (!suggestOptions.showFolders) { result.add(CompletionItemKind.Folder); }625if (!suggestOptions.showTypeParameters) { result.add(CompletionItemKind.TypeParameter); }626if (!suggestOptions.showSnippets) { result.add(CompletionItemKind.Snippet); }627if (!suggestOptions.showUsers) { result.add(CompletionItemKind.User); }628if (!suggestOptions.showIssues) { result.add(CompletionItemKind.Issue); }629630return { itemKind: result, showDeprecated: suggestOptions.showDeprecated };631}632633private _onNewContext(ctx: LineContext): void {634635if (!this._context) {636// happens when 24x7 IntelliSense is enabled and still in its delay637return;638}639640if (ctx.lineNumber !== this._context.lineNumber) {641// e.g. happens when pressing Enter while IntelliSense is computed642this.cancel();643return;644}645646if (getLeadingWhitespace(ctx.leadingLineContent) !== getLeadingWhitespace(this._context.leadingLineContent)) {647// cancel IntelliSense when line start changes648// happens when the current word gets outdented649this.cancel();650return;651}652653if (ctx.column < this._context.column) {654// typed -> moved cursor LEFT -> retrigger if still on a word655if (ctx.leadingWord.word) {656this.trigger({ auto: this._context.triggerOptions.auto, retrigger: true });657} else {658this.cancel();659}660return;661}662663if (!this._completionModel) {664// happens when IntelliSense is not yet computed665return;666}667668if (ctx.leadingWord.word.length !== 0 && ctx.leadingWord.startColumn > this._context.leadingWord.startColumn) {669// started a new word while IntelliSense shows -> retrigger but reuse all items that we currently have670const shouldAutoTrigger = LineContext.shouldAutoTrigger(this._editor);671if (shouldAutoTrigger && this._context) {672// shouldAutoTrigger forces tokenization, which can cause pending cursor change events to be emitted, which can cause673// suggestions to be cancelled, which causes `this._context` to be undefined674const map = this._completionModel.getItemsByProvider();675this.trigger({676auto: this._context.triggerOptions.auto,677retrigger: true,678clipboardText: this._completionModel.clipboardText,679completionOptions: { providerItemsToReuse: map }680});681}682return;683}684685if (ctx.column > this._context.column && this._completionModel.getIncompleteProvider().size > 0 && ctx.leadingWord.word.length !== 0) {686// typed -> moved cursor RIGHT & incomple model & still on a word -> retrigger687688const providerItemsToReuse = new Map<CompletionItemProvider, CompletionItem[]>();689const providerFilter = new Set<CompletionItemProvider>();690for (const [provider, items] of this._completionModel.getItemsByProvider()) {691if (items.length > 0 && items[0].container.incomplete) {692providerFilter.add(provider);693} else {694providerItemsToReuse.set(provider, items);695}696}697698this.trigger({699auto: this._context.triggerOptions.auto,700triggerKind: CompletionTriggerKind.TriggerForIncompleteCompletions,701retrigger: true,702clipboardText: this._completionModel.clipboardText,703completionOptions: { providerFilter, providerItemsToReuse }704});705706} else {707// typed -> moved cursor RIGHT -> update UI708const oldLineContext = this._completionModel.lineContext;709let isFrozen = false;710711this._completionModel.lineContext = {712leadingLineContent: ctx.leadingLineContent,713characterCountDelta: ctx.column - this._context.column714};715716if (this._completionModel.items.length === 0) {717718const shouldAutoTrigger = LineContext.shouldAutoTrigger(this._editor);719if (!this._context) {720// shouldAutoTrigger forces tokenization, which can cause pending cursor change events to be emitted, which can cause721// suggestions to be cancelled, which causes `this._context` to be undefined722this.cancel();723return;724}725726if (shouldAutoTrigger && this._context.leadingWord.endColumn < ctx.leadingWord.startColumn) {727// retrigger when heading into a new word728this.trigger({ auto: this._context.triggerOptions.auto, retrigger: true });729return;730}731732if (!this._context.triggerOptions.auto) {733// freeze when IntelliSense was manually requested734this._completionModel.lineContext = oldLineContext;735isFrozen = this._completionModel.items.length > 0;736737if (isFrozen && ctx.leadingWord.word.length === 0) {738// there were results before but now there aren't739// and also we are not on a word anymore -> cancel740this.cancel();741return;742}743744} else {745// nothing left746this.cancel();747return;748}749}750751this._onDidSuggest.fire({752completionModel: this._completionModel,753triggerOptions: ctx.triggerOptions,754isFrozen,755});756}757}758}759760761