Path: blob/main/extensions/copilot/src/extension/prompts/node/inline/workingCopies.ts
13405 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 type { ChatResponseStream, ExtendedChatResponsePart } from 'vscode';6import { PositionOffsetTransformer } from '../../../../platform/editing/common/positionOffsetTransformer';7import { ChatResponseStreamImpl } from '../../../../util/common/chatResponseStreamImpl';8import { StringEdit } from '../../../../util/vs/editor/common/core/edits/stringEdit';9import { ChatResponseTextEditPart, TextEdit } from '../../../../vscodeTypes';10import { ProjectedDocument } from './summarizedDocument/summarizeDocument';1112export class WorkingCopyDerivedDocument {1314private readonly _workingCopyOriginalDocument: WorkingCopyOriginalDocument;1516public get originalText(): string {17return this._workingCopyOriginalDocument.text;18}1920public get text(): string {21return this._derivedDocument.text;22}2324public get languageId(): string {25return this._derivedDocument.languageId;26}2728public get derivedDocumentTransformer(): PositionOffsetTransformer {29return this._derivedDocument.positionOffsetTransformer;30}3132public get originalDocumentTransformer(): PositionOffsetTransformer {33return this._workingCopyOriginalDocument.transformer;34}3536/**37* All the edits reported through the progress reporter (combined into a single OffsetEdits object).38*/39public get allReportedEdits(): StringEdit {40return this._workingCopyOriginalDocument.appliedEdits;41}4243constructor(44private _derivedDocument: ProjectedDocument45) {46this._workingCopyOriginalDocument = new WorkingCopyOriginalDocument(this._derivedDocument.originalText);47}4849createDerivedDocumentChatResponseStream(outputStream: ChatResponseStream): ChatResponseStream {50return new ChatResponseStreamImpl((_value) => {51const value = this.applyAndTransformProgressItem(_value);52outputStream.push(value);53}, (reason) => {54outputStream.clearToPreviousToolInvocation(reason);55}, undefined, undefined, undefined, (questions, allowSkip) => {56return outputStream.questionCarousel(questions, allowSkip);57});58}5960public applyAndTransformProgressItem(value: ExtendedChatResponsePart): ExtendedChatResponsePart {6162if (!(value instanceof ChatResponseTextEditPart)) {63return value;64}656667// e_sum68// d0 ---------------> s069// | |70// | |71// | e_ai_r | e_ai72// | |73// | |74// v e_sum_r v75/// d1 ---------------> s176//77// d0 - document78// s0 - summarized document79// e_sum - summarization edits80// e_ai - AI edits81//82// The incoming AI edits `e_ai` are based on the derived summarized document `s0`.83// But we need to apply them on the original document `d0`.84// We can compute `e_ai_r` by rebasing `e_ai` against `inverse(e_sum)`85// We can then compute `e_sum_r` by rebasing `e_sum` against `e_ai_r`.86const d0 = this._workingCopyOriginalDocument;87const s0 = this._derivedDocument;88const e_sum = s0.edits;89const e_ai = toOffsetEdits(s0.positionOffsetTransformer, value.edits);90const e_ai_r = e_ai.rebaseSkipConflicting(e_sum.inverse(d0.text));91const e_sum_r = e_sum.rebaseSkipConflicting(e_ai_r);9293const transformedProgressItem = new ChatResponseTextEditPart(value.uri, fromOffsetEdits(d0.transformer, e_ai_r));9495this._workingCopyOriginalDocument.applyOffsetEdits(e_ai_r);96this._derivedDocument = new ProjectedDocument(this._workingCopyOriginalDocument.text, e_sum_r, this._derivedDocument.languageId);9798return transformedProgressItem;99}100101public rebaseEdits(edits: readonly TextEdit[]): TextEdit[] {102// See comment from above explaining the rebasing103const d0 = this._workingCopyOriginalDocument;104const s0 = this._derivedDocument;105const e_sum = s0.edits;106const e_ai = toOffsetEdits(s0.positionOffsetTransformer, edits);107const e_ai_r = e_ai.rebaseSkipConflicting(e_sum.inverse(d0.text));108return fromOffsetEdits(d0.transformer, e_ai_r);109}110111public convertPostEditsOffsetToOriginalOffset(postEditsOffset: number): number {112return this._derivedDocument.projectBack(postEditsOffset);113}114}115116/**117* Keeps track of the current document with edits applied immediately.118* This simulates the EOL sequence behavior of VS Code, namely it keeps the EOL sequence119* of the original document and it does not allow for mixed EOL sequences.120*/121export class WorkingCopyOriginalDocument {122123public get text(): string {124return this._text;125}126127private _transformer: PositionOffsetTransformer | null = null;128public get transformer(): PositionOffsetTransformer {129if (!this._transformer) {130this._transformer = new PositionOffsetTransformer(this._text);131}132return this._transformer;133}134135private _appliedEdits: StringEdit = new StringEdit([]);136public get appliedEdits(): StringEdit {137return this._appliedEdits;138}139140private readonly _eol: '\r\n' | '\n';141142constructor(143private _text: string,144) {145// VS Code doesn't allow mixed EOL sequences, so the presence of one \r\n146// indicates that the document uses \r\n as EOL sequence.147this._eol = _text.includes('\r\n') ? '\r\n' : '\n';148}149150/**151* Checks if the edit would produce no changes when applied to the current document.152*/153isNoop(offsetEdits: StringEdit): boolean {154return offsetEdits.isNeutralOn(this._text);155}156157applyOffsetEdits(_offsetEdits: StringEdit) {158const offsetEdits = _offsetEdits.normalizeEOL(this._eol);159const edits = offsetEdits.replacements;160let text = this._text;161for (let i = edits.length - 1; i >= 0; i--) {162const edit = edits[i];163text = text.substring(0, edit.replaceRange.start) + edit.newText + text.substring(edit.replaceRange.endExclusive);164}165166this._text = text;167if (this._transformer) {168this._transformer.applyOffsetEdits(offsetEdits);169}170this._appliedEdits = this._appliedEdits.compose(offsetEdits);171}172}173174export class DocumentSnapshot {175176public get text(): string {177return this._text;178}179180private _transformer: PositionOffsetTransformer | null = null;181public get transformer(): PositionOffsetTransformer {182if (!this._transformer) {183this._transformer = new PositionOffsetTransformer(this._text);184}185return this._transformer;186}187188constructor(189private readonly _text: string,190) { }191192}193194export function toOffsetEdits(transformer: PositionOffsetTransformer, edits: readonly TextEdit[]): StringEdit {195return transformer.toOffsetEdit(edits);196}197198export function fromOffsetEdits(transformer: PositionOffsetTransformer, edit: StringEdit): TextEdit[] {199return transformer.toTextEdits(edit);200}201202203