Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/extensions/copilot/src/extension/context/node/resolvers/inlineChatSelection.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
7
import type * as vscode from 'vscode';
8
import { TextDocumentSnapshot } from '../../../../platform/editing/common/textDocumentSnapshot';
9
import { TreeSitterOffsetRange } from '../../../../platform/parser/node/nodes';
10
import { ITabsAndEditorsService } from '../../../../platform/tabs/common/tabsAndEditorsService';
11
import { IWorkspaceService } from '../../../../platform/workspace/common/workspaceService';
12
import { ILanguage, getLanguage } from '../../../../util/common/languages';
13
import { findCell, findNotebook } from '../../../../util/common/notebooks';
14
import { Schemas } from '../../../../util/vs/base/common/network';
15
import { Position, Range } from '../../../../vscodeTypes';
16
import { CodeContextRegion, CodeContextTracker } from '../../../inlineChat/node/codeContextRegion';
17
import { IDocumentContext } from '../../../prompt/node/documentContext';
18
19
/**
20
* Get the lines in the selection, and lines above and below the selection.
21
* Gives preference to lines above the selection.
22
* Limits the above/below context to 100 lines.
23
* Limits the total char count to 1/3rd of the max tokens size.
24
*
25
* @param range selection range expanded to the encompassing function(s) but with line limit
26
*/
27
export function getSelectionAndCodeAroundSelection(
28
document: TextDocumentSnapshot,
29
selection: vscode.Range,
30
range: vscode.Range,
31
limitRange: vscode.Range,
32
language: ILanguage,
33
tracker: CodeContextTracker
34
): {
35
language: ILanguage;
36
above: CodeContextRegion;
37
range: CodeContextRegion;
38
below: CodeContextRegion;
39
} {
40
41
if (range.start.line !== range.end.line && range.end.character === 0) {
42
// The range ends at the start of a line, we don't need to include that EOL char
43
const lastLine = document.lineAt(range.end.line - 1);
44
range = new Range(range.start, new Position(range.end.line - 1, lastLine.text.length));
45
} else if (
46
selection.end.character === 0
47
&& selection.end.line !== selection.start.line
48
&& (
49
(range.start.line === selection.start.line
50
&& range.start.character === 0
51
&& range.end.line === selection.end.line
52
&& range.end.character === document.lineAt(range.end.line).text.length
53
)
54
||
55
(range.isEqual(selection))
56
)
57
) {
58
// The selection ends at the start of a line, we don't need to include that line
59
// The range was computed from the selection, expanding it
60
const lastLine = document.lineAt(range.end.line - 1);
61
range = new Range(range.start, new Position(range.end.line - 1, lastLine.text.length));
62
}
63
64
const rangeInfo = new CodeContextRegion(tracker, document, language);
65
const aboveInfo = new CodeContextRegion(tracker, document, language);
66
const belowInfo = new CodeContextRegion(tracker, document, language);
67
68
const finish = () => {
69
aboveInfo.trim();
70
rangeInfo.trim(selection);
71
belowInfo.trim();
72
73
return { language, above: aboveInfo, range: rangeInfo, below: belowInfo };
74
};
75
76
// the selection might not fit, so we iterate from its bottom
77
for (let lineIndex = range.end.line; lineIndex >= range.start.line; lineIndex--) {
78
if (!rangeInfo.prependLine(lineIndex)) {
79
// didn't fit
80
return finish();
81
}
82
}
83
84
const constraints = {
85
aboveLineIndex: range.start.line - 1,
86
belowLineIndex: range.end.line + 1,
87
minimumLineIndex: Math.max(0, limitRange.start.line),
88
maximumLineIndex: Math.min(document.lineCount - 1, limitRange.end.line)
89
};
90
91
processCodeAroundSelection(constraints, aboveInfo, belowInfo);
92
93
return finish();
94
}
95
96
export function processCodeAroundSelection(
97
constraints: { aboveLineIndex: number; belowLineIndex: number; minimumLineIndex: number; maximumLineIndex: number },
98
aboveInfo: CodeContextRegion,
99
belowInfo: CodeContextRegion
100
) {
101
102
let aboveLineIndex = constraints.aboveLineIndex;
103
let canGoAbove = true;
104
let belowLineIndex = constraints.belowLineIndex;
105
let canGoBelow = true;
106
for (let step = 0; step < 100 && (canGoAbove || canGoBelow); step++) {
107
// For each line below the selection, we add 3 lines above it
108
const goBelow = !canGoAbove || (canGoBelow && step % 4 === 3);
109
110
if (goBelow) {
111
// add line from below
112
if (belowLineIndex <= constraints.maximumLineIndex && belowInfo.appendLine(belowLineIndex)) {
113
belowLineIndex++;
114
} else {
115
canGoBelow = false;
116
}
117
} else {
118
// add a line from above
119
if (aboveLineIndex >= constraints.minimumLineIndex && aboveInfo.prependLine(aboveLineIndex)) {
120
aboveLineIndex--;
121
} else {
122
canGoAbove = false;
123
}
124
}
125
}
126
aboveInfo.isComplete = aboveLineIndex < constraints.minimumLineIndex; // all lines above are included
127
belowInfo.isComplete = belowLineIndex > constraints.maximumLineIndex; // all lines below are included
128
}
129
130
export function removeBodiesOutsideRange(
131
src: string,
132
functionBodies: TreeSitterOffsetRange[],
133
rangeToMaintain: { startOffset: number; endOffset: number },
134
replaceBodyWith: string
135
): { outlineAbove: string; outlineBelow: string } {
136
// remove nodes that are outside the range `rangeToMaintain`
137
// by copying undeleted chunks of `src` into `above` and `below`
138
// depending on position of deleted chunk relative to `rangeToMaintain`
139
140
let lastOffsetAbove = 0;
141
let outlineAbove = '';
142
143
let lastOffsetBelow = rangeToMaintain.endOffset;
144
let outlineBelow = '';
145
146
for (const rangeToDelete of functionBodies) {
147
if (rangeToDelete.endIndex < rangeToMaintain.startOffset) {
148
// range is above - delete
149
150
outlineAbove += src.substring(lastOffsetAbove, rangeToDelete.startIndex);
151
outlineAbove += replaceBodyWith;
152
lastOffsetAbove = rangeToDelete.endIndex;
153
} else if (rangeToDelete.startIndex > rangeToMaintain.endOffset) {
154
// range is below - delete
155
156
outlineBelow += src.substring(lastOffsetBelow, rangeToDelete.startIndex);
157
outlineBelow += replaceBodyWith;
158
lastOffsetBelow = rangeToDelete.endIndex;
159
} else {
160
// intersection - do not delete
161
continue;
162
}
163
}
164
165
outlineAbove += src.substring(lastOffsetAbove, rangeToMaintain.startOffset);
166
outlineBelow += src.substring(lastOffsetBelow, src.length);
167
168
return { outlineAbove, outlineBelow };
169
}
170
171
export function generateNotebookCellContext(
172
tabAndEditorService: ITabsAndEditorsService,
173
workspaceService: IWorkspaceService,
174
documentContext: IDocumentContext,
175
initialContext: { language: ILanguage; above: CodeContextRegion; range: CodeContextRegion; below: CodeContextRegion },
176
initialTracker: CodeContextTracker
177
): {
178
language: ILanguage;
179
aboveCells: CodeContextRegion[];
180
belowCells: CodeContextRegion[];
181
} {
182
const emptyContext = {
183
...initialContext,
184
aboveCells: [],
185
belowCells: [],
186
};
187
let notebook: vscode.NotebookDocument | undefined;
188
let aboveCellIndex: number | undefined;
189
let belowCellIndex: number | undefined;
190
191
if (documentContext.document.uri.scheme === Schemas.vscodeNotebookCell) {
192
// inline
193
notebook = findNotebook(documentContext.document.uri, workspaceService.notebookDocuments);
194
195
const cellIndex = notebook && findCell(documentContext.document.uri, notebook)?.index;
196
197
if (cellIndex === undefined || cellIndex === -1) {
198
return emptyContext;
199
}
200
201
aboveCellIndex = cellIndex - 1;
202
belowCellIndex = cellIndex + 1;
203
} else {
204
// floating widget
205
if (tabAndEditorService.activeNotebookEditor?.notebook.uri.path !== documentContext.document.uri.path) {
206
return emptyContext;
207
}
208
209
const notebookEditor = tabAndEditorService.activeNotebookEditor;
210
notebook = notebookEditor?.notebook;
211
const insertIndex = notebookEditor.selection.start;
212
aboveCellIndex = insertIndex - 1;
213
belowCellIndex = insertIndex;
214
}
215
216
if (!notebook) {
217
return emptyContext;
218
}
219
220
const { language, above: aboveInfo, range: rangeInfo, below: belowInfo } = initialContext;
221
const usedSteps = aboveInfo.lines.length + rangeInfo.lines.length + belowInfo.lines.length;
222
const aboveCells: CodeContextRegion[] = [];
223
const belowCells: CodeContextRegion[] = [];
224
225
const finish = () => {
226
aboveCells.forEach(cell => cell.trim());
227
belowCells.forEach(cell => cell.trim());
228
229
return {
230
language,
231
aboveCells,
232
belowCells,
233
};
234
};
235
236
let canGoAboveCell = true;
237
let canGoBelowCell = true;
238
239
for (let step = usedSteps; step < 100 && (canGoAboveCell || canGoBelowCell); step++) {
240
if (canGoAboveCell) {
241
// add lines from above cell is always preferred over cells below
242
if (aboveCellIndex >= 0) {
243
// prepend the cell content
244
const cell = notebook.cellAt(aboveCellIndex);
245
const _cellDocument = cell.document;
246
const cellDocument = TextDocumentSnapshot.create(_cellDocument);
247
const cellContextRegion = new CodeContextRegion(
248
initialTracker,
249
cellDocument,
250
getLanguage(cellDocument)
251
);
252
for (let i = 0; i < cellDocument.lineCount; i++) {
253
cellContextRegion.appendLine(i);
254
}
255
aboveCells.unshift(cellContextRegion);
256
257
aboveCellIndex--;
258
} else {
259
canGoAboveCell = false;
260
}
261
} else {
262
// add lines from below cell
263
if (belowCellIndex < notebook.cellCount) {
264
// append the cell content
265
const cell = notebook.cellAt(belowCellIndex);
266
const _cellDocument = cell.document;
267
const cellDocument = TextDocumentSnapshot.create(_cellDocument);
268
269
const cellContextRegion = new CodeContextRegion(
270
initialTracker,
271
cellDocument,
272
getLanguage(cellDocument)
273
);
274
for (let i = 0; i < cellDocument.lineCount; i++) {
275
cellContextRegion.appendLine(i);
276
}
277
belowCells.push(cellContextRegion);
278
279
belowCellIndex++;
280
} else {
281
canGoBelowCell = false;
282
}
283
}
284
}
285
286
return finish();
287
}
288
289