Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatSession.test.ts
3297 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
import assert from 'assert';
6
import { DisposableStore } from '../../../../../base/common/lifecycle.js';
7
import { Event } from '../../../../../base/common/event.js';
8
import { mock } from '../../../../../base/test/common/mock.js';
9
import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../base/test/common/utils.js';
10
import { TestDiffProviderFactoryService } from '../../../../../editor/test/browser/diff/testDiffProviderFactoryService.js';
11
import { IActiveCodeEditor } from '../../../../../editor/browser/editorBrowser.js';
12
import { IDiffProviderFactoryService } from '../../../../../editor/browser/widget/diffEditor/diffProviderFactoryService.js';
13
import { Range } from '../../../../../editor/common/core/range.js';
14
import { ITextModel } from '../../../../../editor/common/model.js';
15
import { IModelService } from '../../../../../editor/common/services/model.js';
16
import { instantiateTestCodeEditor } from '../../../../../editor/test/browser/testCodeEditor.js';
17
import { IConfigurationService } from '../../../../../platform/configuration/common/configuration.js';
18
import { TestConfigurationService } from '../../../../../platform/configuration/test/common/testConfigurationService.js';
19
import { IContextKeyService } from '../../../../../platform/contextkey/common/contextkey.js';
20
import { SyncDescriptor } from '../../../../../platform/instantiation/common/descriptors.js';
21
import { ServiceCollection } from '../../../../../platform/instantiation/common/serviceCollection.js';
22
import { TestInstantiationService } from '../../../../../platform/instantiation/test/common/instantiationServiceMock.js';
23
import { MockContextKeyService } from '../../../../../platform/keybinding/test/common/mockKeybindingService.js';
24
import { IEditorProgressService, IProgressRunner } from '../../../../../platform/progress/common/progress.js';
25
import { IViewDescriptorService } from '../../../../common/views.js';
26
import { AccessibilityVerbositySettingId } from '../../../accessibility/browser/accessibilityConfiguration.js';
27
import { IChatAccessibilityService, IChatWidgetService } from '../../../chat/browser/chat.js';
28
import { IChatResponseViewModel } from '../../../chat/common/chatViewModel.js';
29
import { HunkState } from '../../browser/inlineChatSession.js';
30
import { IInlineChatSessionService } from '../../browser/inlineChatSessionService.js';
31
import { InlineChatSessionServiceImpl } from '../../browser/inlineChatSessionServiceImpl.js';
32
import { workbenchInstantiationService } from '../../../../test/browser/workbenchTestServices.js';
33
import { CancellationToken } from '../../../../../base/common/cancellation.js';
34
import { assertType } from '../../../../../base/common/types.js';
35
import { EditOperation } from '../../../../../editor/common/core/editOperation.js';
36
import { Position } from '../../../../../editor/common/core/position.js';
37
import { IEditorWorkerService } from '../../../../../editor/common/services/editorWorker.js';
38
import { TestWorkerService } from './testWorkerService.js';
39
import { IExtensionService, nullExtensionDescription } from '../../../../services/extensions/common/extensions.js';
40
import { ILogService, NullLogService } from '../../../../../platform/log/common/log.js';
41
import { ITelemetryService } from '../../../../../platform/telemetry/common/telemetry.js';
42
import { NullTelemetryService } from '../../../../../platform/telemetry/common/telemetryUtils.js';
43
import { IWorkspaceContextService } from '../../../../../platform/workspace/common/workspace.js';
44
import { ChatWidgetService } from '../../../chat/browser/chatWidget.js';
45
import { IChatService } from '../../../chat/common/chatService.js';
46
import { ChatService } from '../../../chat/common/chatServiceImpl.js';
47
import { IChatSlashCommandService, ChatSlashCommandService } from '../../../chat/common/chatSlashCommands.js';
48
import { IChatVariablesService } from '../../../chat/common/chatVariables.js';
49
import { IChatWidgetHistoryService, ChatWidgetHistoryService } from '../../../chat/common/chatWidgetHistoryService.js';
50
import { IViewsService } from '../../../../services/views/common/viewsService.js';
51
import { TestExtensionService, TestContextService } from '../../../../test/common/workbenchTestServices.js';
52
import { IChatAgentService, ChatAgentService } from '../../../chat/common/chatAgents.js';
53
import { ChatVariablesService } from '../../../chat/browser/chatVariables.js';
54
import { ICommandService } from '../../../../../platform/commands/common/commands.js';
55
import { TestCommandService } from '../../../../../editor/test/browser/editorTestServices.js';
56
import { IAccessibleViewService } from '../../../../../platform/accessibility/browser/accessibleView.js';
57
import { IWorkbenchAssignmentService } from '../../../../services/assignment/common/assignmentService.js';
58
import { NullWorkbenchAssignmentService } from '../../../../services/assignment/test/common/nullAssignmentService.js';
59
import { ILanguageModelToolsService } from '../../../chat/common/languageModelToolsService.js';
60
import { MockLanguageModelToolsService } from '../../../chat/test/common/mockLanguageModelToolsService.js';
61
import { IChatRequestModel } from '../../../chat/common/chatModel.js';
62
import { assertSnapshot } from '../../../../../base/test/common/snapshot.js';
63
import { IObservable, constObservable } from '../../../../../base/common/observable.js';
64
import { IChatEditingService, IChatEditingSession } from '../../../chat/common/chatEditingService.js';
65
import { ChatAgentLocation, ChatModeKind } from '../../../chat/common/constants.js';
66
import { ChatTransferService, IChatTransferService } from '../../../chat/common/chatTransferService.js';
67
import { NullLanguageModelsService } from '../../../chat/test/common/languageModels.js';
68
import { ILanguageModelsService } from '../../../chat/common/languageModels.js';
69
import { IMcpService } from '../../../mcp/common/mcpTypes.js';
70
import { TestMcpService } from '../../../mcp/test/common/testMcpService.js';
71
import { IChatSessionsService } from '../../../chat/common/chatSessionsService.js';
72
import { ChatSessionsService } from '../../../chat/browser/chatSessions.contribution.js';
73
74
suite('InlineChatSession', function () {
75
76
const store = new DisposableStore();
77
let editor: IActiveCodeEditor;
78
let model: ITextModel;
79
let instaService: TestInstantiationService;
80
81
let inlineChatSessionService: IInlineChatSessionService;
82
83
setup(function () {
84
const contextKeyService = new MockContextKeyService();
85
86
87
const serviceCollection = new ServiceCollection(
88
[IConfigurationService, new TestConfigurationService()],
89
[IChatVariablesService, new SyncDescriptor(ChatVariablesService)],
90
[ILogService, new NullLogService()],
91
[ITelemetryService, NullTelemetryService],
92
[IExtensionService, new TestExtensionService()],
93
[IContextKeyService, new MockContextKeyService()],
94
[IViewsService, new TestExtensionService()],
95
[IWorkspaceContextService, new TestContextService()],
96
[IChatWidgetHistoryService, new SyncDescriptor(ChatWidgetHistoryService)],
97
[IChatWidgetService, new SyncDescriptor(ChatWidgetService)],
98
[IChatSlashCommandService, new SyncDescriptor(ChatSlashCommandService)],
99
[IChatTransferService, new SyncDescriptor(ChatTransferService)],
100
[IChatSessionsService, new SyncDescriptor(ChatSessionsService)],
101
[IChatService, new SyncDescriptor(ChatService)],
102
[IEditorWorkerService, new SyncDescriptor(TestWorkerService)],
103
[IChatAgentService, new SyncDescriptor(ChatAgentService)],
104
[IContextKeyService, contextKeyService],
105
[IDiffProviderFactoryService, new SyncDescriptor(TestDiffProviderFactoryService)],
106
[ILanguageModelsService, new SyncDescriptor(NullLanguageModelsService)],
107
[IInlineChatSessionService, new SyncDescriptor(InlineChatSessionServiceImpl)],
108
[ICommandService, new SyncDescriptor(TestCommandService)],
109
[ILanguageModelToolsService, new MockLanguageModelToolsService()],
110
[IMcpService, new TestMcpService()],
111
[IEditorProgressService, new class extends mock<IEditorProgressService>() {
112
override show(total: unknown, delay?: unknown): IProgressRunner {
113
return {
114
total() { },
115
worked(value) { },
116
done() { },
117
};
118
}
119
}],
120
[IChatEditingService, new class extends mock<IChatEditingService>() {
121
override editingSessionsObs: IObservable<readonly IChatEditingSession[]> = constObservable([]);
122
}],
123
[IChatAccessibilityService, new class extends mock<IChatAccessibilityService>() {
124
override acceptResponse(response: IChatResponseViewModel | undefined, requestId: number): void { }
125
override acceptRequest(): number { return -1; }
126
override acceptElicitation(): void { }
127
}],
128
[IAccessibleViewService, new class extends mock<IAccessibleViewService>() {
129
override getOpenAriaHint(verbositySettingKey: AccessibilityVerbositySettingId): string | null {
130
return null;
131
}
132
}],
133
[IConfigurationService, new TestConfigurationService()],
134
[IViewDescriptorService, new class extends mock<IViewDescriptorService>() {
135
override onDidChangeLocation = Event.None;
136
}],
137
[IWorkbenchAssignmentService, new NullWorkbenchAssignmentService()]
138
);
139
140
141
142
instaService = store.add(workbenchInstantiationService(undefined, store).createChild(serviceCollection));
143
inlineChatSessionService = store.add(instaService.get(IInlineChatSessionService));
144
store.add(instaService.get(IChatSessionsService) as ChatSessionsService); // Needs to be disposed in between test runs to clear extensionPoint contribution
145
146
instaService.get(IChatAgentService).registerDynamicAgent({
147
extensionId: nullExtensionDescription.identifier,
148
extensionVersion: undefined,
149
publisherDisplayName: '',
150
extensionDisplayName: '',
151
extensionPublisherId: '',
152
id: 'testAgent',
153
name: 'testAgent',
154
isDefault: true,
155
locations: [ChatAgentLocation.Editor],
156
modes: [ChatModeKind.Ask],
157
metadata: {},
158
slashCommands: [],
159
disambiguation: [],
160
}, {
161
async invoke() {
162
return {};
163
}
164
});
165
166
167
store.add(instaService.get(IEditorWorkerService) as TestWorkerService);
168
model = store.add(instaService.get(IModelService).createModel('one\ntwo\nthree\nfour\nfive\nsix\nseven\neight\nnine\nten\neleven', null));
169
editor = store.add(instantiateTestCodeEditor(instaService, model));
170
});
171
172
teardown(function () {
173
store.clear();
174
});
175
176
ensureNoDisposablesAreLeakedInTestSuite();
177
178
async function makeEditAsAi(edit: EditOperation | EditOperation[]) {
179
const session = inlineChatSessionService.getSession(editor, editor.getModel()!.uri);
180
assertType(session);
181
session.hunkData.ignoreTextModelNChanges = true;
182
try {
183
editor.executeEdits('test', Array.isArray(edit) ? edit : [edit]);
184
} finally {
185
session.hunkData.ignoreTextModelNChanges = false;
186
}
187
await session.hunkData.recompute({ applied: 0, sha1: 'fakeSha1' });
188
}
189
190
function makeEdit(edit: EditOperation | EditOperation[]) {
191
editor.executeEdits('test', Array.isArray(edit) ? edit : [edit]);
192
}
193
194
test('Create, release', async function () {
195
196
const session = await inlineChatSessionService.createSession(editor, {}, CancellationToken.None);
197
assertType(session);
198
inlineChatSessionService.releaseSession(session);
199
});
200
201
test('HunkData, info', async function () {
202
203
const decorationCountThen = model.getAllDecorations().length;
204
205
const session = await inlineChatSessionService.createSession(editor, {}, CancellationToken.None);
206
assertType(session);
207
assert.ok(session.textModelN === model);
208
209
await makeEditAsAi(EditOperation.insert(new Position(1, 1), 'AI_EDIT\n'));
210
211
212
assert.strictEqual(session.hunkData.size, 1);
213
let [hunk] = session.hunkData.getInfo();
214
assertType(hunk);
215
216
assert.ok(!session.textModel0.equalsTextBuffer(session.textModelN.getTextBuffer()));
217
assert.strictEqual(hunk.getState(), HunkState.Pending);
218
assert.ok(hunk.getRangesN()[0].equalsRange({ startLineNumber: 1, startColumn: 1, endLineNumber: 1, endColumn: 8 }));
219
220
await makeEditAsAi(EditOperation.insert(new Position(1, 3), 'foobar'));
221
[hunk] = session.hunkData.getInfo();
222
assert.ok(hunk.getRangesN()[0].equalsRange({ startLineNumber: 1, startColumn: 1, endLineNumber: 1, endColumn: 14 }));
223
224
inlineChatSessionService.releaseSession(session);
225
226
assert.strictEqual(model.getAllDecorations().length, decorationCountThen); // no leaked decorations!
227
});
228
229
test('HunkData, accept', async function () {
230
231
const session = await inlineChatSessionService.createSession(editor, {}, CancellationToken.None);
232
assertType(session);
233
234
await makeEditAsAi([EditOperation.insert(new Position(1, 1), 'AI_EDIT\n'), EditOperation.insert(new Position(10, 1), 'AI_EDIT\n')]);
235
236
assert.strictEqual(session.hunkData.size, 2);
237
assert.ok(!session.textModel0.equalsTextBuffer(session.textModelN.getTextBuffer()));
238
239
for (const hunk of session.hunkData.getInfo()) {
240
assertType(hunk);
241
assert.strictEqual(hunk.getState(), HunkState.Pending);
242
hunk.acceptChanges();
243
assert.strictEqual(hunk.getState(), HunkState.Accepted);
244
}
245
246
assert.strictEqual(session.textModel0.getValue(), session.textModelN.getValue());
247
inlineChatSessionService.releaseSession(session);
248
});
249
250
test('HunkData, reject', async function () {
251
252
const session = await inlineChatSessionService.createSession(editor, {}, CancellationToken.None);
253
assertType(session);
254
255
await makeEditAsAi([EditOperation.insert(new Position(1, 1), 'AI_EDIT\n'), EditOperation.insert(new Position(10, 1), 'AI_EDIT\n')]);
256
257
assert.strictEqual(session.hunkData.size, 2);
258
assert.ok(!session.textModel0.equalsTextBuffer(session.textModelN.getTextBuffer()));
259
260
for (const hunk of session.hunkData.getInfo()) {
261
assertType(hunk);
262
assert.strictEqual(hunk.getState(), HunkState.Pending);
263
hunk.discardChanges();
264
assert.strictEqual(hunk.getState(), HunkState.Rejected);
265
}
266
267
assert.strictEqual(session.textModel0.getValue(), session.textModelN.getValue());
268
inlineChatSessionService.releaseSession(session);
269
});
270
271
test('HunkData, N rounds', async function () {
272
273
model.setValue('one\ntwo\nthree\nfour\nfive\nsix\nseven\neight\nnine\nten\neleven\ntwelwe\nthirteen\nfourteen\nfifteen\nsixteen\nseventeen\neighteen\nnineteen\n');
274
275
const session = await inlineChatSessionService.createSession(editor, {}, CancellationToken.None);
276
assertType(session);
277
278
assert.ok(session.textModel0.equalsTextBuffer(session.textModelN.getTextBuffer()));
279
280
assert.strictEqual(session.hunkData.size, 0);
281
282
// ROUND #1
283
await makeEditAsAi([
284
EditOperation.insert(new Position(1, 1), 'AI1'),
285
EditOperation.insert(new Position(4, 1), 'AI2'),
286
EditOperation.insert(new Position(19, 1), 'AI3')
287
]);
288
289
assert.strictEqual(session.hunkData.size, 2); // AI1, AI2 are merged into one hunk, AI3 is a separate hunk
290
291
let [first, second] = session.hunkData.getInfo();
292
293
assert.ok(model.getValueInRange(first.getRangesN()[0]).includes('AI1'));
294
assert.ok(model.getValueInRange(first.getRangesN()[0]).includes('AI2'));
295
assert.ok(model.getValueInRange(second.getRangesN()[0]).includes('AI3'));
296
297
assert.ok(!session.textModel0.getValueInRange(first.getRangesN()[0]).includes('AI1'));
298
assert.ok(!session.textModel0.getValueInRange(first.getRangesN()[0]).includes('AI2'));
299
assert.ok(!session.textModel0.getValueInRange(second.getRangesN()[0]).includes('AI3'));
300
301
first.acceptChanges();
302
assert.ok(session.textModel0.getValueInRange(first.getRangesN()[0]).includes('AI1'));
303
assert.ok(session.textModel0.getValueInRange(first.getRangesN()[0]).includes('AI2'));
304
assert.ok(!session.textModel0.getValueInRange(second.getRangesN()[0]).includes('AI3'));
305
306
307
// ROUND #2
308
await makeEditAsAi([
309
EditOperation.insert(new Position(7, 1), 'AI4'),
310
]);
311
assert.strictEqual(session.hunkData.size, 2);
312
313
[first, second] = session.hunkData.getInfo();
314
assert.ok(model.getValueInRange(first.getRangesN()[0]).includes('AI4')); // the new hunk (in line-order)
315
assert.ok(model.getValueInRange(second.getRangesN()[0]).includes('AI3')); // the previous hunk remains
316
317
inlineChatSessionService.releaseSession(session);
318
});
319
320
test('HunkData, (mirror) edit before', async function () {
321
322
const lines = ['one', 'two', 'three'];
323
model.setValue(lines.join('\n'));
324
const session = await inlineChatSessionService.createSession(editor, {}, CancellationToken.None);
325
assertType(session);
326
327
await makeEditAsAi([EditOperation.insert(new Position(3, 1), 'AI WAS HERE\n')]);
328
assert.strictEqual(session.textModelN.getValue(), ['one', 'two', 'AI WAS HERE', 'three'].join('\n'));
329
assert.strictEqual(session.textModel0.getValue(), lines.join('\n'));
330
331
makeEdit([EditOperation.replace(new Range(1, 1, 1, 4), 'ONE')]);
332
assert.strictEqual(session.textModelN.getValue(), ['ONE', 'two', 'AI WAS HERE', 'three'].join('\n'));
333
assert.strictEqual(session.textModel0.getValue(), ['ONE', 'two', 'three'].join('\n'));
334
});
335
336
test('HunkData, (mirror) edit after', async function () {
337
338
const lines = ['one', 'two', 'three', 'four', 'five'];
339
model.setValue(lines.join('\n'));
340
341
const session = await inlineChatSessionService.createSession(editor, {}, CancellationToken.None);
342
assertType(session);
343
344
await makeEditAsAi([EditOperation.insert(new Position(3, 1), 'AI_EDIT\n')]);
345
346
assert.strictEqual(session.hunkData.size, 1);
347
const [hunk] = session.hunkData.getInfo();
348
349
makeEdit([EditOperation.insert(new Position(1, 1), 'USER1')]);
350
assert.strictEqual(session.textModelN.getValue(), ['USER1one', 'two', 'AI_EDIT', 'three', 'four', 'five'].join('\n'));
351
assert.strictEqual(session.textModel0.getValue(), ['USER1one', 'two', 'three', 'four', 'five'].join('\n'));
352
353
makeEdit([EditOperation.insert(new Position(5, 1), 'USER2')]);
354
assert.strictEqual(session.textModelN.getValue(), ['USER1one', 'two', 'AI_EDIT', 'three', 'USER2four', 'five'].join('\n'));
355
assert.strictEqual(session.textModel0.getValue(), ['USER1one', 'two', 'three', 'USER2four', 'five'].join('\n'));
356
357
hunk.acceptChanges();
358
assert.strictEqual(session.textModelN.getValue(), ['USER1one', 'two', 'AI_EDIT', 'three', 'USER2four', 'five'].join('\n'));
359
assert.strictEqual(session.textModel0.getValue(), ['USER1one', 'two', 'AI_EDIT', 'three', 'USER2four', 'five'].join('\n'));
360
});
361
362
test('HunkData, (mirror) edit inside ', async function () {
363
364
const lines = ['one', 'two', 'three'];
365
model.setValue(lines.join('\n'));
366
const session = await inlineChatSessionService.createSession(editor, {}, CancellationToken.None);
367
assertType(session);
368
369
await makeEditAsAi([EditOperation.insert(new Position(3, 1), 'AI WAS HERE\n')]);
370
assert.strictEqual(session.textModelN.getValue(), ['one', 'two', 'AI WAS HERE', 'three'].join('\n'));
371
assert.strictEqual(session.textModel0.getValue(), lines.join('\n'));
372
373
makeEdit([EditOperation.replace(new Range(3, 4, 3, 7), 'wwaaassss')]);
374
assert.strictEqual(session.textModelN.getValue(), ['one', 'two', 'AI wwaaassss HERE', 'three'].join('\n'));
375
assert.strictEqual(session.textModel0.getValue(), ['one', 'two', 'three'].join('\n'));
376
});
377
378
test('HunkData, (mirror) edit after dicard ', async function () {
379
380
const lines = ['one', 'two', 'three'];
381
model.setValue(lines.join('\n'));
382
const session = await inlineChatSessionService.createSession(editor, {}, CancellationToken.None);
383
assertType(session);
384
385
await makeEditAsAi([EditOperation.insert(new Position(3, 1), 'AI WAS HERE\n')]);
386
assert.strictEqual(session.textModelN.getValue(), ['one', 'two', 'AI WAS HERE', 'three'].join('\n'));
387
assert.strictEqual(session.textModel0.getValue(), lines.join('\n'));
388
389
assert.strictEqual(session.hunkData.size, 1);
390
const [hunk] = session.hunkData.getInfo();
391
hunk.discardChanges();
392
assert.strictEqual(session.textModelN.getValue(), lines.join('\n'));
393
assert.strictEqual(session.textModel0.getValue(), lines.join('\n'));
394
395
makeEdit([EditOperation.replace(new Range(3, 4, 3, 6), '3333')]);
396
assert.strictEqual(session.textModelN.getValue(), ['one', 'two', 'thr3333'].join('\n'));
397
assert.strictEqual(session.textModel0.getValue(), ['one', 'two', 'thr3333'].join('\n'));
398
});
399
400
test('HunkData, (mirror) edit after, multi turn', async function () {
401
402
const lines = ['one', 'two', 'three', 'four', 'five'];
403
model.setValue(lines.join('\n'));
404
405
const session = await inlineChatSessionService.createSession(editor, {}, CancellationToken.None);
406
assertType(session);
407
408
await makeEditAsAi([EditOperation.insert(new Position(3, 1), 'AI_EDIT\n')]);
409
410
assert.strictEqual(session.hunkData.size, 1);
411
412
makeEdit([EditOperation.insert(new Position(5, 1), 'FOO')]);
413
assert.strictEqual(session.textModelN.getValue(), ['one', 'two', 'AI_EDIT', 'three', 'FOOfour', 'five'].join('\n'));
414
assert.strictEqual(session.textModel0.getValue(), ['one', 'two', 'three', 'FOOfour', 'five'].join('\n'));
415
416
await makeEditAsAi([EditOperation.insert(new Position(2, 4), ' zwei')]);
417
assert.strictEqual(session.hunkData.size, 1);
418
419
assert.strictEqual(session.textModelN.getValue(), ['one', 'two zwei', 'AI_EDIT', 'three', 'FOOfour', 'five'].join('\n'));
420
assert.strictEqual(session.textModel0.getValue(), ['one', 'two', 'three', 'FOOfour', 'five'].join('\n'));
421
422
makeEdit([EditOperation.replace(new Range(6, 3, 6, 5), 'vefivefi')]);
423
assert.strictEqual(session.textModelN.getValue(), ['one', 'two zwei', 'AI_EDIT', 'three', 'FOOfour', 'fivefivefi'].join('\n'));
424
assert.strictEqual(session.textModel0.getValue(), ['one', 'two', 'three', 'FOOfour', 'fivefivefi'].join('\n'));
425
});
426
427
test('HunkData, (mirror) edit after, multi turn 2', async function () {
428
429
const lines = ['one', 'two', 'three', 'four', 'five'];
430
model.setValue(lines.join('\n'));
431
432
const session = await inlineChatSessionService.createSession(editor, {}, CancellationToken.None);
433
assertType(session);
434
435
await makeEditAsAi([EditOperation.insert(new Position(3, 1), 'AI_EDIT\n')]);
436
437
assert.strictEqual(session.hunkData.size, 1);
438
439
makeEdit([EditOperation.insert(new Position(5, 1), 'FOO')]);
440
assert.strictEqual(session.textModelN.getValue(), ['one', 'two', 'AI_EDIT', 'three', 'FOOfour', 'five'].join('\n'));
441
assert.strictEqual(session.textModel0.getValue(), ['one', 'two', 'three', 'FOOfour', 'five'].join('\n'));
442
443
await makeEditAsAi([EditOperation.insert(new Position(2, 4), 'zwei')]);
444
assert.strictEqual(session.hunkData.size, 1);
445
446
assert.strictEqual(session.textModelN.getValue(), ['one', 'twozwei', 'AI_EDIT', 'three', 'FOOfour', 'five'].join('\n'));
447
assert.strictEqual(session.textModel0.getValue(), ['one', 'two', 'three', 'FOOfour', 'five'].join('\n'));
448
449
makeEdit([EditOperation.replace(new Range(6, 3, 6, 5), 'vefivefi')]);
450
assert.strictEqual(session.textModelN.getValue(), ['one', 'twozwei', 'AI_EDIT', 'three', 'FOOfour', 'fivefivefi'].join('\n'));
451
assert.strictEqual(session.textModel0.getValue(), ['one', 'two', 'three', 'FOOfour', 'fivefivefi'].join('\n'));
452
453
session.hunkData.getInfo()[0].acceptChanges();
454
assert.strictEqual(session.textModelN.getValue(), session.textModel0.getValue());
455
456
makeEdit([EditOperation.replace(new Range(1, 1, 1, 1), 'done')]);
457
assert.strictEqual(session.textModelN.getValue(), session.textModel0.getValue());
458
});
459
460
test('HunkData, accept, discardAll', async function () {
461
462
const session = await inlineChatSessionService.createSession(editor, {}, CancellationToken.None);
463
assertType(session);
464
465
await makeEditAsAi([EditOperation.insert(new Position(1, 1), 'AI_EDIT\n'), EditOperation.insert(new Position(10, 1), 'AI_EDIT\n')]);
466
467
assert.strictEqual(session.hunkData.size, 2);
468
assert.ok(!session.textModel0.equalsTextBuffer(session.textModelN.getTextBuffer()));
469
470
const textModeNNow = session.textModelN.getValue();
471
472
session.hunkData.getInfo()[0].acceptChanges();
473
assert.strictEqual(textModeNNow, session.textModelN.getValue());
474
475
session.hunkData.discardAll(); // all remaining
476
assert.strictEqual(session.textModelN.getValue(), 'AI_EDIT\none\ntwo\nthree\nfour\nfive\nsix\nseven\neight\nnine\nten\neleven');
477
assert.strictEqual(session.textModelN.getValue(), session.textModel0.getValue());
478
479
inlineChatSessionService.releaseSession(session);
480
});
481
482
test('HunkData, discardAll return undo edits', async function () {
483
484
const session = await inlineChatSessionService.createSession(editor, {}, CancellationToken.None);
485
assertType(session);
486
487
await makeEditAsAi([EditOperation.insert(new Position(1, 1), 'AI_EDIT\n'), EditOperation.insert(new Position(10, 1), 'AI_EDIT\n')]);
488
489
assert.strictEqual(session.hunkData.size, 2);
490
assert.ok(!session.textModel0.equalsTextBuffer(session.textModelN.getTextBuffer()));
491
492
const textModeNNow = session.textModelN.getValue();
493
494
session.hunkData.getInfo()[0].acceptChanges();
495
assert.strictEqual(textModeNNow, session.textModelN.getValue());
496
497
const undoEdits = session.hunkData.discardAll(); // all remaining
498
assert.strictEqual(session.textModelN.getValue(), 'AI_EDIT\none\ntwo\nthree\nfour\nfive\nsix\nseven\neight\nnine\nten\neleven');
499
assert.strictEqual(session.textModelN.getValue(), session.textModel0.getValue());
500
501
// undo the discards
502
session.textModelN.pushEditOperations(null, undoEdits, () => null);
503
assert.strictEqual(textModeNNow, session.textModelN.getValue());
504
505
inlineChatSessionService.releaseSession(session);
506
});
507
508
test('Pressing Escape after inline chat errored with "response filtered" leaves document dirty #7764', async function () {
509
510
const origValue = `class Foo {
511
private onError(error: string): void {
512
if (/The request timed out|The network connection was lost/i.test(error)) {
513
return;
514
}
515
516
error = error.replace(/See https:\/\/github\.com\/Squirrel\/Squirrel\.Mac\/issues\/182 for more information/, 'This might mean the application was put on quarantine by macOS. See [this link](https://github.com/microsoft/vscode/issues/7426#issuecomment-425093469) for more information');
517
518
this.notificationService.notify({
519
severity: Severity.Error,
520
message: error,
521
source: nls.localize('update service', "Update Service"),
522
});
523
}
524
}`;
525
model.setValue(origValue);
526
527
const session = await inlineChatSessionService.createSession(editor, {}, CancellationToken.None);
528
assertType(session);
529
530
const fakeRequest = new class extends mock<IChatRequestModel>() {
531
override get id() { return 'one'; }
532
};
533
session.markModelVersion(fakeRequest);
534
535
assert.strictEqual(editor.getModel().getLineCount(), 15);
536
537
await makeEditAsAi([EditOperation.replace(new Range(7, 1, 7, Number.MAX_SAFE_INTEGER), `error = error.replace(
538
/See https:\/\/github\.com\/Squirrel\/Squirrel\.Mac\/issues\/182 for more information/,
539
'This might mean the application was put on quarantine by macOS. See [this link](https://github.com/microsoft/vscode/issues/7426#issuecomment-425093469) for more information'
540
);`)]);
541
542
assert.strictEqual(editor.getModel().getLineCount(), 18);
543
544
// called when a response errors out
545
await session.undoChangesUntil(fakeRequest.id);
546
await session.hunkData.recompute({ applied: 0, sha1: 'fakeSha1' }, undefined);
547
548
assert.strictEqual(editor.getModel().getValue(), origValue);
549
550
session.hunkData.discardAll(); // called when dimissing the session
551
assert.strictEqual(editor.getModel().getValue(), origValue);
552
});
553
554
test('Apply Code\'s preview should be easier to undo/esc #7537', async function () {
555
model.setValue(`export function fib(n) {
556
if (n <= 0) return 0;
557
if (n === 1) return 0;
558
if (n === 2) return 1;
559
return fib(n - 1) + fib(n - 2);
560
}`);
561
const session = await inlineChatSessionService.createSession(editor, {}, CancellationToken.None);
562
assertType(session);
563
564
await makeEditAsAi([EditOperation.replace(new Range(5, 1, 6, Number.MAX_SAFE_INTEGER), `
565
let a = 0, b = 1, c;
566
for (let i = 3; i <= n; i++) {
567
c = a + b;
568
a = b;
569
b = c;
570
}
571
return b;
572
}`)]);
573
574
assert.strictEqual(session.hunkData.size, 1);
575
assert.strictEqual(session.hunkData.pending, 1);
576
assert.ok(session.hunkData.getInfo().every(d => d.getState() === HunkState.Pending));
577
578
await assertSnapshot(editor.getModel().getValue(), { name: '1' });
579
580
await model.undo();
581
await assertSnapshot(editor.getModel().getValue(), { name: '2' });
582
583
// overlapping edits (even UNDO) mark edits as accepted
584
assert.strictEqual(session.hunkData.size, 1);
585
assert.strictEqual(session.hunkData.pending, 0);
586
assert.ok(session.hunkData.getInfo().every(d => d.getState() === HunkState.Accepted));
587
588
// no further change when discarding
589
session.hunkData.discardAll(); // CANCEL
590
await assertSnapshot(editor.getModel().getValue(), { name: '2' });
591
});
592
593
});
594
595