/*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ import { join } from 'path'; import * as vscode from 'vscode'; import { IAuthenticationService, IExperimentationService } from '../../../lib/node/chatLibMain'; import { ConfigKey, IConfigurationService } from '../../../platform/configuration/common/configurationService'; import { IEnvService } from '../../../platform/env/common/envService'; import { IVSCodeExtensionContext } from '../../../platform/extContext/common/extensionContext'; import { JointCompletionsProviderStrategy } from '../../../platform/inlineEdits/common/dataTypes/jointCompletionsProviderOptions'; import { InlineEditRequestLogContext } from '../../../platform/inlineEdits/common/inlineEditLogContext'; import { ObservableGit } from '../../../platform/inlineEdits/common/observableGit'; import { shortenOpportunityId } from '../../../platform/inlineEdits/common/utils/utils'; import { NesHistoryContextProvider } from '../../../platform/inlineEdits/common/workspaceEditTracker/nesHistoryContextProvider'; import { ILogService } from '../../../platform/log/common/logService'; import * as errors from '../../../util/common/errors'; import { isNotebookCell } from '../../../util/common/notebooks'; import { createTracer, ITracer } from '../../../util/common/tracing'; import { coalesce } from '../../../util/vs/base/common/arrays'; import { assertNever, softAssert } from '../../../util/vs/base/common/assert'; import { raceCancellation, raceTimeout } from '../../../util/vs/base/common/async'; import { CancellationToken, CancellationTokenSource } from '../../../util/vs/base/common/cancellation'; import { Disposable } from '../../../util/vs/base/common/lifecycle'; import { autorun, derived, derivedDisposable, observableFromEvent } from '../../../util/vs/base/common/observable'; import { StopWatch } from '../../../util/vs/base/common/stopwatch'; import { URI } from '../../../util/vs/base/common/uri'; import { StringReplacement } from '../../../util/vs/editor/common/core/edits/stringEdit'; import { Range } from '../../../util/vs/editor/common/core/range'; import { StringText } from '../../../util/vs/editor/common/core/text/abstractText'; import { IInstantiationService } from '../../../util/vs/platform/instantiation/common/instantiation'; import { IExtensionContribution } from '../../common/contributions'; import { registerUnificationCommands } from '../../completions-core/vscode-node/completionsServiceBridges'; import { CopilotInlineCompletionItemProvider } from '../../completions-core/vscode-node/extension/src/inlineCompletion'; import { ICopilotInlineCompletionItemProviderService } from '../../completions/common/copilotInlineCompletionItemProviderService'; import { CompletionsCoreContribution } from '../../completions/vscode-node/completionsCoreContribution'; import { unificationStateObservable } from '../../completions/vscode-node/completionsUnificationContribution'; import { TelemetrySender } from '../node/nextEditProviderTelemetry'; import { InlineEditDebugComponent, reportFeedbackCommandId } from './components/inlineEditDebugComponent'; import { LogContextRecorder } from './components/logContextRecorder'; import { DiagnosticsNextEditProvider } from './features/diagnosticsInlineEditProvider'; import { InlineCompletionProviderImpl, NesCompletionItem, NesCompletionList } from './inlineCompletionProvider'; import { InlineEditModel } from './inlineEditModel'; import { clearCacheCommandId, InlineEditProviderFeature, InlineEditProviderFeatureContribution, learnMoreCommandId, learnMoreLink, reportNotebookNESIssueCommandId } from './inlineEditProviderFeature'; import { InlineEditLogger } from './parts/inlineEditLogger'; import { VSCodeWorkspace } from './parts/vscodeWorkspace'; import { makeSettable } from './utils/observablesUtils'; export class JointCompletionsProviderContribution extends Disposable implements IExtensionContribution { private readonly _inlineEditsProviderId = makeSettable(this._configurationService.getExperimentBasedConfigObservable(ConfigKey.TeamInternal.InlineEditsProviderId, this._expService)); private readonly _hideInternalInterface = this._configurationService.getConfigObservable(ConfigKey.TeamInternal.InlineEditsHideInternalInterface); private readonly _enableDiagnosticsProvider = this._configurationService.getExperimentBasedConfigObservable(ConfigKey.InlineEditsEnableDiagnosticsProvider, this._expService); // FIXME@ulugbekna: re-enable when yieldTo is supported // private readonly _yieldToCopilot = this._configurationService.getExperimentBasedConfigObservable(ConfigKey.TeamInternal.InlineEditsYieldToCopilot, this._expService); private readonly _excludedProviders = this._configurationService.getExperimentBasedConfigObservable(ConfigKey.TeamInternal.InlineEditsExcludedProviders, this._expService).map(v => v ? v.split(',').map(v => v.trim()).filter(v => v !== '') : []); private readonly _copilotToken = observableFromEvent(this, this._authenticationService.onDidAuthenticationChange, () => this._authenticationService.copilotToken); public readonly inlineEditsEnabled = derived(this, (reader) => { const copilotToken = this._copilotToken.read(reader); if (copilotToken === undefined) { return false; } if (copilotToken.isCompletionsQuotaExceeded) { return false; } return true; }); private readonly _internalActionsEnabled = derived(this, (reader) => { return !!this._copilotToken.read(reader)?.isInternal && !this._hideInternalInterface.read(reader); }); public readonly isInlineEditsLogFileEnabledObservable = this._configurationService.getConfigObservable(ConfigKey.TeamInternal.InlineEditsLogContextRecorderEnabled); private readonly _workspace = derivedDisposable(this, _reader => { return this._instantiationService.createInstance(VSCodeWorkspace); }); constructor( @IVSCodeExtensionContext private readonly _vscodeExtensionContext: IVSCodeExtensionContext, @IInstantiationService private readonly _instantiationService: IInstantiationService, @ICopilotInlineCompletionItemProviderService private readonly _copilotInlineCompletionItemProviderService: ICopilotInlineCompletionItemProviderService, @IConfigurationService private readonly _configurationService: IConfigurationService, @IExperimentationService private readonly _expService: IExperimentationService, @IAuthenticationService private readonly _authenticationService: IAuthenticationService, @IEnvService private readonly _envService: IEnvService, ) { super(); const useJointCompletionsProviderObs = _configurationService.getExperimentBasedConfigObservable(ConfigKey.TeamInternal.InlineEditsJointCompletionsProviderEnabled, _expService); this._register(autorun((reader) => { // FX const useJointCompletionsProvider = useJointCompletionsProviderObs.read(reader); if (!useJointCompletionsProvider) { reader.store.add(_instantiationService.createInstance(InlineEditProviderFeatureContribution)); reader.store.add(_instantiationService.createInstance(CompletionsCoreContribution)); return; } const inlineEditFeature = _instantiationService.createInstance(InlineEditProviderFeature); reader.store.add(inlineEditFeature.rolloutFeature()); inlineEditFeature.setContext(); const unificationState = unificationStateObservable(this); reader.store.add(autorun((reader) => { const unificationStateValue = unificationState.read(reader); const excludes = this._excludedProviders.read(reader).slice(); let inlineEditProvider: InlineCompletionProviderImpl | undefined = undefined; if (this.inlineEditsEnabled.read(reader)) { const logger = reader.store.add(this._instantiationService.createInstance(InlineEditLogger)); const statelessProviderId = this._inlineEditsProviderId.read(reader); const workspace = this._workspace.read(reader); const git = reader.store.add(this._instantiationService.createInstance(ObservableGit)); const historyContextProvider = new NesHistoryContextProvider(workspace, git); let diagnosticsProvider: DiagnosticsNextEditProvider | undefined = undefined; if (this._enableDiagnosticsProvider.read(reader)) { diagnosticsProvider = reader.store.add(this._instantiationService.createInstance(DiagnosticsNextEditProvider, workspace, git)); } const model = reader.store.add(this._instantiationService.createInstance(InlineEditModel, statelessProviderId, workspace, historyContextProvider, diagnosticsProvider)); const recordingDirPath = join(this._vscodeExtensionContext.globalStorageUri.fsPath, 'logContextRecordings'); const isInlineEditLogFileEnabled = this.isInlineEditsLogFileEnabledObservable.read(reader); let logContextRecorder: LogContextRecorder | undefined; if (isInlineEditLogFileEnabled) { logContextRecorder = reader.store.add(this._instantiationService.createInstance(LogContextRecorder, recordingDirPath, logger)); } else { void LogContextRecorder.cleanupOldRecordings(recordingDirPath); } const inlineEditDebugComponent = reader.store.add(new InlineEditDebugComponent(this._internalActionsEnabled, this.inlineEditsEnabled, model.debugRecorder, this._inlineEditsProviderId)); const telemetrySender = reader.store.add(this._instantiationService.createInstance(TelemetrySender)); inlineEditProvider = this._instantiationService.createInstance(InlineCompletionProviderImpl, model, logger, logContextRecorder, inlineEditDebugComponent, telemetrySender); reader.store.add(vscode.commands.registerCommand(learnMoreCommandId, () => { this._envService.openExternal(URI.parse(learnMoreLink)); })); reader.store.add(vscode.commands.registerCommand(clearCacheCommandId, () => { model.nextEditProvider.clearCache(); })); reader.store.add(vscode.commands.registerCommand(reportNotebookNESIssueCommandId, () => { const activeNotebook = vscode.window.activeNotebookEditor; const document = vscode.window.activeTextEditor?.document; if (!activeNotebook || !document || !isNotebookCell(document.uri)) { return; } const doc = model.workspace.getDocumentByTextDocument(document); const selection = activeNotebook.selection; if (!selection || !doc) { return; } const logContext = new InlineEditRequestLogContext(doc.id.uri, document.version, undefined); logContext.recordingBookmark = model.debugRecorder.createBookmark(); void vscode.commands.executeCommand(reportFeedbackCommandId, { logContext }); })); } let completionsProvider: CopilotInlineCompletionItemProvider | undefined; { const configEnabled = this._configurationService.getExperimentBasedConfigObservable(ConfigKey.TeamInternal.InlineEditsEnableGhCompletionsProvider, this._expService).read(reader); const extensionUnification = unificationStateValue?.extensionUnification ?? false; // @ulugbekna: note that we don't want it if modelUnification is on const modelUnification = unificationStateValue?.modelUnification ?? false; if (!modelUnification || unificationStateValue?.codeUnification || extensionUnification || configEnabled || this._copilotToken.read(reader)?.isNoAuthUser) { completionsProvider = this._copilotInlineCompletionItemProviderService.getOrCreateProvider() as CopilotInlineCompletionItemProvider; } void vscode.commands.executeCommand('setContext', 'github.copilot.extensionUnification.activated', extensionUnification); if (extensionUnification && completionsProvider) { const completionsInstaService = this._copilotInlineCompletionItemProviderService.getOrCreateInstantiationService(); reader.store.add(completionsInstaService.invokeFunction(registerUnificationCommands)); } } const singularProvider = reader.store.add(this._instantiationService.createInstance(JointCompletionsProvider, completionsProvider, inlineEditProvider)); if (unificationStateValue?.modelUnification) { if (!excludes.includes('github.copilot')) { excludes.push('github.copilot'); } } reader.store.add(vscode.languages.registerInlineCompletionItemProvider( '*', singularProvider, { displayName: inlineEditProvider?.displayName, debounceDelayMs: 0, // set 0 debounce to ensure consistent delays/timings groupId: 'nes', excludes, }) ); })); })); } } type SingularCompletionItem = | ({ source: 'completions' } & vscode.InlineCompletionItem) | ({ source: 'inlineEdits' } & NesCompletionItem) ; type SingularCompletionList = | ({ source: 'completions' } & vscode.InlineCompletionList) | ({ source: 'inlineEdits' } & NesCompletionList) ; function toCompletionsList(list: vscode.InlineCompletionList): SingularCompletionList { return { ...list, items: list.items.map(item => ({ ...item, source: 'completions' })), source: 'completions' }; } function toInlineEditsList(list: NesCompletionList): SingularCompletionList { return { ...list, items: list.items.map(item => ({ ...item, source: 'inlineEdits' })), source: 'inlineEdits' }; } type LastNesSuggestion = { docUri: vscode.Uri; docVersionId: number; docWithNesEditApplied: StringText; }; class JointCompletionsProvider extends Disposable implements vscode.InlineCompletionItemProvider { public onDidChange?: vscode.Event | undefined; constructor( private readonly _completionsProvider: CopilotInlineCompletionItemProvider | undefined, private readonly _inlineEditProvider: InlineCompletionProviderImpl | undefined, @IConfigurationService private readonly _configService: IConfigurationService, @IExperimentationService private readonly _expService: IExperimentationService, @ILogService private readonly _logService: ILogService, ) { super(); this.onDidChange = _inlineEditProvider?.onDidChange; softAssert( _completionsProvider?.onDidChange === undefined, 'CompletionsProvider does not implement onDidChange' ); } public async provideInlineCompletionItems(document: vscode.TextDocument, position: vscode.Position, context: vscode.InlineCompletionContext, token: vscode.CancellationToken): Promise { const tracer = createTracer(['JointCompletionsProvider', shortenOpportunityId(context.requestUuid), 'provideInlineCompletionItems'], (msg) => this._logService.trace(msg)); const strategy = this._configService.getExperimentBasedConfig(ConfigKey.TeamInternal.InlineEditsJointCompletionsProviderStrategy, this._expService); switch (strategy) { case JointCompletionsProviderStrategy.Regular: return this.provideInlineCompletionItemsRegular(document, position, context, token, tracer); default: assertNever(strategy); } } private lastNesSuggestion: null | LastNesSuggestion = null; private provideInlineCompletionItemsInvocationCount = 0; private async provideInlineCompletionItemsRegular(document: vscode.TextDocument, position: vscode.Position, context: vscode.InlineCompletionContext, token: vscode.CancellationToken, tracer: ITracer): Promise { const invocationId = ++this.provideInlineCompletionItemsInvocationCount; const completionsCts = new CancellationTokenSource(token); const nesCts = new CancellationTokenSource(token); let saveLastNesSuggestion: null | LastNesSuggestion = null; try { const docSnapshot = new StringText(document.getText()); const docVersionId = document.version; if (this.lastNesSuggestion && this.lastNesSuggestion.docUri.toString() !== document.uri.toString()) { tracer.trace('last NES suggestion is not for the current document, ignoring'); this.lastNesSuggestion = null; } const list = await this._provideInlineCompletionItemsRegular({ document, docSnapshot }, position, this.lastNesSuggestion, context, tracer, { coreToken: token, completionsCts, nesCts }); if (token.isCancellationRequested) { return list; } if (!list || list.source !== 'inlineEdits' || list.items.length === 0) { return list; } const firstItem = list.items[0]; if (!firstItem.range || typeof firstItem.insertText !== 'string') { return list; } if (firstItem.uri && firstItem.uri.toString() !== document.uri.toString()) { tracer.trace(`The NES suggestion is for a different document (${firstItem.uri.toString()} vs ${document.uri.toString()}), not saving as last NES suggestion`); return list; } const applied = applyTextEdit(docSnapshot, firstItem.range, firstItem.insertText); saveLastNesSuggestion = { docUri: document.uri, docVersionId, docWithNesEditApplied: new StringText(applied), }; return list; } finally { // Only save the last NES suggestion if this is the latest invocation if (invocationId === this.provideInlineCompletionItemsInvocationCount) { this.lastNesSuggestion = saveLastNesSuggestion; if (this.lastNesSuggestion) { tracer.trace(`Set the last NES suggestion for document ${this.lastNesSuggestion.docUri.toString()}`); } else { tracer.trace(`Cleared the last NES suggestion`); } } else { tracer.trace(`Not setting the last NES suggestion because a newer invocation exists`); } completionsCts.dispose(); nesCts.dispose(); } } private async _provideInlineCompletionItemsRegular( { document, docSnapshot }: { document: vscode.TextDocument; docSnapshot: StringText }, position: vscode.Position, lastNesSuggestion: null | LastNesSuggestion, context: vscode.InlineCompletionContext, tracer: ITracer, tokens: { coreToken: CancellationToken; completionsCts: CancellationTokenSource; nesCts: CancellationTokenSource }, ): Promise { const sw = new StopWatch(); if (this._completionsProvider === undefined && this._inlineEditProvider === undefined) { tracer.returns('neither completions nor NES provider available'); return undefined; } tracer.trace('requesting completions and/or NES'); if (!lastNesSuggestion) { // prefer completions unless there are none tracer.trace(`no last NES suggestion to consider`); const completionsP = this._invokeCompletionsProvider(tracer, document, position, context, tokens, sw); const nesP = this._invokeNESProvider(tracer, document, position, context, tokens, sw); return this._returnCompletionsOrOtherwiseNES(completionsP, nesP, sw, tracer, tokens); } tracer.trace(`last NES suggestion is for the current document, checking if it agrees with the current suggestion`); const nesP = this._invokeNESProvider(tracer, document, position, context, tokens, sw); if (!nesP) { tracer.trace(`no NES provider`); const completionsP = this._invokeCompletionsProvider(tracer, document, position, context, tokens, sw); return this._returnCompletionsOrOtherwiseNES(completionsP, nesP, sw, tracer, tokens); } const NES_CACHE_WAIT_MS = 10; // scoping the variables { tracer.trace(`giving the NES provider ${NES_CACHE_WAIT_MS}ms to return what it has in its cache`); const fastNesResult = await raceCancellation( raceTimeout( nesP, NES_CACHE_WAIT_MS ), tokens.coreToken ); // got nes quickly if (fastNesResult && this.doesNesSuggestionAgree(docSnapshot, lastNesSuggestion.docWithNesEditApplied, (fastNesResult.items as NesCompletionItem[]).at(0))) { tracer.trace('last NES suggestion agrees with the current suggestion, using NES'); const list: SingularCompletionList = toInlineEditsList(fastNesResult); tracer.returns(`returning NES result in ${sw.elapsed()}ms`); return list; } if (tokens.coreToken.isCancellationRequested) { tracer.trace(`suggestions request was cancelled`); void setEndOfLifeReason(this._completionsProvider, undefined, { kind: vscode.InlineCompletionsDisposeReasonKind.TokenCancellation }); void setEndOfLifeReason(this._inlineEditProvider, nesP, { kind: vscode.InlineCompletionsDisposeReasonKind.TokenCancellation }); tokens.completionsCts.cancel(); tokens.nesCts.cancel(); return undefined; } } tracer.trace(`the NES provider did not return in ${NES_CACHE_WAIT_MS}ms so we are triggering the completions provider too`); const completionsP = this._invokeCompletionsProvider(tracer, document, position, context, tokens, sw); const suggestionsList = await raceCancellation( Promise.race(coalesce([ completionsP?.then(res => ({ type: 'completions' as const, res })), nesP?.then(res => ({ type: 'nes' as const, res })), ])), tokens.coreToken ); // got cancelled if (suggestionsList === undefined) { tracer.trace(`suggestions request was cancelled`); void setEndOfLifeReason(this._completionsProvider, completionsP, { kind: vscode.InlineCompletionsDisposeReasonKind.TokenCancellation }); void setEndOfLifeReason(this._inlineEditProvider, nesP, { kind: vscode.InlineCompletionsDisposeReasonKind.TokenCancellation }); tokens.completionsCts.cancel(); tokens.nesCts.cancel(); return undefined; } // got NES first if (suggestionsList.type === 'nes' && suggestionsList.res && this.doesNesSuggestionAgree(docSnapshot, lastNesSuggestion.docWithNesEditApplied, (suggestionsList.res.items as NesCompletionItem[]).at(0))) { tracer.trace('last NES suggestion agrees with the current suggestion, using NES'); return this._returnNES(suggestionsList.res, { kind: vscode.InlineCompletionsDisposeReasonKind.NotTaken }, completionsP, sw, tracer, tokens); } tracer.trace('falling back to the default because completions came first or NES disagreed'); return this._returnCompletionsOrOtherwiseNES(completionsP, nesP, sw, tracer, tokens); } private _invokeNESProvider(tracer: ITracer, document: vscode.TextDocument, position: vscode.Position, context: vscode.InlineCompletionContext, tokens: { coreToken: CancellationToken; completionsCts: CancellationTokenSource; nesCts: CancellationTokenSource }, sw: StopWatch) { let nesP: Promise | undefined; if (this._inlineEditProvider) { tracer.trace(`- requesting NES provideInlineCompletionItems`); nesP = this._inlineEditProvider.provideInlineCompletionItems(document, position, context, tokens.nesCts.token); nesP.then((nesR) => { tracer.trace(`got NES response in ${sw.elapsed()}ms -- ${nesR === undefined ? 'undefined' : `with ${nesR.items.length} items`}`); }).catch((e) => { tracer.trace(`NES provider errored after ${sw.elapsed()}ms -- ${errors.toString(errors.fromUnknown(e))}`); }); } else { tracer.trace(`- no NES provider`); nesP = undefined; } return nesP; } private _invokeCompletionsProvider(tracer: ITracer, document: vscode.TextDocument, position: vscode.Position, context: vscode.InlineCompletionContext, tokens: { coreToken: CancellationToken; completionsCts: CancellationTokenSource; nesCts: CancellationTokenSource }, sw: StopWatch) { let completionsP: Promise | undefined; if (this._completionsProvider) { tracer.trace(`- requesting completions provideInlineCompletionItems`); completionsP = this._completionsProvider.provideInlineCompletionItems(document, position, context, tokens.completionsCts.token); completionsP.then((completionsR) => { tracer.trace(`got completions response in ${sw.elapsed()}ms -- ${completionsR === undefined ? 'undefined' : `with ${completionsR.items.length} items`}`); }).catch((e) => { tracer.trace(`completions provider errored after ${sw.elapsed()}ms -- ${errors.toString(errors.fromUnknown(e))}`); }); } else { tracer.trace(`- no completions provider`); completionsP = undefined; } return completionsP; } private async _returnCompletionsOrOtherwiseNES( completionsP: Promise | undefined, nesP: Promise | undefined, sw: StopWatch, tracer: ITracer, tokens: { coreToken: CancellationToken; completionsCts: CancellationTokenSource; nesCts: CancellationTokenSource }, ) { tracer.trace(`waiting for completions and/or NES responses`); const completionsR = completionsP ? await completionsP : undefined; tracer.trace(`completions response received`); if (completionsR && completionsR.items.length > 0) { tracer.trace(`using completions response, cancelling NES provider`); return this._returnCompletions(completionsR, { kind: vscode.InlineCompletionsDisposeReasonKind.LostRace }, nesP, sw, tracer, tokens); } const nesR = nesP ? await nesP : undefined; tracer.trace(`NES response received`); if (nesR && nesR.items.length > 0) { tracer.trace(`using NES response`); return this._returnNES(nesR, { kind: vscode.InlineCompletionsDisposeReasonKind.NotTaken }, completionsP, sw, tracer, tokens); } // return empty completions return this._returnCompletions(completionsR ?? { items: [] }, { kind: vscode.InlineCompletionsDisposeReasonKind.NotTaken }, nesP, sw, tracer, tokens); } private _returnCompletions( completionsR: vscode.InlineCompletionList, nesDisposeReason: vscode.InlineCompletionsDisposeReason, nesP: Promise | undefined, sw: StopWatch, tracer: ITracer, tokens: { coreToken: CancellationToken; completionsCts: CancellationTokenSource; nesCts: CancellationTokenSource }, ) { void setEndOfLifeReason(this._inlineEditProvider, nesP, nesDisposeReason); tokens.nesCts.cancel(); // cancel NES request if still pending const list: SingularCompletionList = toCompletionsList(completionsR); tracer.returns(`use completions response in ${sw.elapsed()}ms`); return list; } private _returnNES( nesR: NesCompletionList, completionsDisposeReason: vscode.InlineCompletionsDisposeReason, completionsP: Promise | undefined, sw: StopWatch, tracer: ITracer, tokens: { coreToken: CancellationToken; completionsCts: CancellationTokenSource; nesCts: CancellationTokenSource }, ) { void setEndOfLifeReason(this._completionsProvider, completionsP, completionsDisposeReason); tokens.completionsCts.cancel(); // cancel completions request if still pending const list: SingularCompletionList = toInlineEditsList(nesR); tracer.returns(`returning NES result in ${sw.elapsed()}ms`); return list; } private doesNesSuggestionAgree(doc: StringText, docWithNesEditApplied: StringText, nesEdit: NesCompletionItem | undefined): boolean { if (nesEdit === undefined || nesEdit.range === undefined || typeof nesEdit.insertText !== 'string') { return false; } const applied = applyTextEdit(doc, nesEdit.range, nesEdit.insertText); return applied === docWithNesEditApplied.getValue(); } public handleDidShowCompletionItem?(completionItem: SingularCompletionItem, updatedInsertText: string): void { switch (completionItem.source) { case 'completions': this._completionsProvider?.handleDidShowCompletionItem?.(completionItem, updatedInsertText); break; case 'inlineEdits': this._inlineEditProvider?.handleDidShowCompletionItem?.(completionItem, updatedInsertText); break; default: assertNever(completionItem); } } public handleDidPartiallyAcceptCompletionItem?(completionItem: SingularCompletionItem, acceptedLength: number & vscode.PartialAcceptInfo): void { switch (completionItem.source) { case 'completions': this._completionsProvider?.handleDidPartiallyAcceptCompletionItem?.(completionItem, acceptedLength); break; case 'inlineEdits': softAssert(this._inlineEditProvider?.handleDidPartiallyAcceptCompletionItem === undefined, 'InlineEditProvider does not implement handleDidPartiallyAcceptCompletionItem'); break; default: assertNever(completionItem); } } public handleEndOfLifetime?(completionItem: SingularCompletionItem, reason: vscode.InlineCompletionEndOfLifeReason): void { switch (completionItem.source) { case 'completions': this._completionsProvider?.handleEndOfLifetime?.(completionItem, reason); break; case 'inlineEdits': this._inlineEditProvider?.handleEndOfLifetime?.(completionItem, reason); break; default: assertNever(completionItem); } } public handleListEndOfLifetime?(list: SingularCompletionList, reason: vscode.InlineCompletionsDisposeReason): void { switch (list.source) { case 'completions': softAssert(this._completionsProvider?.handleListEndOfLifetime === undefined, 'CompletionsProvider does not implement handleListEndOfLifetime'); break; case 'inlineEdits': this._inlineEditProvider?.handleListEndOfLifetime?.(list, reason); break; default: assertNever(list); } } // neither provider implements this deprecated method public handleDidRejectCompletionItem = undefined; } function applyTextEdit(doc: StringText, range: vscode.Range, insertText: string): string { const rangeOneBased = new Range(range.start.line + 1, range.start.character + 1, range.end.line + 1, range.end.character + 1); const offsetRange = doc.getTransformer().getOffsetRange(rangeOneBased); const edit = new StringReplacement(offsetRange, insertText); const bigEdit = edit.toEdit(); return bigEdit.apply(doc.getValue()); } async function setEndOfLifeReason(provider: vscode.InlineCompletionItemProvider | undefined, promise: Promise | undefined, reason: vscode.InlineCompletionsDisposeReason) { if (promise === undefined) { return; } const result = await promise; if (result === undefined) { return; } for (const item of result.items) { provider?.handleEndOfLifetime?.(item, { kind: vscode.InlineCompletionEndOfLifeReasonKind.Ignored, userTypingDisagreed: false }); } provider?.handleListEndOfLifetime?.(result, reason); }