Path: blob/main/src/vs/editor/contrib/suggest/browser/suggestModel.ts
4797 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') {417return;418}419}420421if (!canShowQuickSuggest(this._editor, this._contextKeyService, this._configurationService)) {422// do not trigger quick suggestions if inline suggestions are shown423return;424}425426if (!this._languageFeaturesService.completionProvider.has(model)) {427return;428}429430// we made it till here -> trigger now431this.trigger({ auto: true });432433}, this._editor.getOption(EditorOption.quickSuggestionsDelay));434}435436private _refilterCompletionItems(): void {437assertType(this._editor.hasModel());438assertType(this._triggerState !== undefined);439440const model = this._editor.getModel();441const position = this._editor.getPosition();442const ctx = new LineContext(model, position, { ...this._triggerState, refilter: true });443this._onNewContext(ctx);444}445446trigger(options: SuggestTriggerOptions): void {447if (!this._editor.hasModel()) {448return;449}450451const model = this._editor.getModel();452const ctx = new LineContext(model, this._editor.getPosition(), options);453454// Cancel previous requests, change state & update UI455this.cancel(options.retrigger);456this._triggerState = options;457this._onDidTrigger.fire({ auto: options.auto, shy: options.shy ?? false, position: this._editor.getPosition() });458459// Capture context when request was sent460this._context = ctx;461462// Build context for request463let suggestCtx: CompletionContext = { triggerKind: options.triggerKind ?? CompletionTriggerKind.Invoke };464if (options.triggerCharacter) {465suggestCtx = {466triggerKind: CompletionTriggerKind.TriggerCharacter,467triggerCharacter: options.triggerCharacter468};469}470471this._requestToken = new CancellationTokenSource();472473// kind filter and snippet sort rules474const snippetSuggestions = this._editor.getOption(EditorOption.snippetSuggestions);475let snippetSortOrder = SnippetSortOrder.Inline;476switch (snippetSuggestions) {477case 'top':478snippetSortOrder = SnippetSortOrder.Top;479break;480// ↓ that's the default anyways...481// case 'inline':482// snippetSortOrder = SnippetSortOrder.Inline;483// break;484case 'bottom':485snippetSortOrder = SnippetSortOrder.Bottom;486break;487}488489const { itemKind: itemKindFilter, showDeprecated } = SuggestModel.createSuggestFilter(this._editor);490const completionOptions = new CompletionOptions(snippetSortOrder, options.completionOptions?.kindFilter ?? itemKindFilter, options.completionOptions?.providerFilter, options.completionOptions?.providerItemsToReuse, showDeprecated);491const wordDistance = WordDistance.create(this._editorWorkerService, this._editor);492493const completions = provideSuggestionItems(494this._languageFeaturesService.completionProvider,495model,496this._editor.getPosition(),497completionOptions,498suggestCtx,499this._requestToken.token500);501502Promise.all([completions, wordDistance]).then(async ([completions, wordDistance]) => {503504this._requestToken?.dispose();505506if (!this._editor.hasModel()) {507completions.disposable.dispose();508return;509}510511let clipboardText = options?.clipboardText;512if (!clipboardText && completions.needsClipboard) {513clipboardText = await this._clipboardService.readText();514}515516if (this._triggerState === undefined) {517completions.disposable.dispose();518return;519}520521const model = this._editor.getModel();522// const items = completions.items;523524// if (existing) {525// const cmpFn = getSuggestionComparator(snippetSortOrder);526// items = items.concat(existing.items).sort(cmpFn);527// }528529const ctx = new LineContext(model, this._editor.getPosition(), options);530const fuzzySearchOptions = {531...FuzzyScoreOptions.default,532firstMatchCanBeWeak: !this._editor.getOption(EditorOption.suggest).matchOnWordStartOnly533};534this._completionModel = new CompletionModel(completions.items, this._context!.column, {535leadingLineContent: ctx.leadingLineContent,536characterCountDelta: ctx.column - this._context!.column537},538wordDistance,539this._editor.getOption(EditorOption.suggest),540this._editor.getOption(EditorOption.snippetSuggestions),541fuzzySearchOptions,542clipboardText543);544545// store containers so that they can be disposed later546this._completionDisposables.add(completions.disposable);547548this._onNewContext(ctx);549550// finally report telemetry about durations551this._reportDurationsTelemetry(completions.durations);552553// report invalid completions by source554if (!this._envService.isBuilt || this._envService.isExtensionDevelopment) {555for (const item of completions.items) {556if (item.isInvalid) {557this._logService.warn(`[suggest] did IGNORE invalid completion item from ${item.provider._debugDisplayName}`, item.completion);558}559}560}561562}).catch(onUnexpectedError);563}564565/**566* Report durations telemetry with a 1% sampling rate.567* The telemetry is reported only if a random number between 0 and 100 is less than or equal to 1.568*/569private _reportDurationsTelemetry(durations: CompletionDurations): void {570if (Math.random() > 0.0001) { // 0.01%571return;572}573574setTimeout(() => {575type Durations = { data: string };576type DurationsClassification = {577owner: 'jrieken';578comment: 'Completions performance numbers';579data: { comment: 'Durations per source and overall'; classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth' };580};581this._telemetryService.publicLog2<Durations, DurationsClassification>('suggest.durations.json', { data: JSON.stringify(durations) });582this._logService.debug('suggest.durations.json', durations);583});584}585586static createSuggestFilter(editor: ICodeEditor): { itemKind: Set<CompletionItemKind>; showDeprecated: boolean } {587// kind filter and snippet sort rules588const result = new Set<CompletionItemKind>();589590// snippet setting591const snippetSuggestions = editor.getOption(EditorOption.snippetSuggestions);592if (snippetSuggestions === 'none') {593result.add(CompletionItemKind.Snippet);594}595596// type setting597const suggestOptions = editor.getOption(EditorOption.suggest);598if (!suggestOptions.showMethods) { result.add(CompletionItemKind.Method); }599if (!suggestOptions.showFunctions) { result.add(CompletionItemKind.Function); }600if (!suggestOptions.showConstructors) { result.add(CompletionItemKind.Constructor); }601if (!suggestOptions.showFields) { result.add(CompletionItemKind.Field); }602if (!suggestOptions.showVariables) { result.add(CompletionItemKind.Variable); }603if (!suggestOptions.showClasses) { result.add(CompletionItemKind.Class); }604if (!suggestOptions.showStructs) { result.add(CompletionItemKind.Struct); }605if (!suggestOptions.showInterfaces) { result.add(CompletionItemKind.Interface); }606if (!suggestOptions.showModules) { result.add(CompletionItemKind.Module); }607if (!suggestOptions.showProperties) { result.add(CompletionItemKind.Property); }608if (!suggestOptions.showEvents) { result.add(CompletionItemKind.Event); }609if (!suggestOptions.showOperators) { result.add(CompletionItemKind.Operator); }610if (!suggestOptions.showUnits) { result.add(CompletionItemKind.Unit); }611if (!suggestOptions.showValues) { result.add(CompletionItemKind.Value); }612if (!suggestOptions.showConstants) { result.add(CompletionItemKind.Constant); }613if (!suggestOptions.showEnums) { result.add(CompletionItemKind.Enum); }614if (!suggestOptions.showEnumMembers) { result.add(CompletionItemKind.EnumMember); }615if (!suggestOptions.showKeywords) { result.add(CompletionItemKind.Keyword); }616if (!suggestOptions.showWords) { result.add(CompletionItemKind.Text); }617if (!suggestOptions.showColors) { result.add(CompletionItemKind.Color); }618if (!suggestOptions.showFiles) { result.add(CompletionItemKind.File); }619if (!suggestOptions.showReferences) { result.add(CompletionItemKind.Reference); }620if (!suggestOptions.showColors) { result.add(CompletionItemKind.Customcolor); }621if (!suggestOptions.showFolders) { result.add(CompletionItemKind.Folder); }622if (!suggestOptions.showTypeParameters) { result.add(CompletionItemKind.TypeParameter); }623if (!suggestOptions.showSnippets) { result.add(CompletionItemKind.Snippet); }624if (!suggestOptions.showUsers) { result.add(CompletionItemKind.User); }625if (!suggestOptions.showIssues) { result.add(CompletionItemKind.Issue); }626627return { itemKind: result, showDeprecated: suggestOptions.showDeprecated };628}629630private _onNewContext(ctx: LineContext): void {631632if (!this._context) {633// happens when 24x7 IntelliSense is enabled and still in its delay634return;635}636637if (ctx.lineNumber !== this._context.lineNumber) {638// e.g. happens when pressing Enter while IntelliSense is computed639this.cancel();640return;641}642643if (getLeadingWhitespace(ctx.leadingLineContent) !== getLeadingWhitespace(this._context.leadingLineContent)) {644// cancel IntelliSense when line start changes645// happens when the current word gets outdented646this.cancel();647return;648}649650if (ctx.column < this._context.column) {651// typed -> moved cursor LEFT -> retrigger if still on a word652if (ctx.leadingWord.word) {653this.trigger({ auto: this._context.triggerOptions.auto, retrigger: true });654} else {655this.cancel();656}657return;658}659660if (!this._completionModel) {661// happens when IntelliSense is not yet computed662return;663}664665if (ctx.leadingWord.word.length !== 0 && ctx.leadingWord.startColumn > this._context.leadingWord.startColumn) {666// started a new word while IntelliSense shows -> retrigger but reuse all items that we currently have667const shouldAutoTrigger = LineContext.shouldAutoTrigger(this._editor);668if (shouldAutoTrigger && this._context) {669// shouldAutoTrigger forces tokenization, which can cause pending cursor change events to be emitted, which can cause670// suggestions to be cancelled, which causes `this._context` to be undefined671const map = this._completionModel.getItemsByProvider();672this.trigger({673auto: this._context.triggerOptions.auto,674retrigger: true,675clipboardText: this._completionModel.clipboardText,676completionOptions: { providerItemsToReuse: map }677});678}679return;680}681682if (ctx.column > this._context.column && this._completionModel.getIncompleteProvider().size > 0 && ctx.leadingWord.word.length !== 0) {683// typed -> moved cursor RIGHT & incomple model & still on a word -> retrigger684685const providerItemsToReuse = new Map<CompletionItemProvider, CompletionItem[]>();686const providerFilter = new Set<CompletionItemProvider>();687for (const [provider, items] of this._completionModel.getItemsByProvider()) {688if (items.length > 0 && items[0].container.incomplete) {689providerFilter.add(provider);690} else {691providerItemsToReuse.set(provider, items);692}693}694695this.trigger({696auto: this._context.triggerOptions.auto,697triggerKind: CompletionTriggerKind.TriggerForIncompleteCompletions,698retrigger: true,699clipboardText: this._completionModel.clipboardText,700completionOptions: { providerFilter, providerItemsToReuse }701});702703} else {704// typed -> moved cursor RIGHT -> update UI705const oldLineContext = this._completionModel.lineContext;706let isFrozen = false;707708this._completionModel.lineContext = {709leadingLineContent: ctx.leadingLineContent,710characterCountDelta: ctx.column - this._context.column711};712713if (this._completionModel.items.length === 0) {714715const shouldAutoTrigger = LineContext.shouldAutoTrigger(this._editor);716if (!this._context) {717// shouldAutoTrigger forces tokenization, which can cause pending cursor change events to be emitted, which can cause718// suggestions to be cancelled, which causes `this._context` to be undefined719this.cancel();720return;721}722723if (shouldAutoTrigger && this._context.leadingWord.endColumn < ctx.leadingWord.startColumn) {724// retrigger when heading into a new word725this.trigger({ auto: this._context.triggerOptions.auto, retrigger: true });726return;727}728729if (!this._context.triggerOptions.auto) {730// freeze when IntelliSense was manually requested731this._completionModel.lineContext = oldLineContext;732isFrozen = this._completionModel.items.length > 0;733734if (isFrozen && ctx.leadingWord.word.length === 0) {735// there were results before but now there aren't736// and also we are not on a word anymore -> cancel737this.cancel();738return;739}740741} else {742// nothing left743this.cancel();744return;745}746}747748this._onDidSuggest.fire({749completionModel: this._completionModel,750triggerOptions: ctx.triggerOptions,751isFrozen,752});753}754}755}756757758