Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/extensions/copilot/src/extension/prompts/node/inline/promptingSummarizedDocument.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 * as vscode from 'vscode';
7
import { IResponsePart } from '../../../../platform/chat/common/chatMLFetcher';
8
import { TextDocumentSnapshot } from '../../../../platform/editing/common/textDocumentSnapshot';
9
import { IParserService } from '../../../../platform/parser/node/parserService';
10
import { findLastIdx } from '../../../../util/vs/base/common/arraysFind';
11
import { CancellationToken } from '../../../../util/vs/base/common/cancellation';
12
import { OffsetRange } from '../../../../util/vs/editor/common/core/ranges/offsetRange';
13
import { Range, TextEdit } from '../../../../vscodeTypes';
14
import { ISessionTurnStorage, OutcomeAnnotationLabel } from '../../../inlineChat/node/promptCraftingTypes';
15
import { isImportStatement } from '../../../prompt/common/importStatement';
16
import { EditStrategy, trimLeadingWhitespace } from '../../../prompt/node/editGeneration';
17
import { EarlyStopping, IResponseProcessorContext, LeadingMarkdownStreaming, ReplyInterpreter, StreamingEditsController } from '../../../prompt/node/intents';
18
import { ILineFilter, IStreamingEditsStrategyFactory, IStreamingTextPieceClassifier, InsertOrReplaceStreamingEdits, InsertionStreamingEdits, LineRange, ReplaceSelectionStreamingEdits, SentInCodeBlock, SentLine, StreamingWorkingCopyDocument } from '../../../prompt/node/streamingEdits';
19
import { ProjectedDocument } from './summarizedDocument/summarizeDocument';
20
import { adjustSelectionAndSummarizeDocument } from './summarizedDocument/summarizeDocumentHelpers';
21
import { DocumentSnapshot, WorkingCopyDerivedDocument } from './workingCopies';
22
23
export async function createPromptingSummarizedDocument(
24
parserService: IParserService,
25
document: TextDocumentSnapshot,
26
formattingOptions: vscode.FormattingOptions | undefined,
27
userSelection: Range,
28
tokensBudget: number,
29
): Promise<PromptingSummarizedDocument> {
30
const result = await adjustSelectionAndSummarizeDocument(parserService, document, formattingOptions, userSelection, tokensBudget);
31
return new PromptingSummarizedDocument(
32
result.selection,
33
result.adjustedSelection,
34
result.document,
35
document,
36
formattingOptions,
37
);
38
}
39
40
export class PromptingSummarizedDocument {
41
42
public get uri(): vscode.Uri {
43
return this._document.uri;
44
}
45
46
public get languageId(): string {
47
return this._document.languageId;
48
}
49
50
constructor(
51
private readonly _selection: OffsetRange,
52
private readonly _adjustedSelection: OffsetRange,
53
private readonly _projectedDocument: ProjectedDocument,
54
private readonly _document: TextDocumentSnapshot,
55
private readonly _formattingOptions: vscode.FormattingOptions | undefined,
56
) { }
57
58
public splitAroundAdjustedSelection(): SummarizedDocumentSplit {
59
return new SummarizedDocumentSplit(
60
this._projectedDocument,
61
this.uri,
62
this._formattingOptions,
63
this._adjustedSelection
64
);
65
}
66
67
public splitAroundOriginalSelectionEnd(): SummarizedDocumentSplit {
68
return new SummarizedDocumentSplit(
69
this._projectedDocument,
70
this.uri,
71
this._formattingOptions,
72
new OffsetRange(
73
this._selection.endExclusive,
74
this._selection.endExclusive
75
)
76
);
77
}
78
}
79
80
export class SummarizedDocumentSplit {
81
82
public readonly codeAbove: string;
83
public readonly codeSelected: string;
84
public readonly codeBelow: string;
85
private readonly _selection: vscode.Range;
86
87
public get hasCodeWithoutSelection(): boolean {
88
return (
89
this.codeAbove.trim().length > 0
90
|| this.codeBelow.trim().length > 0
91
);
92
}
93
94
public get hasContent(): boolean {
95
return (
96
this.codeAbove.trim().length > 0
97
|| this.codeSelected.trim().length > 0
98
|| this.codeBelow.trim().length > 0
99
);
100
}
101
102
constructor(
103
private readonly _projectedDocument: ProjectedDocument,
104
private readonly _uri: vscode.Uri,
105
private readonly _formattingOptions: vscode.FormattingOptions | undefined,
106
offsetSelection: OffsetRange
107
) {
108
this._selection = this._projectedDocument.positionOffsetTransformer.toRange(offsetSelection);
109
this.codeAbove = this._projectedDocument.text.substring(0, offsetSelection.start);
110
this.codeSelected = this._projectedDocument.text.substring(offsetSelection.start, offsetSelection.endExclusive);
111
this.codeBelow = this._projectedDocument.text.substring(offsetSelection.endExclusive);
112
}
113
114
public get replaceSelectionStreaming(): IStreamingEditsStrategyFactory {
115
return (lineFilter, streamingWorkingCopyDocument) => new ReplaceSelectionStreamingEdits(
116
streamingWorkingCopyDocument,
117
this._selection,
118
lineFilter
119
);
120
}
121
122
public get insertStreaming(): IStreamingEditsStrategyFactory {
123
return (lineFilter, streamingWorkingCopyDocument) => new InsertionStreamingEdits(
124
streamingWorkingCopyDocument,
125
this._selection.end,
126
lineFilter
127
);
128
}
129
130
public get insertOrReplaceStreaming(): IStreamingEditsStrategyFactory {
131
return (lineFilter, streamingWorkingCopyDocument) => new InsertOrReplaceStreamingEdits(
132
streamingWorkingCopyDocument,
133
this._selection,
134
this._selection,
135
EditStrategy.FallbackToInsertBelowRange,
136
true,
137
lineFilter
138
);
139
}
140
141
public createReplyInterpreter(
142
leadingMarkdownStreaming: LeadingMarkdownStreaming,
143
earlyStopping: EarlyStopping,
144
streamingStrategyFactory: IStreamingEditsStrategyFactory,
145
textPieceClassifier: IStreamingTextPieceClassifier,
146
lineFilter: ILineFilter
147
): ReplyInterpreter {
148
return new InlineReplyInterpreter(
149
this._uri,
150
this._projectedDocument,
151
this._formattingOptions,
152
leadingMarkdownStreaming,
153
earlyStopping,
154
streamingStrategyFactory,
155
textPieceClassifier,
156
lineFilter
157
);
158
}
159
}
160
161
export class InlineReplyInterpreter implements ReplyInterpreter {
162
163
private readonly _initialDocumentSnapshot: DocumentSnapshot;
164
private readonly _workingCopySummarizedDoc: WorkingCopyDerivedDocument;
165
private _lastText: string = '';
166
167
constructor(
168
private readonly _uri: vscode.Uri,
169
summarizedDoc: ProjectedDocument,
170
private readonly _fileIndentInfo: vscode.FormattingOptions | undefined,
171
private readonly _leadingMarkdownStreaming: LeadingMarkdownStreaming,
172
private readonly _earlyStopping: EarlyStopping,
173
private readonly _streamingStrategyFactory: IStreamingEditsStrategyFactory,
174
private readonly _textPieceClassifier: IStreamingTextPieceClassifier,
175
private readonly _lineFilter: ILineFilter
176
) {
177
this._initialDocumentSnapshot = new DocumentSnapshot(summarizedDoc.originalText);
178
this._workingCopySummarizedDoc = new WorkingCopyDerivedDocument(summarizedDoc);
179
}
180
181
async processResponse(context: IResponseProcessorContext, inputStream: AsyncIterable<IResponsePart>, _outputStream: vscode.ChatResponseStream, token: CancellationToken): Promise<void> {
182
const outputStream = this._workingCopySummarizedDoc.createDerivedDocumentChatResponseStream(_outputStream);
183
const streamingWorkingCopyDocument = new StreamingWorkingCopyDocument(
184
outputStream,
185
this._uri,
186
this._workingCopySummarizedDoc.text,
187
this._workingCopySummarizedDoc.text.split('\n').map((_, index) => new SentLine(index, SentInCodeBlock.Other)), // not used
188
new LineRange(0, 0), // not used
189
this._workingCopySummarizedDoc.languageId,
190
this._fileIndentInfo
191
);
192
193
const streaming = new StreamingEditsController(
194
outputStream,
195
this._leadingMarkdownStreaming,
196
this._earlyStopping,
197
this._textPieceClassifier,
198
this._streamingStrategyFactory(this._lineFilter, streamingWorkingCopyDocument),
199
);
200
201
for await (const part of inputStream) {
202
this._lastText += part.delta.text;
203
const { shouldFinish } = streaming.update(this._lastText);
204
if (shouldFinish) {
205
break;
206
}
207
}
208
209
const { didEdits, didNoopEdits, additionalImports } = await streaming.finish();
210
if (didEdits) {
211
const additionalImportsEdits = this._generateAdditionalImportsEdits(additionalImports);
212
213
const reversedEdits = this._workingCopySummarizedDoc.allReportedEdits.inverse(this._initialDocumentSnapshot.text);
214
const entireModifiedRangeOffsets = reversedEdits.replacements.reduce((prev, curr) => prev.join(curr.replaceRange), reversedEdits.replacements[0].replaceRange);
215
const entireModifiedRange = this._workingCopySummarizedDoc.originalDocumentTransformer.toRange(entireModifiedRangeOffsets);
216
const store = {
217
lastDocumentContent: this._workingCopySummarizedDoc.originalText,
218
lastWholeRange: entireModifiedRange,
219
} satisfies ISessionTurnStorage;
220
221
_outputStream.textEdit(this._uri, additionalImportsEdits);
222
context.storeInInlineSession(store);
223
return;
224
}
225
226
if (additionalImports.length > 0) {
227
// No edits, but imports encountered
228
_outputStream.textEdit(this._uri, this._generateAdditionalImportsEdits(additionalImports));
229
return;
230
}
231
232
if (didNoopEdits) {
233
// we attempted to do edits, but they were not meaningful, i.e. they didn't change anything
234
context.addAnnotations([{ label: OutcomeAnnotationLabel.NOOP_EDITS, message: 'Edits were not applied because they were having no actual effects.', severity: 'info' }]);
235
return;
236
}
237
238
if (!this._lastText) {
239
return;
240
}
241
242
outputStream.markdown(this._lastText);
243
}
244
245
private _generateAdditionalImportsEdits(additionalImports: string[]): vscode.TextEdit[] {
246
if (additionalImports.length === 0) {
247
return [];
248
}
249
250
const documentLines = this._workingCopySummarizedDoc.originalText.split(/\r\n|\r|\n/g);
251
const lastImportStatementLineIdx = findLastIdx(documentLines, l => isImportStatement(l, this._workingCopySummarizedDoc.languageId));
252
if (lastImportStatementLineIdx === -1) {
253
// no existing import statements, we insert it on line 0
254
return [new TextEdit(new Range(0, 0, 0, 0), additionalImports.join('\n') + '\n\n')];
255
}
256
257
// traverse lines upward starting at `lastImportStatementLineIdx` to capture all existing imports
258
const existingImports = new Set<string>();
259
for (let i = lastImportStatementLineIdx; i >= 0; i--) {
260
const line = documentLines[i];
261
if (line.trim() === '') { // skip empty lines
262
continue;
263
}
264
if (isImportStatement(line, this._workingCopySummarizedDoc.languageId)) {
265
existingImports.add(trimLeadingWhitespace(line));
266
} else {
267
break;
268
}
269
}
270
271
additionalImports = additionalImports.filter(i => !existingImports.has(i));
272
if (additionalImports.length === 0) {
273
return [];
274
}
275
276
const lastImportStatementLineLength = documentLines[lastImportStatementLineIdx].length;
277
return [new TextEdit(new Range(lastImportStatementLineIdx, lastImportStatementLineLength, lastImportStatementLineIdx, lastImportStatementLineLength), '\n' + additionalImports.join('\n'))];
278
}
279
}
280
281