Path: blob/main/src/vs/editor/contrib/inlineCompletions/browser/model/provideInlineCompletions.ts
5346 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 { assertNever } from '../../../../../base/common/assert.js';6import { AsyncIterableProducer } from '../../../../../base/common/async.js';7import { CancellationToken, CancellationTokenSource } from '../../../../../base/common/cancellation.js';8import { BugIndicatingError, onUnexpectedExternalError } from '../../../../../base/common/errors.js';9import { Disposable, IDisposable } from '../../../../../base/common/lifecycle.js';10import { prefixedUuid } from '../../../../../base/common/uuid.js';11import { ICommandService } from '../../../../../platform/commands/common/commands.js';12import { ISingleEditOperation } from '../../../../common/core/editOperation.js';13import { StringReplacement } from '../../../../common/core/edits/stringEdit.js';14import { OffsetRange } from '../../../../common/core/ranges/offsetRange.js';15import { Position } from '../../../../common/core/position.js';16import { Range } from '../../../../common/core/range.js';17import { TextReplacement } from '../../../../common/core/edits/textEdit.js';18import { InlineCompletionEndOfLifeReason, InlineCompletionEndOfLifeReasonKind, InlineCompletion, InlineCompletionContext, InlineCompletions, InlineCompletionsProvider, PartialAcceptInfo, InlineCompletionsDisposeReason, LifetimeSummary, ProviderId, IInlineCompletionHint, InlineCompletionTriggerKind } from '../../../../common/languages.js';19import { ILanguageConfigurationService } from '../../../../common/languages/languageConfigurationRegistry.js';20import { ITextModel } from '../../../../common/model.js';21import { fixBracketsInLine } from '../../../../common/model/bracketPairsTextModelPart/fixBrackets.js';22import { SnippetParser, Text } from '../../../snippet/browser/snippetParser.js';23import { ErrorResult, getReadonlyEmptyArray } from '../utils.js';24import { groupByMap } from '../../../../../base/common/collections.js';25import { DirectedGraph } from './graph.js';26import { CachedFunction } from '../../../../../base/common/cache.js';27import { InlineCompletionViewData, InlineCompletionViewKind } from '../view/inlineEdits/inlineEditsViewInterface.js';28import { isDefined } from '../../../../../base/common/types.js';29import { inlineCompletionIsVisible } from './inlineCompletionIsVisible.js';30import { EditDeltaInfo } from '../../../../common/textModelEditSource.js';31import { URI } from '../../../../../base/common/uri.js';32import { InlineSuggestionEditKind } from './editKind.js';33import { InlineSuggestAlternativeAction } from './InlineSuggestAlternativeAction.js';3435export type InlineCompletionContextWithoutUuid = Omit<InlineCompletionContext, 'requestUuid'>;3637export function provideInlineCompletions(38providers: InlineCompletionsProvider[],39position: Position,40model: ITextModel,41context: InlineCompletionContextWithoutUuid,42requestInfo: InlineSuggestRequestInfo,43languageConfigurationService?: ILanguageConfigurationService,44): IInlineCompletionProviderResult {45const requestUuid = prefixedUuid('icr');4647const cancellationTokenSource = new CancellationTokenSource();48let cancelReason: InlineCompletionsDisposeReason | undefined = undefined;4950const contextWithUuid: InlineCompletionContext = { ...context, requestUuid: requestUuid };5152const defaultReplaceRange = getDefaultRange(position, model);5354const providersByGroupId = groupByMap(providers, p => p.groupId);55const yieldsToGraph = DirectedGraph.from(providers, p => {56return p.yieldsToGroupIds?.flatMap(groupId => providersByGroupId.get(groupId) ?? []) ?? [];57});58const { foundCycles } = yieldsToGraph.removeCycles();59if (foundCycles.length > 0) {60onUnexpectedExternalError(new Error(`Inline completions: cyclic yield-to dependency detected.`61+ ` Path: ${foundCycles.map(s => s.toString ? s.toString() : ('' + s)).join(' -> ')}`));62}6364let runningCount = 0;6566const queryProvider = new CachedFunction(async (provider: InlineCompletionsProvider<InlineCompletions>): Promise<InlineSuggestionList | undefined> => {67try {68runningCount++;69if (cancellationTokenSource.token.isCancellationRequested) {70return undefined;71}7273const yieldsTo = yieldsToGraph.getOutgoing(provider);74for (const p of yieldsTo) {75// We know there is no cycle, so no recursion here76const result = await queryProvider.get(p);77if (result) {78for (const item of result.inlineSuggestions.items) {79if (item.isInlineEdit || typeof item.insertText !== 'string' && item.insertText !== undefined) {80return undefined;81}82if (item.insertText !== undefined) {83const t = new TextReplacement(Range.lift(item.range) ?? defaultReplaceRange, item.insertText);84if (inlineCompletionIsVisible(t, undefined, model, position)) {85return undefined;86}87}8889// else: inline completion is not visible, so lets not block90}91}92}9394let result: InlineCompletions | null | undefined;95const providerStartTime = Date.now();96try {97result = await provider.provideInlineCompletions(model, position, contextWithUuid, cancellationTokenSource.token);98} catch (e) {99onUnexpectedExternalError(e);100return undefined;101}102const providerEndTime = Date.now();103104if (!result) {105return undefined;106}107108const data: InlineSuggestData[] = [];109const list = new InlineSuggestionList(result, data, provider);110list.addRef();111runWhenCancelled(cancellationTokenSource.token, () => {112return list.removeRef(cancelReason);113});114if (cancellationTokenSource.token.isCancellationRequested) {115return undefined; // The list is disposed now, so we cannot return the items!116}117118for (const item of result.items) {119const r = toInlineSuggestData(item, list, defaultReplaceRange, model, languageConfigurationService, contextWithUuid, requestInfo, { startTime: providerStartTime, endTime: providerEndTime });120if (ErrorResult.is(r)) {121r.logError();122continue;123}124data.push(r);125}126127return list;128} finally {129runningCount--;130}131});132133const inlineCompletionLists = AsyncIterableProducer.fromPromisesResolveOrder(providers.map(p => queryProvider.get(p))).filter(isDefined);134135return {136contextWithUuid,137get didAllProvidersReturn() { return runningCount === 0; },138lists: inlineCompletionLists,139cancelAndDispose: reason => {140if (cancelReason !== undefined) {141return;142}143cancelReason = reason;144cancellationTokenSource.dispose(true);145}146};147}148149/** If the token is eventually cancelled, this will not leak either. */150export function runWhenCancelled(token: CancellationToken, callback: () => void): IDisposable {151if (token.isCancellationRequested) {152callback();153return Disposable.None;154} else {155const listener = token.onCancellationRequested(() => {156listener.dispose();157callback();158});159return { dispose: () => listener.dispose() };160}161}162163export interface IInlineCompletionProviderResult {164get didAllProvidersReturn(): boolean;165166contextWithUuid: InlineCompletionContext;167168cancelAndDispose(reason: InlineCompletionsDisposeReason): void;169170lists: AsyncIterableProducer<InlineSuggestionList>;171}172173function toInlineSuggestData(174inlineCompletion: InlineCompletion,175source: InlineSuggestionList,176defaultReplaceRange: Range,177textModel: ITextModel,178languageConfigurationService: ILanguageConfigurationService | undefined,179context: InlineCompletionContext,180requestInfo: InlineSuggestRequestInfo,181providerRequestInfo: InlineSuggestProviderRequestInfo,182): InlineSuggestData | ErrorResult {183184let action: IInlineSuggestDataAction | undefined;185const uri = inlineCompletion.uri ? URI.revive(inlineCompletion.uri) : undefined;186187if (inlineCompletion.jumpToPosition !== undefined) {188action = {189kind: 'jumpTo',190position: Position.lift(inlineCompletion.jumpToPosition),191uri,192};193} else if (inlineCompletion.insertText !== undefined) {194let insertText: string;195let snippetInfo: SnippetInfo | undefined;196let range = inlineCompletion.range ? Range.lift(inlineCompletion.range) : defaultReplaceRange;197198if (typeof inlineCompletion.insertText === 'string') {199insertText = inlineCompletion.insertText;200201if (languageConfigurationService && inlineCompletion.completeBracketPairs) {202insertText = closeBrackets(203insertText,204range.getStartPosition(),205textModel,206languageConfigurationService207);208209// Modify range depending on if brackets are added or removed210const diff = insertText.length - inlineCompletion.insertText.length;211if (diff !== 0) {212range = new Range(range.startLineNumber, range.startColumn, range.endLineNumber, range.endColumn + diff);213}214}215216snippetInfo = undefined;217} else if ('snippet' in inlineCompletion.insertText) {218const preBracketCompletionLength = inlineCompletion.insertText.snippet.length;219220if (languageConfigurationService && inlineCompletion.completeBracketPairs) {221inlineCompletion.insertText.snippet = closeBrackets(222inlineCompletion.insertText.snippet,223range.getStartPosition(),224textModel,225languageConfigurationService226);227228// Modify range depending on if brackets are added or removed229const diff = inlineCompletion.insertText.snippet.length - preBracketCompletionLength;230if (diff !== 0) {231range = new Range(range.startLineNumber, range.startColumn, range.endLineNumber, range.endColumn + diff);232}233}234235const snippet = new SnippetParser().parse(inlineCompletion.insertText.snippet);236237if (snippet.children.length === 1 && snippet.children[0] instanceof Text) {238insertText = snippet.children[0].value;239snippetInfo = undefined;240} else {241insertText = snippet.toString();242snippetInfo = {243snippet: inlineCompletion.insertText.snippet,244range: range245};246}247} else {248assertNever(inlineCompletion.insertText);249}250action = {251kind: 'edit',252range,253insertText,254snippetInfo,255uri,256alternativeAction: undefined,257};258} else {259action = undefined;260if (!inlineCompletion.hint) {261return ErrorResult.message('Inline completion has no insertText, jumpToPosition nor hint.');262}263}264265return new InlineSuggestData(266action,267inlineCompletion.hint,268inlineCompletion.additionalTextEdits || getReadonlyEmptyArray(),269inlineCompletion,270source,271context,272inlineCompletion.isInlineEdit ?? false,273inlineCompletion.supportsRename ?? false,274requestInfo,275providerRequestInfo,276inlineCompletion.correlationId,277);278}279280export type InlineSuggestSku = { type: string; plan: string };281282export type InlineSuggestRequestInfo = {283startTime: number;284editorType: InlineCompletionEditorType;285languageId: string;286reason: string;287typingInterval: number;288typingIntervalCharacterCount: number;289availableProviders: ProviderId[];290sku: InlineSuggestSku | undefined;291};292293export type InlineSuggestProviderRequestInfo = {294startTime: number;295endTime: number;296};297298export type PartialAcceptance = {299characters: number;300count: number;301ratio: number;302};303304export type RenameInfo = {305createdRename: boolean;306duration: number;307timedOut?: boolean;308droppedOtherEdits?: number;309droppedRenameEdits?: number;310};311312export type InlineSuggestViewData = {313editorType: InlineCompletionEditorType;314renderData?: InlineCompletionViewData;315viewKind?: InlineCompletionViewKind;316};317318export type IInlineSuggestDataAction = IInlineSuggestDataActionEdit | IInlineSuggestDataActionJumpTo;319320export interface IInlineSuggestDataActionEdit {321kind: 'edit';322range: Range;323insertText: string;324snippetInfo: SnippetInfo | undefined;325uri: URI | undefined;326alternativeAction: InlineSuggestAlternativeAction | undefined;327}328329export interface IInlineSuggestDataActionJumpTo {330kind: 'jumpTo';331position: Position;332uri: URI | undefined;333}334335export class InlineSuggestData {336public static createForTest(action: IInlineSuggestDataAction | undefined, targetUri: URI): InlineSuggestData {337const mockInlineCompletion: InlineCompletion = {338insertText: action?.kind === 'edit' ? action.insertText : '',339range: action?.kind === 'edit' ? action.range : undefined,340isInlineEdit: true,341};342const mockProvider: InlineCompletionsProvider = {343provideInlineCompletions: () => ({ items: [] }),344disposeInlineCompletions: () => { },345};346const mockSource = new InlineSuggestionList(347{ items: [mockInlineCompletion] },348[],349mockProvider350);351const mockContext: InlineCompletionContext = {352triggerKind: InlineCompletionTriggerKind.Explicit,353selectedSuggestionInfo: undefined,354requestUuid: 'test-' + Date.now(),355earliestShownDateTime: 0,356includeInlineCompletions: true,357includeInlineEdits: false,358requestIssuedDateTime: Date.now(),359};360const mockRequestInfo: InlineSuggestRequestInfo = {361startTime: Date.now(),362sku: undefined,363editorType: InlineCompletionEditorType.TextEditor,364languageId: 'plaintext',365availableProviders: [],366reason: '',367typingInterval: 0,368typingIntervalCharacterCount: 0,369};370const mockProviderRequestInfo: InlineSuggestProviderRequestInfo = {371startTime: Date.now(),372endTime: Date.now(),373};374375return new InlineSuggestData(376action,377undefined,378[],379mockInlineCompletion,380mockSource,381mockContext,382true,383false,384mockRequestInfo,385mockProviderRequestInfo,386undefined387);388}389390private _didShow = false;391private _timeUntilShown: number | undefined = undefined;392private _timeUntilActuallyShown: number | undefined = undefined;393private _showStartTime: number | undefined = undefined;394private _shownDuration: number = 0;395private _showUncollapsedStartTime: number | undefined = undefined;396private _showUncollapsedDuration: number = 0;397private _notShownReason: string | undefined = undefined;398399private _viewData: InlineSuggestViewData;400private _didReportEndOfLife = false;401private _lastSetEndOfLifeReason: InlineCompletionEndOfLifeReason | undefined = undefined;402private _isPreceeded = false;403private _partiallyAcceptedCount = 0;404private _partiallyAcceptedSinceOriginal: PartialAcceptance = { characters: 0, ratio: 0, count: 0 };405private _renameInfo: RenameInfo | undefined = undefined;406private _editKind: InlineSuggestionEditKind | undefined = undefined;407408get action(): IInlineSuggestDataAction | undefined {409return this._action;410}411412constructor(413private _action: IInlineSuggestDataAction | undefined,414public readonly hint: IInlineCompletionHint | undefined,415public readonly additionalTextEdits: readonly ISingleEditOperation[],416public readonly sourceInlineCompletion: InlineCompletion,417public readonly source: InlineSuggestionList,418public readonly context: InlineCompletionContext,419public readonly isInlineEdit: boolean,420public readonly supportsRename: boolean,421private readonly _requestInfo: InlineSuggestRequestInfo,422private readonly _providerRequestInfo: InlineSuggestProviderRequestInfo,423private readonly _correlationId: string | undefined,424) {425this._viewData = { editorType: _requestInfo.editorType };426}427428public get showInlineEditMenu() { return this.sourceInlineCompletion.showInlineEditMenu ?? false; }429430public get partialAccepts(): PartialAcceptance { return this._partiallyAcceptedSinceOriginal; }431432433public async reportInlineEditShown(commandService: ICommandService, updatedInsertText: string, viewKind: InlineCompletionViewKind, viewData: InlineCompletionViewData, editKind: InlineSuggestionEditKind | undefined, timeWhenShown: number): Promise<void> {434this.updateShownDuration(viewKind);435436if (this._didShow || this._didReportEndOfLife) {437return;438}439this.addPerformanceMarker('shown');440this._didShow = true;441this._editKind = editKind;442this._viewData.viewKind = viewKind;443this._viewData.renderData = viewData;444this._timeUntilShown = timeWhenShown - this._requestInfo.startTime;445this._timeUntilActuallyShown = Date.now() - this._requestInfo.startTime;446447const editDeltaInfo = new EditDeltaInfo(viewData.lineCountModified, viewData.lineCountOriginal, viewData.characterCountModified, viewData.characterCountOriginal);448this.source.provider.handleItemDidShow?.(this.source.inlineSuggestions, this.sourceInlineCompletion, updatedInsertText, editDeltaInfo);449450if (this.sourceInlineCompletion.shownCommand) {451await commandService.executeCommand(this.sourceInlineCompletion.shownCommand.id, ...(this.sourceInlineCompletion.shownCommand.arguments || []));452}453}454455public reportPartialAccept(acceptedCharacters: number, info: PartialAcceptInfo, partialAcceptance: PartialAcceptance) {456this._partiallyAcceptedCount++;457this._partiallyAcceptedSinceOriginal.characters += partialAcceptance.characters;458this._partiallyAcceptedSinceOriginal.ratio = Math.min(this._partiallyAcceptedSinceOriginal.ratio + (1 - this._partiallyAcceptedSinceOriginal.ratio) * partialAcceptance.ratio, 1);459this._partiallyAcceptedSinceOriginal.count += partialAcceptance.count;460461this.source.provider.handlePartialAccept?.(462this.source.inlineSuggestions,463this.sourceInlineCompletion,464acceptedCharacters,465info466);467}468469/**470* Sends the end of life event to the provider.471* If no reason is provided, the last set reason is used.472* If no reason was set, the default reason is used.473*/474public reportEndOfLife(reason?: InlineCompletionEndOfLifeReason): void {475if (this._didReportEndOfLife) {476return;477}478this._didReportEndOfLife = true;479this.reportInlineEditHidden();480481if (!reason) {482reason = this._lastSetEndOfLifeReason ?? { kind: InlineCompletionEndOfLifeReasonKind.Ignored, userTypingDisagreed: false, supersededBy: undefined };483}484485// A suggestion can only be "rejected" if it was actually shown to the user.486// If the suggestion was never shown, downgrade to "ignored".487if (reason.kind === InlineCompletionEndOfLifeReasonKind.Rejected && !this._didShow) {488reason = { kind: InlineCompletionEndOfLifeReasonKind.Ignored, userTypingDisagreed: false, supersededBy: undefined };489}490491if (reason.kind === InlineCompletionEndOfLifeReasonKind.Rejected && this.source.provider.handleRejection) {492this.source.provider.handleRejection(this.source.inlineSuggestions, this.sourceInlineCompletion);493}494495if (this.source.provider.handleEndOfLifetime) {496const summary: LifetimeSummary = {497requestUuid: this.context.requestUuid,498correlationId: this._correlationId,499selectedSuggestionInfo: !!this.context.selectedSuggestionInfo,500partiallyAccepted: this._partiallyAcceptedCount,501partiallyAcceptedCountSinceOriginal: this._partiallyAcceptedSinceOriginal.count,502partiallyAcceptedRatioSinceOriginal: this._partiallyAcceptedSinceOriginal.ratio,503partiallyAcceptedCharactersSinceOriginal: this._partiallyAcceptedSinceOriginal.characters,504shown: this._didShow,505shownDuration: this._shownDuration,506shownDurationUncollapsed: this._showUncollapsedDuration,507editKind: this._editKind?.toString(),508preceeded: this._isPreceeded,509timeUntilShown: this._timeUntilShown,510timeUntilActuallyShown: this._timeUntilActuallyShown,511timeUntilProviderRequest: this._providerRequestInfo.startTime - this._requestInfo.startTime,512timeUntilProviderResponse: this._providerRequestInfo.endTime - this._requestInfo.startTime,513editorType: this._viewData.editorType,514languageId: this._requestInfo.languageId,515requestReason: this._requestInfo.reason,516viewKind: this._viewData.viewKind,517notShownReason: this._notShownReason,518performanceMarkers: this.performance.toString(),519renameCreated: this._renameInfo?.createdRename,520renameDuration: this._renameInfo?.duration,521renameTimedOut: this._renameInfo?.timedOut,522renameDroppedOtherEdits: this._renameInfo?.droppedOtherEdits,523renameDroppedRenameEdits: this._renameInfo?.droppedRenameEdits,524typingInterval: this._requestInfo.typingInterval,525typingIntervalCharacterCount: this._requestInfo.typingIntervalCharacterCount,526skuPlan: this._requestInfo.sku?.plan,527skuType: this._requestInfo.sku?.type,528availableProviders: this._requestInfo.availableProviders.map(p => p.toString()).join(','),529...this._viewData.renderData?.getData(),530};531this.source.provider.handleEndOfLifetime(this.source.inlineSuggestions, this.sourceInlineCompletion, reason, summary);532}533}534535public setIsPreceeded(partialAccepts: PartialAcceptance): void {536this._isPreceeded = true;537538if (this._partiallyAcceptedSinceOriginal.characters !== 0 || this._partiallyAcceptedSinceOriginal.ratio !== 0 || this._partiallyAcceptedSinceOriginal.count !== 0) {539console.warn('Expected partiallyAcceptedCountSinceOriginal to be { characters: 0, rate: 0, partialAcceptances: 0 } before setIsPreceeded.');540}541this._partiallyAcceptedSinceOriginal = partialAccepts;542}543544public setNotShownReason(reason: string): void {545this._notShownReason ??= reason;546}547548/**549* Sets the end of life reason, but does not send the event to the provider yet.550*/551public setEndOfLifeReason(reason: InlineCompletionEndOfLifeReason): void {552this.reportInlineEditHidden();553this._lastSetEndOfLifeReason = reason;554}555556private updateShownDuration(viewKind: InlineCompletionViewKind) {557const timeNow = Date.now();558if (!this._showStartTime) {559this._showStartTime = timeNow;560}561562const isCollapsed = viewKind === InlineCompletionViewKind.Collapsed;563if (!isCollapsed && this._showUncollapsedStartTime === undefined) {564this._showUncollapsedStartTime = timeNow;565}566567if (isCollapsed && this._showUncollapsedStartTime !== undefined) {568this._showUncollapsedDuration += timeNow - this._showUncollapsedStartTime;569}570}571572private reportInlineEditHidden() {573if (this._showStartTime === undefined) {574return;575}576const timeNow = Date.now();577this._shownDuration += timeNow - this._showStartTime;578this._showStartTime = undefined;579580if (this._showUncollapsedStartTime === undefined) {581return;582}583this._showUncollapsedDuration += timeNow - this._showUncollapsedStartTime;584this._showUncollapsedStartTime = undefined;585}586587public setRenameProcessingInfo(info: RenameInfo): void {588if (this._renameInfo) {589throw new BugIndicatingError('Rename info has already been set.');590}591this._renameInfo = info;592}593594public withAction(action: IInlineSuggestDataAction): InlineSuggestData {595this._action = action;596return this;597}598599private performance = new InlineSuggestionsPerformance();600public addPerformanceMarker(marker: string): void {601this.performance.mark(marker);602}603}604605class InlineSuggestionsPerformance {606private markers: { name: string; timeStamp: number }[] = [];607constructor() {608this.markers.push({ name: 'start', timeStamp: Date.now() });609}610611mark(marker: string): void {612this.markers.push({ name: marker, timeStamp: Date.now() });613}614615toString(): string {616const deltas = [];617for (let i = 1; i < this.markers.length; i++) {618const delta = this.markers[i].timeStamp - this.markers[i - 1].timeStamp;619deltas.push({ [this.markers[i].name]: delta });620}621return JSON.stringify(deltas);622}623}624625export interface SnippetInfo {626snippet: string;627/* Could be different than the main range */628range: Range;629}630631export enum InlineCompletionEditorType {632TextEditor = 'textEditor',633DiffEditor = 'diffEditor',634Notebook = 'notebook',635}636637/**638* A ref counted pointer to the computed `InlineCompletions` and the `InlineCompletionsProvider` that639* computed them.640*/641export class InlineSuggestionList {642private refCount = 0;643constructor(644public readonly inlineSuggestions: InlineCompletions,645public readonly inlineSuggestionsData: readonly InlineSuggestData[],646public readonly provider: InlineCompletionsProvider,647) { }648649addRef(): void {650this.refCount++;651}652653removeRef(reason: InlineCompletionsDisposeReason = { kind: 'other' }): void {654this.refCount--;655if (this.refCount === 0) {656for (const item of this.inlineSuggestionsData) {657// Fallback if it has not been called before658item.reportEndOfLife();659}660this.provider.disposeInlineCompletions(this.inlineSuggestions, reason);661}662}663}664665function getDefaultRange(position: Position, model: ITextModel): Range {666const word = model.getWordAtPosition(position);667const maxColumn = model.getLineMaxColumn(position.lineNumber);668// By default, always replace up until the end of the current line.669// This default might be subject to change!670return word671? new Range(position.lineNumber, word.startColumn, position.lineNumber, maxColumn)672: Range.fromPositions(position, position.with(undefined, maxColumn));673}674675function closeBrackets(text: string, position: Position, model: ITextModel, languageConfigurationService: ILanguageConfigurationService): string {676const currentLine = model.getLineContent(position.lineNumber);677const edit = StringReplacement.replace(new OffsetRange(position.column - 1, currentLine.length), text);678679const proposedLineTokens = model.tokenization.tokenizeLinesAt(position.lineNumber, [edit.replace(currentLine)]);680const textTokens = proposedLineTokens?.[0].sliceZeroCopy(edit.getRangeAfterReplace());681if (!textTokens) {682return text;683}684685const fixedText = fixBracketsInLine(textTokens, languageConfigurationService);686return fixedText;687}688689690