Skip to content

Commit 6eb3213

Browse files
authored
inline chat work (microsoft#203534)
* debt - use hunks to render preview diff * add `InteractiveEditorRequest#previewDocument` to indicate for which document edits are previewed before being applied
1 parent a10ecfa commit 6eb3213

File tree

9 files changed

+78
-29
lines changed

9 files changed

+78
-29
lines changed

src/vs/workbench/api/common/extHostInlineChat.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,7 @@ export class ExtHostInteractiveEditor implements ExtHostInlineChatShape {
151151
wholeRange: typeConvert.Range.to(request.wholeRange),
152152
attempt: request.attempt,
153153
live: request.live,
154+
previewDocument: this._documents.getDocument(URI.revive(request.previewDocument)),
154155
withIntentDetection: request.withIntentDetection,
155156
};
156157

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -559,6 +559,7 @@ export class InlineChatController implements IEditorContribution {
559559
selection: this._editor.getSelection(),
560560
wholeRange: this._session.wholeRange.trackedInitialRange,
561561
live: this._session.editMode !== EditMode.Preview, // TODO@jrieken let extension know what document is used for previewing
562+
previewDocument: this._session.textModelN.uri,
562563
withIntentDetection: options.withIntentDetection ?? true /* use intent detection by default */,
563564
};
564565

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ export class InlineChatSessionServiceImpl implements IInlineChatSessionService {
110110
textModelN = store.add(this._modelService.createModel(
111111
createTextBufferFactoryFromSnapshot(textModel.createSnapshot()),
112112
{ languageId: textModel.getLanguageId(), onDidChange: Event.None },
113-
targetUri.with({ scheme: Schemas.inMemory, query: new URLSearchParams({ id, 'inline-chat-textModelN': '' }).toString() }), true
113+
targetUri.with({ scheme: Schemas.vscode, authority: 'inline-chat', path: '', query: new URLSearchParams({ id, 'textModelN': '' }).toString() })
114114
));
115115
} else {
116116
// AI edits happen in the actual model, keep a reference but make no copy
@@ -122,7 +122,7 @@ export class InlineChatSessionServiceImpl implements IInlineChatSessionService {
122122
const textModel0 = store.add(this._modelService.createModel(
123123
createTextBufferFactoryFromSnapshot(textModel.createSnapshot()),
124124
{ languageId: textModel.getLanguageId(), onDidChange: Event.None },
125-
targetUri.with({ scheme: Schemas.inMemory, query: new URLSearchParams({ id, 'inline-chat-textModel0': '' }).toString() }), true
125+
targetUri.with({ scheme: Schemas.vscode, authority: 'inline-chat', path: '', query: new URLSearchParams({ id, 'textModel0': '' }).toString() }), true
126126
));
127127

128128
// untitled documents are special

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

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -192,7 +192,9 @@ export class PreviewStrategy extends EditModeStrategy {
192192
}
193193

194194
override async makeProgressiveChanges(edits: ISingleEditOperation[], obs: IEditObserver, opts: ProgressingEditsOptions): Promise<void> {
195-
return this._makeChanges(edits, obs, opts, undefined);
195+
await this._makeChanges(edits, obs, opts, new Progress<any>(() => {
196+
this._zone.widget.showEditsPreview(this._session.hunkData, this._session.textModel0, this._session.textModelN);
197+
}));
196198
}
197199

198200
override async undoChanges(altVersionId: number): Promise<void> {
@@ -202,7 +204,7 @@ export class PreviewStrategy extends EditModeStrategy {
202204

203205
override async renderChanges(response: ReplyResponse): Promise<undefined> {
204206
if (response.allLocalEdits.length > 0) {
205-
await this._zone.widget.showEditsPreview(this._session.textModel0, this._session.textModelN);
207+
this._zone.widget.showEditsPreview(this._session.hunkData, this._session.textModel0, this._session.textModelN);
206208
} else {
207209
this._zone.widget.hideEditsPreview();
208210
}

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

Lines changed: 17 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur
4141
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
4242
import { AccessibilityVerbositySettingId } from 'vs/workbench/contrib/accessibility/browser/accessibilityConfiguration';
4343
import { renderLabelWithIcons } from 'vs/base/browser/ui/iconLabel/iconLabels';
44-
import { ExpansionState } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSession';
44+
import { ExpansionState, HunkData } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSession';
4545
import * as aria from 'vs/base/browser/ui/aria/aria';
4646
import { IWorkbenchButtonBarOptions, MenuWorkbenchButtonBar } from 'vs/platform/actions/browser/buttonbar';
4747
import { SlashCommandContentWidget } from 'vs/workbench/contrib/chat/browser/chatSlashCommandContentWidget';
@@ -54,7 +54,6 @@ import { ChatEditorOptions } from 'vs/workbench/contrib/chat/browser/chatOptions
5454
import { MenuId } from 'vs/platform/actions/common/actions';
5555
import { editorForeground, inputBackground, editorBackground } from 'vs/platform/theme/common/colorRegistry';
5656
import { Lazy } from 'vs/base/common/lazy';
57-
import { IEditorWorkerService } from 'vs/editor/common/services/editorWorker';
5857
import { ChatResponseViewModel } from 'vs/workbench/contrib/chat/common/chatViewModel';
5958
import { ChatModel, ChatResponseModel } from 'vs/workbench/contrib/chat/common/chatModel';
6059
import { ILogService } from 'vs/platform/log/common/log';
@@ -246,7 +245,6 @@ export class InlineChatWidget {
246245
@IAccessibilityService private readonly _accessibilityService: IAccessibilityService,
247246
@IConfigurationService private readonly _configurationService: IConfigurationService,
248247
@IAccessibleViewService private readonly _accessibleViewService: IAccessibleViewService,
249-
@IEditorWorkerService private readonly _editorWorkerService: IEditorWorkerService,
250248
@ILogService private readonly _logService: ILogService,
251249
@ITextModelService private readonly _textModelResolverService: ITextModelService,
252250
@IChatAgentService private readonly _chatAgentService: IChatAgentService,
@@ -749,36 +747,32 @@ export class InlineChatWidget {
749747

750748
// --- preview
751749

752-
async showEditsPreview(textModel0: ITextModel, textModelN: ITextModel) {
750+
showEditsPreview(hunks: HunkData, textModel0: ITextModel, textModelN: ITextModel) {
753751

754-
this._elements.previewDiff.classList.remove('hidden');
755-
756-
const diff = await this._editorWorkerService.computeDiff(textModel0.uri, textModelN.uri, { ignoreTrimWhitespace: false, maxComputationTimeMs: 5000, computeMoves: false }, 'advanced');
757-
if (!diff || diff.changes.length === 0) {
752+
if (hunks.size === 0) {
758753
this.hideEditsPreview();
759754
return;
760755
}
761756

757+
this._elements.previewDiff.classList.remove('hidden');
758+
762759
this._previewDiffEditor.value.setModel({ original: textModel0, modified: textModelN });
763760

764761
// joined ranges
765-
let originalLineRange = diff.changes[0].original;
766-
let modifiedLineRange = diff.changes[0].modified;
767-
for (let i = 1; i < diff.changes.length; i++) {
768-
originalLineRange = originalLineRange.join(diff.changes[i].original);
769-
modifiedLineRange = modifiedLineRange.join(diff.changes[i].modified);
762+
let originalLineRange: LineRange | undefined;
763+
let modifiedLineRange: LineRange | undefined;
764+
for (const item of hunks.getInfo()) {
765+
const [first0] = item.getRanges0();
766+
const [firstN] = item.getRangesN();
767+
768+
originalLineRange = !originalLineRange ? LineRange.fromRangeInclusive(first0) : originalLineRange.join(LineRange.fromRangeInclusive(first0));
769+
modifiedLineRange = !modifiedLineRange ? LineRange.fromRangeInclusive(firstN) : modifiedLineRange.join(LineRange.fromRangeInclusive(firstN));
770770
}
771771

772-
// apply extra padding
773-
const pad = 3;
774-
const newStartLine = Math.max(1, originalLineRange.startLineNumber - pad);
775-
modifiedLineRange = new LineRange(newStartLine, modifiedLineRange.endLineNumberExclusive);
776-
originalLineRange = new LineRange(newStartLine, originalLineRange.endLineNumberExclusive);
777-
778-
const newEndLineModified = Math.min(modifiedLineRange.endLineNumberExclusive + pad, textModelN.getLineCount());
779-
modifiedLineRange = new LineRange(modifiedLineRange.startLineNumber, newEndLineModified);
780-
const newEndLineOriginal = Math.min(originalLineRange.endLineNumberExclusive + pad, textModel0.getLineCount());
781-
originalLineRange = new LineRange(originalLineRange.startLineNumber, newEndLineOriginal);
772+
if (!originalLineRange || !modifiedLineRange) {
773+
this.hideEditsPreview();
774+
return;
775+
}
782776

783777
const hiddenOriginal = invertLineRange(originalLineRange, textModel0);
784778
const hiddenModified = invertLineRange(modifiedLineRange, textModelN);

src/vs/workbench/contrib/inlineChat/common/inlineChat.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import { Registry } from 'vs/platform/registry/common/platform';
2121
import { diffInserted, diffRemoved, editorHoverHighlight, editorWidgetBackground, editorWidgetBorder, focusBorder, inputBackground, inputPlaceholderForeground, registerColor, transparent, widgetShadow } from 'vs/platform/theme/common/colorRegistry';
2222
import { Extensions as ExtensionsMigration, IConfigurationMigrationRegistry } from 'vs/workbench/common/configuration';
2323
import { IChatFollowup } from 'vs/workbench/contrib/chat/common/chatService';
24+
import { URI } from 'vs/base/common/uri';
2425

2526
export interface IInlineChatSlashCommand {
2627
command: string;
@@ -45,6 +46,7 @@ export interface IInlineChatRequest {
4546
attempt: number;
4647
requestId: string;
4748
live: boolean;
49+
previewDocument: URI;
4850
withIntentDetection: boolean;
4951
}
5052

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

Lines changed: 49 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,12 +36,13 @@ import { IInlineChatSavingService } from '../../browser/inlineChatSavingService'
3636
import { Session } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSession';
3737
import { InlineChatSessionServiceImpl } from '../../browser/inlineChatSessionServiceImpl';
3838
import { IInlineChatSessionService } from '../../browser/inlineChatSessionService';
39-
import { CTX_INLINE_CHAT_USER_DID_EDIT, EditMode, IInlineChatService, InlineChatConfigKeys, InlineChatResponseType } from 'vs/workbench/contrib/inlineChat/common/inlineChat';
39+
import { CTX_INLINE_CHAT_USER_DID_EDIT, EditMode, IInlineChatRequest, IInlineChatService, InlineChatConfigKeys, InlineChatResponseType } from 'vs/workbench/contrib/inlineChat/common/inlineChat';
4040
import { InlineChatServiceImpl } from 'vs/workbench/contrib/inlineChat/common/inlineChatServiceImpl';
4141
import { workbenchInstantiationService } from 'vs/workbench/test/browser/workbenchTestServices';
4242
import { EditOperation } from 'vs/editor/common/core/editOperation';
4343
import { TestWorkerService } from './testWorkerService';
4444
import { IEditorWorkerService } from 'vs/editor/common/services/editorWorker';
45+
import { Schemas } from 'vs/base/common/network';
4546

4647
suite('InteractiveChatController', function () {
4748
class TestController extends InlineChatController {
@@ -68,7 +69,7 @@ suite('InteractiveChatController', function () {
6869

6970
setTimeout(() => {
7071
d.dispose();
71-
reject(`timeout, \nWANTED ${states.join('>')}, \nGOT ${actual.join('>')}`);
72+
reject(new Error(`timeout, \nEXPECTED: ${states.join('>')}, \nACTUAL : ${actual.join('>')}`));
7273
}, 1000);
7374
});
7475
}
@@ -105,6 +106,7 @@ suite('InteractiveChatController', function () {
105106

106107
configurationService = new TestConfigurationService();
107108
configurationService.setUserConfiguration('chat', { editor: { fontSize: 14, fontFamily: 'default' } });
109+
configurationService.setUserConfiguration('inlineChat', { mode: 'livePreview' });
108110
configurationService.setUserConfiguration('editor', {});
109111

110112
const serviceCollection = new ServiceCollection(
@@ -477,4 +479,49 @@ suite('InteractiveChatController', function () {
477479

478480
});
479481

482+
test('context has correct preview document', async function () {
483+
484+
const requests: IInlineChatRequest[] = [];
485+
486+
store.add(inlineChatService.addProvider({
487+
debugName: 'Unit Test',
488+
label: 'Unit Test',
489+
prepareInlineChatSession() {
490+
return {
491+
id: Math.random()
492+
};
493+
},
494+
provideResponse(_session, request) {
495+
requests.push(request);
496+
return undefined;
497+
}
498+
}));
499+
500+
async function makeRequest() {
501+
const p = ctrl.waitFor(TestController.INIT_SEQUENCE_AUTO_SEND);
502+
const r = ctrl.run({ message: 'Hello', autoSend: true });
503+
await p;
504+
await ctrl.cancelSession();
505+
await r;
506+
}
507+
508+
// manual edits -> finish
509+
ctrl = instaService.createInstance(TestController, editor);
510+
511+
configurationService.setUserConfiguration('inlineChat', { mode: EditMode.Live });
512+
await makeRequest();
513+
514+
configurationService.setUserConfiguration('inlineChat', { mode: EditMode.LivePreview });
515+
await makeRequest();
516+
517+
configurationService.setUserConfiguration('inlineChat', { mode: EditMode.Preview });
518+
await makeRequest();
519+
520+
assert.strictEqual(requests.length, 3);
521+
522+
assert.strictEqual(requests[0].previewDocument.toString(), model.uri.toString()); // live
523+
assert.strictEqual(requests[1].previewDocument.toString(), model.uri.toString()); // live preview
524+
assert.strictEqual(requests[2].previewDocument.scheme, Schemas.vscode); // preview
525+
assert.strictEqual(requests[2].previewDocument.authority, 'inline-chat');
526+
});
480527
});

src/vs/workbench/contrib/notebook/browser/view/cellParts/chat/cellChatController.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -307,6 +307,7 @@ export class NotebookCellChatController extends Disposable {
307307
selection: { selectionStartLineNumber: 1, selectionStartColumn: 1, positionLineNumber: 1, positionColumn: 1 },
308308
wholeRange: { startLineNumber: 1, startColumn: 1, endLineNumber: 1, endColumn: 1 },
309309
live: true,
310+
previewDocument: editor.getModel().uri,
310311
withIntentDetection: true, // TODO: don't hard code but allow in corresponding UI to run without intent detection?
311312
};
312313

src/vscode-dts/vscode.proposed.interactive.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ declare module 'vscode' {
3333
wholeRange: Range;
3434
attempt: number;
3535
live: boolean;
36+
previewDocument: TextDocument | undefined;
3637
withIntentDetection: boolean;
3738
}
3839

0 commit comments

Comments
 (0)