Skip to content

Commit 9f627c2

Browse files
authored
no undoStops between streaming edits, esp not between last and second to last round (microsoft#196708)
fixes microsoft/vscode-copilot#2403
1 parent 4422a67 commit 9f627c2

File tree

3 files changed

+49
-8
lines changed

3 files changed

+49
-8
lines changed

src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -596,7 +596,6 @@ export class InlineChatController implements IEditorContribution {
596596
const progressiveEditsCts = new CancellationTokenSource(requestCts.token);
597597
const progressiveEditsClock = StopWatch.create();
598598
const progressiveEditsQueue = new Queue();
599-
let round = 0;
600599

601600
const progress = new AsyncProgress<IInlineChatProgressItem>(async data => {
602601
this._log('received chunk', data, request);
@@ -632,7 +631,7 @@ export class InlineChatController implements IEditorContribution {
632631
// become infinitely fast
633632
await this._makeChanges(data.edits!, data.editsShouldBeInstant
634633
? undefined
635-
: { duration: progressiveEditsAvgDuration.value, round: round++, token: progressiveEditsCts.token }
634+
: { duration: progressiveEditsAvgDuration.value, token: progressiveEditsCts.token }
636635
);
637636

638637
// reshow the widget if the start position changed or shows at the wrong position

src/vs/workbench/contrib/inlineChat/browser/inlineChatStrategies.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -225,7 +225,6 @@ class InlineDiffDecorations {
225225

226226
export interface ProgressingEditsOptions {
227227
duration: number;
228-
round: number;
229228
token: CancellationToken;
230229
}
231230

@@ -328,8 +327,9 @@ export class LiveStrategy extends EditModeStrategy {
328327

329328
override async makeProgressiveChanges(edits: ISingleEditOperation[], opts: ProgressingEditsOptions): Promise<void> {
330329

331-
if (opts.round === 0) {
332-
this._session.textModelN.pushStackElement();
330+
// push undo stop before first edit
331+
if (++this._editCount === 1) {
332+
this._editor.pushUndoStop();
333333
}
334334

335335
const durationInSec = opts.duration / 1000;
@@ -591,14 +591,16 @@ export function asProgressiveEdit(edit: IIdentifiedSingleEditOperation, wordsPer
591591
if (r.isFullString) {
592592
clearInterval(handle);
593593
stream.resolve();
594+
d.dispose();
594595
}
595596

596597
}, 1000 / wordsPerSec);
597598

598599
// cancel ASAP
599-
token.onCancellationRequested(() => {
600+
const d = token.onCancellationRequested(() => {
600601
clearTimeout(handle);
601602
stream.resolve();
603+
d.dispose();
602604
});
603605

604606
return {

src/vs/workbench/contrib/inlineChat/test/browser/inlineChatController.test.ts

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import { DisposableStore } from 'vs/base/common/lifecycle';
1010
import { mock } from 'vs/base/test/common/mock';
1111
import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils';
1212
import { TestDiffProviderFactoryService } from 'vs/editor/browser/diff/testDiffProviderFactoryService';
13-
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
13+
import { IActiveCodeEditor } from 'vs/editor/browser/editorBrowser';
1414
import { IDiffProviderFactoryService } from 'vs/editor/browser/widget/diffEditor/diffProviderFactoryService';
1515
import { Range } from 'vs/editor/common/core/range';
1616
import { ITextModel } from 'vs/editor/common/model';
@@ -81,7 +81,7 @@ suite('InteractiveChatController', function () {
8181
}
8282

8383
const store = new DisposableStore();
84-
let editor: ICodeEditor;
84+
let editor: IActiveCodeEditor;
8585
let model: ITextModel;
8686
let ctrl: TestController;
8787
// let contextKeys: MockContextKeyService;
@@ -327,4 +327,44 @@ suite('InteractiveChatController', function () {
327327
await r;
328328
assert.strictEqual(ctrl.getWidgetPosition(), undefined);
329329
});
330+
331+
test('[Bug] Inline Chat\'s streaming pushed broken iterations to the undo stack #2403', async function () {
332+
333+
const d = inlineChatService.addProvider({
334+
debugName: 'Unit Test',
335+
label: 'Unit Test',
336+
prepareInlineChatSession() {
337+
return {
338+
id: Math.random(),
339+
wholeRange: new Range(3, 1, 3, 3)
340+
};
341+
},
342+
async provideResponse(session, request, progress) {
343+
344+
progress.report({ edits: [{ range: new Range(1, 1, 1, 1), text: 'hEllo1\n' }] });
345+
progress.report({ edits: [{ range: new Range(2, 1, 2, 1), text: 'hEllo2\n' }] });
346+
347+
return {
348+
id: Math.random(),
349+
type: InlineChatResponseType.EditorEdit,
350+
edits: [{ range: new Range(1, 1, 1000, 1), text: 'Hello1\nHello2\n' }]
351+
};
352+
}
353+
});
354+
355+
const valueThen = editor.getModel().getValue();
356+
357+
store.add(d);
358+
ctrl = instaService.createInstance(TestController, editor);
359+
const p = ctrl.waitFor([...TestController.INIT_SEQUENCE, State.MAKE_REQUEST, State.APPLY_RESPONSE, State.SHOW_RESPONSE, State.WAIT_FOR_INPUT]);
360+
const r = ctrl.run({ message: 'Hello', autoSend: true });
361+
await p;
362+
ctrl.acceptSession();
363+
await r;
364+
365+
assert.strictEqual(editor.getModel().getValue(), 'Hello1\nHello2\n');
366+
367+
editor.getModel().undo();
368+
assert.strictEqual(editor.getModel().getValue(), valueThen);
369+
});
330370
});

0 commit comments

Comments
 (0)