Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/extensions/copilot/src/extension/prompts/node/inline/workingCopies.ts
13405 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import type { ChatResponseStream, ExtendedChatResponsePart } from 'vscode';
7
import { PositionOffsetTransformer } from '../../../../platform/editing/common/positionOffsetTransformer';
8
import { ChatResponseStreamImpl } from '../../../../util/common/chatResponseStreamImpl';
9
import { StringEdit } from '../../../../util/vs/editor/common/core/edits/stringEdit';
10
import { ChatResponseTextEditPart, TextEdit } from '../../../../vscodeTypes';
11
import { ProjectedDocument } from './summarizedDocument/summarizeDocument';
12
13
export class WorkingCopyDerivedDocument {
14
15
private readonly _workingCopyOriginalDocument: WorkingCopyOriginalDocument;
16
17
public get originalText(): string {
18
return this._workingCopyOriginalDocument.text;
19
}
20
21
public get text(): string {
22
return this._derivedDocument.text;
23
}
24
25
public get languageId(): string {
26
return this._derivedDocument.languageId;
27
}
28
29
public get derivedDocumentTransformer(): PositionOffsetTransformer {
30
return this._derivedDocument.positionOffsetTransformer;
31
}
32
33
public get originalDocumentTransformer(): PositionOffsetTransformer {
34
return this._workingCopyOriginalDocument.transformer;
35
}
36
37
/**
38
* All the edits reported through the progress reporter (combined into a single OffsetEdits object).
39
*/
40
public get allReportedEdits(): StringEdit {
41
return this._workingCopyOriginalDocument.appliedEdits;
42
}
43
44
constructor(
45
private _derivedDocument: ProjectedDocument
46
) {
47
this._workingCopyOriginalDocument = new WorkingCopyOriginalDocument(this._derivedDocument.originalText);
48
}
49
50
createDerivedDocumentChatResponseStream(outputStream: ChatResponseStream): ChatResponseStream {
51
return new ChatResponseStreamImpl((_value) => {
52
const value = this.applyAndTransformProgressItem(_value);
53
outputStream.push(value);
54
}, (reason) => {
55
outputStream.clearToPreviousToolInvocation(reason);
56
}, undefined, undefined, undefined, (questions, allowSkip) => {
57
return outputStream.questionCarousel(questions, allowSkip);
58
});
59
}
60
61
public applyAndTransformProgressItem(value: ExtendedChatResponsePart): ExtendedChatResponsePart {
62
63
if (!(value instanceof ChatResponseTextEditPart)) {
64
return value;
65
}
66
67
68
// e_sum
69
// d0 ---------------> s0
70
// | |
71
// | |
72
// | e_ai_r | e_ai
73
// | |
74
// | |
75
// v e_sum_r v
76
/// d1 ---------------> s1
77
//
78
// d0 - document
79
// s0 - summarized document
80
// e_sum - summarization edits
81
// e_ai - AI edits
82
//
83
// The incoming AI edits `e_ai` are based on the derived summarized document `s0`.
84
// But we need to apply them on the original document `d0`.
85
// We can compute `e_ai_r` by rebasing `e_ai` against `inverse(e_sum)`
86
// We can then compute `e_sum_r` by rebasing `e_sum` against `e_ai_r`.
87
const d0 = this._workingCopyOriginalDocument;
88
const s0 = this._derivedDocument;
89
const e_sum = s0.edits;
90
const e_ai = toOffsetEdits(s0.positionOffsetTransformer, value.edits);
91
const e_ai_r = e_ai.rebaseSkipConflicting(e_sum.inverse(d0.text));
92
const e_sum_r = e_sum.rebaseSkipConflicting(e_ai_r);
93
94
const transformedProgressItem = new ChatResponseTextEditPart(value.uri, fromOffsetEdits(d0.transformer, e_ai_r));
95
96
this._workingCopyOriginalDocument.applyOffsetEdits(e_ai_r);
97
this._derivedDocument = new ProjectedDocument(this._workingCopyOriginalDocument.text, e_sum_r, this._derivedDocument.languageId);
98
99
return transformedProgressItem;
100
}
101
102
public rebaseEdits(edits: readonly TextEdit[]): TextEdit[] {
103
// See comment from above explaining the rebasing
104
const d0 = this._workingCopyOriginalDocument;
105
const s0 = this._derivedDocument;
106
const e_sum = s0.edits;
107
const e_ai = toOffsetEdits(s0.positionOffsetTransformer, edits);
108
const e_ai_r = e_ai.rebaseSkipConflicting(e_sum.inverse(d0.text));
109
return fromOffsetEdits(d0.transformer, e_ai_r);
110
}
111
112
public convertPostEditsOffsetToOriginalOffset(postEditsOffset: number): number {
113
return this._derivedDocument.projectBack(postEditsOffset);
114
}
115
}
116
117
/**
118
* Keeps track of the current document with edits applied immediately.
119
* This simulates the EOL sequence behavior of VS Code, namely it keeps the EOL sequence
120
* of the original document and it does not allow for mixed EOL sequences.
121
*/
122
export class WorkingCopyOriginalDocument {
123
124
public get text(): string {
125
return this._text;
126
}
127
128
private _transformer: PositionOffsetTransformer | null = null;
129
public get transformer(): PositionOffsetTransformer {
130
if (!this._transformer) {
131
this._transformer = new PositionOffsetTransformer(this._text);
132
}
133
return this._transformer;
134
}
135
136
private _appliedEdits: StringEdit = new StringEdit([]);
137
public get appliedEdits(): StringEdit {
138
return this._appliedEdits;
139
}
140
141
private readonly _eol: '\r\n' | '\n';
142
143
constructor(
144
private _text: string,
145
) {
146
// VS Code doesn't allow mixed EOL sequences, so the presence of one \r\n
147
// indicates that the document uses \r\n as EOL sequence.
148
this._eol = _text.includes('\r\n') ? '\r\n' : '\n';
149
}
150
151
/**
152
* Checks if the edit would produce no changes when applied to the current document.
153
*/
154
isNoop(offsetEdits: StringEdit): boolean {
155
return offsetEdits.isNeutralOn(this._text);
156
}
157
158
applyOffsetEdits(_offsetEdits: StringEdit) {
159
const offsetEdits = _offsetEdits.normalizeEOL(this._eol);
160
const edits = offsetEdits.replacements;
161
let text = this._text;
162
for (let i = edits.length - 1; i >= 0; i--) {
163
const edit = edits[i];
164
text = text.substring(0, edit.replaceRange.start) + edit.newText + text.substring(edit.replaceRange.endExclusive);
165
}
166
167
this._text = text;
168
if (this._transformer) {
169
this._transformer.applyOffsetEdits(offsetEdits);
170
}
171
this._appliedEdits = this._appliedEdits.compose(offsetEdits);
172
}
173
}
174
175
export class DocumentSnapshot {
176
177
public get text(): string {
178
return this._text;
179
}
180
181
private _transformer: PositionOffsetTransformer | null = null;
182
public get transformer(): PositionOffsetTransformer {
183
if (!this._transformer) {
184
this._transformer = new PositionOffsetTransformer(this._text);
185
}
186
return this._transformer;
187
}
188
189
constructor(
190
private readonly _text: string,
191
) { }
192
193
}
194
195
export function toOffsetEdits(transformer: PositionOffsetTransformer, edits: readonly TextEdit[]): StringEdit {
196
return transformer.toOffsetEdit(edits);
197
}
198
199
export function fromOffsetEdits(transformer: PositionOffsetTransformer, edit: StringEdit): TextEdit[] {
200
return transformer.toTextEdits(edit);
201
}
202
203