Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/sessions/contrib/agentFeedback/browser/agentFeedbackEditorUtils.ts
13401 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 { URI } from '../../../../base/common/uri.js';
7
import { isEqual } from '../../../../base/common/resources.js';
8
import { ICodeEditor, IDiffEditor } from '../../../../editor/browser/editorBrowser.js';
9
import { ICodeEditorService } from '../../../../editor/browser/services/codeEditorService.js';
10
import { IRange } from '../../../../editor/common/core/range.js';
11
import { DetailedLineRangeMapping } from '../../../../editor/common/diff/rangeMapping.js';
12
import { EditorResourceAccessor, SideBySideEditor } from '../../../../workbench/common/editor.js';
13
import { IChatEditingService } from '../../../../workbench/contrib/chat/common/editing/chatEditingService.js';
14
import { editingEntriesContainResource } from '../../../../workbench/contrib/chat/browser/sessionResourceMatching.js';
15
import { isIChatSessionFileChange2 } from '../../../../workbench/contrib/chat/common/chatSessionsService.js';
16
import { ISessionsManagementService } from '../../../services/sessions/common/sessionsManagement.js';
17
import { MultiDiffEditorInput } from '../../../../workbench/contrib/multiDiffEditor/browser/multiDiffEditorInput.js';
18
import { ISessionFileChange } from '../../../services/sessions/common/session.js';
19
20
/**
21
* Find the session that contains the given resource by checking editing sessions,
22
* sessions providers, and agent sessions.
23
*/
24
export function getSessionForResource(
25
resourceUri: URI,
26
chatEditingService: IChatEditingService,
27
sessionsManagementService: ISessionsManagementService,
28
): URI | undefined {
29
for (const editingSession of chatEditingService.editingSessionsObs.get()) {
30
if (editingEntriesContainResource(editingSession.entries.get(), resourceUri)) {
31
return editingSession.chatSessionResource;
32
}
33
}
34
for (const session of sessionsManagementService.getSessions()) {
35
const changes = session.changes.get();
36
if (changes.some(change => changeMatchesResource(change, resourceUri))) {
37
return session.resource;
38
}
39
}
40
41
return undefined;
42
}
43
44
export interface IAgentFeedbackContext {
45
readonly codeSelection?: string;
46
readonly diffHunks?: string;
47
}
48
49
export function changeMatchesResource(change: ISessionFileChange, resourceUri: URI): boolean {
50
if (isIChatSessionFileChange2(change)) {
51
return isEqual(change.uri, resourceUri)
52
|| isEqual(change.modifiedUri, resourceUri)
53
|| isEqual(change.originalUri, resourceUri);
54
}
55
56
return isEqual(change.modifiedUri, resourceUri)
57
|| isEqual(change.originalUri, resourceUri);
58
}
59
60
export function getSessionChangeForResource(
61
sessionResource: URI | undefined,
62
resourceUri: URI,
63
sessionsManagementService: ISessionsManagementService,
64
): ISessionFileChange | undefined {
65
if (!sessionResource) {
66
return undefined;
67
}
68
69
const sessionData = sessionsManagementService.getSession(sessionResource);
70
if (sessionData) {
71
const changes = sessionData.changes.get();
72
return changes.find(change => changeMatchesResource(change, resourceUri));
73
}
74
75
return undefined;
76
}
77
78
export function createAgentFeedbackContext(
79
editor: ICodeEditor,
80
codeEditorService: ICodeEditorService,
81
resourceUri: URI,
82
range: IRange,
83
): IAgentFeedbackContext {
84
const codeSelection = getCodeSelection(editor, codeEditorService, resourceUri, range);
85
const diffHunks = getDiffHunks(editor, codeEditorService, resourceUri, range);
86
return { codeSelection, diffHunks };
87
}
88
89
function getCodeSelection(
90
editor: ICodeEditor,
91
codeEditorService: ICodeEditorService,
92
resourceUri: URI,
93
range: IRange,
94
): string | undefined {
95
const model = getModelForResource(editor, codeEditorService, resourceUri);
96
if (!model) {
97
return undefined;
98
}
99
100
const selection = model.getValueInRange(range);
101
return selection.length > 0 ? selection : undefined;
102
}
103
104
function getDiffHunks(
105
editor: ICodeEditor,
106
codeEditorService: ICodeEditorService,
107
resourceUri: URI,
108
range: IRange,
109
): string | undefined {
110
const diffEditor = getContainingDiffEditor(editor, codeEditorService);
111
if (!diffEditor) {
112
return undefined;
113
}
114
115
const originalModel = diffEditor.getOriginalEditor().getModel();
116
const modifiedModel = diffEditor.getModifiedEditor().getModel();
117
if (!originalModel || !modifiedModel) {
118
return undefined;
119
}
120
121
const selectionIsInOriginal = isEqual(resourceUri, originalModel.uri);
122
const selectionIsInModified = isEqual(resourceUri, modifiedModel.uri);
123
if (!selectionIsInOriginal && !selectionIsInModified) {
124
return undefined;
125
}
126
127
const diffResult = diffEditor.getDiffComputationResult();
128
if (!diffResult) {
129
return undefined;
130
}
131
132
const selectionIsEmpty = range.startLineNumber === range.endLineNumber && range.startColumn === range.endColumn;
133
const relevantGroups = groupChanges(diffResult.changes2).filter(group => {
134
const changeTouchesSelection = (change: DetailedLineRangeMapping) => rangeTouchesChange(range, selectionIsInOriginal ? change.original : change.modified);
135
return selectionIsEmpty ? group.some(changeTouchesSelection) : group.every(changeTouchesSelection);
136
});
137
if (relevantGroups.length === 0) {
138
return undefined;
139
}
140
141
const originalText = originalModel.getValue();
142
const modifiedText = modifiedModel.getValue();
143
const originalEndsWithNewline = originalText.length > 0 && originalText.endsWith('\n');
144
const modifiedEndsWithNewline = modifiedText.length > 0 && modifiedText.endsWith('\n');
145
const originalLines = originalText.split('\n');
146
const modifiedLines = modifiedText.split('\n');
147
148
if (originalEndsWithNewline && originalLines[originalLines.length - 1] === '') {
149
originalLines.pop();
150
}
151
if (modifiedEndsWithNewline && modifiedLines[modifiedLines.length - 1] === '') {
152
modifiedLines.pop();
153
}
154
155
return relevantGroups.map(group => renderHunkGroup(group, originalLines, modifiedLines, originalEndsWithNewline, modifiedEndsWithNewline)).join('\n');
156
}
157
158
function getContainingDiffEditor(editor: ICodeEditor, codeEditorService: ICodeEditorService): IDiffEditor | undefined {
159
return codeEditorService.listDiffEditors().find(diffEditor =>
160
diffEditor.getModifiedEditor() === editor || diffEditor.getOriginalEditor() === editor
161
);
162
}
163
164
function getModelForResource(editor: ICodeEditor, codeEditorService: ICodeEditorService, resourceUri: URI) {
165
const currentModel = editor.getModel();
166
if (currentModel && isEqual(currentModel.uri, resourceUri)) {
167
return currentModel;
168
}
169
170
const diffEditor = getContainingDiffEditor(editor, codeEditorService);
171
const originalModel = diffEditor?.getOriginalEditor().getModel();
172
if (originalModel && isEqual(originalModel.uri, resourceUri)) {
173
return originalModel;
174
}
175
176
const modifiedModel = diffEditor?.getModifiedEditor().getModel();
177
if (modifiedModel && isEqual(modifiedModel.uri, resourceUri)) {
178
return modifiedModel;
179
}
180
181
return undefined;
182
}
183
184
function groupChanges(changes: readonly DetailedLineRangeMapping[]): DetailedLineRangeMapping[][] {
185
const contextSize = 3;
186
const groups: DetailedLineRangeMapping[][] = [];
187
let currentGroup: DetailedLineRangeMapping[] = [];
188
189
for (const change of changes) {
190
if (currentGroup.length === 0) {
191
currentGroup.push(change);
192
continue;
193
}
194
195
const lastChange = currentGroup[currentGroup.length - 1];
196
const lastContextEnd = lastChange.original.endLineNumberExclusive - 1 + contextSize;
197
const currentContextStart = change.original.startLineNumber - contextSize;
198
if (currentContextStart <= lastContextEnd + 1) {
199
currentGroup.push(change);
200
} else {
201
groups.push(currentGroup);
202
currentGroup = [change];
203
}
204
}
205
206
if (currentGroup.length > 0) {
207
groups.push(currentGroup);
208
}
209
210
return groups;
211
}
212
213
function rangeTouchesChange(
214
range: IRange,
215
lineRange: { startLineNumber: number; endLineNumberExclusive: number; isEmpty: boolean; contains(lineNumber: number): boolean },
216
): boolean {
217
const isEmptySelection = range.startLineNumber === range.endLineNumber && range.startColumn === range.endColumn;
218
if (isEmptySelection) {
219
return !lineRange.isEmpty && lineRange.contains(range.startLineNumber);
220
}
221
222
const selectionStart = range.startLineNumber;
223
const selectionEndExclusive = range.endLineNumber + 1;
224
return selectionStart <= lineRange.startLineNumber && lineRange.endLineNumberExclusive <= selectionEndExclusive;
225
}
226
227
function renderHunkGroup(
228
group: readonly DetailedLineRangeMapping[],
229
originalLines: string[],
230
modifiedLines: string[],
231
originalEndsWithNewline: boolean,
232
modifiedEndsWithNewline: boolean,
233
): string {
234
const contextSize = 3;
235
const firstChange = group[0];
236
const lastChange = group[group.length - 1];
237
const hunkOrigStart = Math.max(1, firstChange.original.startLineNumber - contextSize);
238
const hunkOrigEnd = Math.min(originalLines.length, lastChange.original.endLineNumberExclusive - 1 + contextSize);
239
const hunkModStart = Math.max(1, firstChange.modified.startLineNumber - contextSize);
240
241
const hunkLines: string[] = [];
242
let lastOriginalLineIndex = -1;
243
let lastModifiedLineIndex = -1;
244
let origLineNum = hunkOrigStart;
245
let origCount = 0;
246
let modCount = 0;
247
248
for (const change of group) {
249
const origStart = change.original.startLineNumber;
250
const origEnd = change.original.endLineNumberExclusive;
251
const modStart = change.modified.startLineNumber;
252
const modEnd = change.modified.endLineNumberExclusive;
253
254
while (origLineNum < origStart) {
255
const idx = hunkLines.length;
256
hunkLines.push(` ${originalLines[origLineNum - 1]}`);
257
if (origLineNum === originalLines.length) {
258
lastOriginalLineIndex = idx;
259
}
260
const modLineNum = hunkModStart + modCount;
261
if (modLineNum === modifiedLines.length) {
262
lastModifiedLineIndex = idx;
263
}
264
origLineNum++;
265
origCount++;
266
modCount++;
267
}
268
269
for (let i = origStart; i < origEnd; i++) {
270
const idx = hunkLines.length;
271
hunkLines.push(`-${originalLines[i - 1]}`);
272
if (i === originalLines.length) {
273
lastOriginalLineIndex = idx;
274
}
275
origLineNum++;
276
origCount++;
277
}
278
279
for (let i = modStart; i < modEnd; i++) {
280
const idx = hunkLines.length;
281
hunkLines.push(`+${modifiedLines[i - 1]}`);
282
if (i === modifiedLines.length) {
283
lastModifiedLineIndex = idx;
284
}
285
modCount++;
286
}
287
}
288
289
while (origLineNum <= hunkOrigEnd) {
290
const idx = hunkLines.length;
291
hunkLines.push(` ${originalLines[origLineNum - 1]}`);
292
if (origLineNum === originalLines.length) {
293
lastOriginalLineIndex = idx;
294
}
295
const modLineNum = hunkModStart + modCount;
296
if (modLineNum === modifiedLines.length) {
297
lastModifiedLineIndex = idx;
298
}
299
origLineNum++;
300
origCount++;
301
modCount++;
302
}
303
304
const header = `@@ -${hunkOrigStart},${origCount} +${hunkModStart},${modCount} @@`;
305
const result = [header, ...hunkLines];
306
307
if (!originalEndsWithNewline && lastOriginalLineIndex >= 0) {
308
result.splice(lastOriginalLineIndex + 2, 0, '\\ No newline at end of file');
309
} else if (!modifiedEndsWithNewline && lastModifiedLineIndex >= 0) {
310
result.splice(lastModifiedLineIndex + 2, 0, '\\ No newline at end of file');
311
}
312
313
return result.join('\n');
314
}
315
316
export function getActiveResourceCandidates(input: Parameters<typeof EditorResourceAccessor.getOriginalUri>[0]): URI[] {
317
const result: URI[] = [];
318
319
if (input instanceof MultiDiffEditorInput) {
320
const items = input.resources.get();
321
if (items) {
322
for (const item of items) {
323
if (item.originalUri) { result.push(item.originalUri); }
324
if (item.modifiedUri) { result.push(item.modifiedUri); }
325
}
326
}
327
return result;
328
}
329
330
const resources = EditorResourceAccessor.getOriginalUri(input, { supportSideBySide: SideBySideEditor.BOTH });
331
if (!resources) {
332
return result;
333
}
334
335
if (URI.isUri(resources)) {
336
result.push(resources);
337
return result;
338
}
339
340
if (resources.secondary) {
341
result.push(resources.secondary);
342
}
343
if (resources.primary) {
344
result.push(resources.primary);
345
}
346
347
return result;
348
}
349
350