Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use hunks also for livePreview and preview mode #202843

Merged
merged 4 commits into from Jan 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
18 changes: 4 additions & 14 deletions src/vs/workbench/contrib/inlineChat/browser/inlineChat.css
Expand Up @@ -295,28 +295,18 @@
display: none;
}

.monaco-editor .inline-chat .previewDiff {
.monaco-editor .inline-chat .previewDiff,
.monaco-editor .inline-chat .previewCreate {
display: inherit;
padding: 6px;
border: 1px solid var(--vscode-inlineChat-border);
border-top: none;
border-bottom-left-radius: 2px;
border-bottom-right-radius: 2px;
margin: 0 2px 6px 2px;
border-radius: 2px;
margin: 6px 0px;
}

.monaco-editor .inline-chat .previewCreateTitle {
padding-top: 6px;
}

.monaco-editor .inline-chat .previewCreate {
display: inherit;
padding: 6px;
border: 1px solid var(--vscode-inlineChat-border);
border-radius: 2px;
margin: 0 2px 6px 2px;
}

.monaco-editor .inline-chat .previewDiff.hidden,
.monaco-editor .inline-chat .previewCreate.hidden,
.monaco-editor .inline-chat .previewCreateTitle.hidden {
Expand Down
Expand Up @@ -330,7 +330,7 @@ export class InlineChatController implements IEditorContribution {
this._strategy = this._instaService.createInstance(LiveStrategy, session, this._editor, this._zone.value);
break;
case EditMode.Preview:
this._strategy = this._instaService.createInstance(PreviewStrategy, session, this._zone.value);
this._strategy = this._instaService.createInstance(PreviewStrategy, session, this._editor, this._zone.value);
break;
case EditMode.LivePreview:
default:
Expand Down
Expand Up @@ -18,15 +18,14 @@ import * as editorColorRegistry from 'vs/editor/common/core/editorColorRegistry'
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { INLINE_CHAT_ID, inlineChatDiffInserted, inlineChatDiffRemoved, inlineChatRegionHighlight } from 'vs/workbench/contrib/inlineChat/common/inlineChat';
import { LineRange } from 'vs/editor/common/core/lineRange';
import { LineRangeMapping } from 'vs/editor/common/diff/rangeMapping';
import { Position } from 'vs/editor/common/core/position';
import { EditorExtensionsRegistry } from 'vs/editor/browser/editorExtensions';
import { IEditorDecorationsCollection } from 'vs/editor/common/editorCommon';
import { ILogService } from 'vs/platform/log/common/log';
import { lineRangeAsRange, invertLineRange } from 'vs/workbench/contrib/inlineChat/browser/utils';
import { invertLineRange, asRange } from 'vs/workbench/contrib/inlineChat/browser/utils';
import { ResourceLabel } from 'vs/workbench/browser/labels';
import { FileKind } from 'vs/platform/files/common/files';
import { Session } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSession';
import { HunkInformation, Session } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSession';
import { FoldingController } from 'vs/editor/contrib/folding/browser/folding';
import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility';
import { generateUuid } from 'vs/base/common/uuid';
Expand Down Expand Up @@ -170,22 +169,22 @@ export class InlineChatLivePreviewWidget extends ZoneWidget {
throw new Error('use showForChanges');
}

showForChanges(changes: readonly LineRangeMapping[]): void {
showForChanges(hunk: HunkInformation): void {
const hasFocus = this._diffEditor.hasTextFocus();
this._isVisible = true;

const onlyInserts = changes.every(change => change.original.isEmpty);
const onlyInserts = hunk.isInsertion();

if (onlyInserts || changes.length === 0 || this._session.textModel0.getValueLength() === 0) {
if (onlyInserts || this._session.textModel0.getValueLength() === 0) {
// no change or changes to an empty file
this._logService.debug('[IE] livePreview-mode: no diff');
this._cleanupFullDiff();
this._renderInsertWithHighlight(changes);
this._renderInsertWithHighlight(hunk);
} else {
// complex changes
this._logService.debug('[IE] livePreview-mode: full diff');
this._decorationCollection.clear();
this._renderChangesWithFullDiff(changes);
this._renderChangesWithFullDiff(hunk);
}

// TODO@jrieken find a better fix for this. this is the challenge:
Expand All @@ -197,7 +196,7 @@ export class InlineChatLivePreviewWidget extends ZoneWidget {
}
}

private _renderInsertWithHighlight(changes: readonly LineRangeMapping[]) {
private _renderInsertWithHighlight(hunk: HunkInformation) {
assertType(this.editor.hasModel());

const options: IModelDecorationOptions = {
Expand All @@ -207,21 +206,18 @@ export class InlineChatLivePreviewWidget extends ZoneWidget {
className: 'inline-chat-lines-inserted-range',
};

this._decorationCollection.set(changes.map(change => {
return {
range: lineRangeAsRange(change.modified),
options,
};
}));
this._decorationCollection.set([{
range: hunk.getRangesN()[0],
options
}]);
}

// --- full diff

private _renderChangesWithFullDiff(changes: readonly LineRangeMapping[]) {
private _renderChangesWithFullDiff(hunk: HunkInformation) {
assertType(this.editor.hasModel());

const modified = this.editor.getModel();
const ranges = this._computeHiddenRanges(modified, changes);
const ranges = this._computeHiddenRanges(this._session.textModelN, hunk);

this._hideEditorRanges(this.editor, [ranges.modifiedHidden]);
this._hideEditorRanges(this._diffEditor.getOriginalEditor(), ranges.originalDiffHidden);
Expand All @@ -246,15 +242,11 @@ export class InlineChatLivePreviewWidget extends ZoneWidget {
this._isVisible = false;
}

private _computeHiddenRanges(model: ITextModel, changes: readonly LineRangeMapping[]) {
private _computeHiddenRanges(model: ITextModel, hunk: HunkInformation) {

let originalLineRange = changes[0].original;
let modifiedLineRange = changes[0].modified;
for (let i = 1; i < changes.length; i++) {
originalLineRange = originalLineRange.join(changes[i].original);
modifiedLineRange = modifiedLineRange.join(changes[i].modified);
}

const modifiedLineRange = LineRange.fromRangeInclusive(hunk.getRangesN()[0]);
let originalLineRange = LineRange.fromRangeInclusive(hunk.getRanges0()[0]);
if (originalLineRange.isEmpty) {
originalLineRange = new LineRange(originalLineRange.startLineNumber, originalLineRange.endLineNumberExclusive + 1);
}
Expand Down Expand Up @@ -287,7 +279,7 @@ export class InlineChatLivePreviewWidget extends ZoneWidget {
// TODO: not every line can be hidden, keep the first line around
hiddenRanges = [editor.getModel().getFullModelRange().delta(1)];
} else {
hiddenRanges = lineRanges.map(lineRangeAsRange);
hiddenRanges = lineRanges.map(lr => asRange(lr, editor.getModel()));
}
editor.setHiddenAreas(hiddenRanges, this._hideId);
this._logService.debug(`[IE] diff HIDING ${hiddenRanges} for ${editor.getId()} with ${String(editor.getModel()?.uri)}`);
Expand Down
Expand Up @@ -67,7 +67,7 @@ export class InlineChatSavingServiceImpl implements IInlineChatSavingService {
markChanged(session: Session): void {
if (!this._sessionData.has(session)) {

let uri = session.textModelN.uri;
let uri = session.targetUri;

// notebooks: use the notebook-uri because saving happens on the notebook-level
if (uri.scheme === Schemas.vscodeNotebookCell) {
Expand Down Expand Up @@ -209,23 +209,23 @@ export class InlineChatSavingServiceImpl implements IInlineChatSavingService {
break;
}

array.sort((a, b) => compare(a.session.textModelN.uri.toString(), b.session.textModelN.uri.toString()));
array.sort((a, b) => compare(a.session.targetUri.toString(), b.session.targetUri.toString()));


for (const data of array) {

const input: IResourceEditorInput = { resource: data.resourceUri };
const pane = await this._editorService.openEditor(input, group);
let editor: ICodeEditor | undefined;
if (data.session.textModelN.uri.scheme === Schemas.vscodeNotebookCell) {
if (data.session.targetUri.scheme === Schemas.vscodeNotebookCell) {
const notebookEditor = getNotebookEditorFromEditorPane(pane);
const uriData = CellUri.parse(data.session.textModelN.uri);
const uriData = CellUri.parse(data.session.targetUri);
if (notebookEditor && notebookEditor.hasModel() && uriData) {
const cell = notebookEditor.getCellByHandle(uriData.handle);
if (cell) {
await notebookEditor.revealRangeInCenterIfOutsideViewportAsync(cell, data.session.wholeRange.value);
}
const tuple = notebookEditor.codeEditors.find(tuple => tuple[1].getModel()?.uri.toString() === data.session.textModelN.uri.toString());
const tuple = notebookEditor.codeEditors.find(tuple => tuple[1].getModel()?.uri.toString() === data.session.targetUri.toString());
editor = tuple?.[1];
}

Expand All @@ -240,7 +240,7 @@ export class InlineChatSavingServiceImpl implements IInlineChatSavingService {
break;
}
this._inlineChatSessionService.moveSession(data.session, editor);
this._logService.info('WAIT for session to end', editor.getId(), data.session.textModelN.uri.toString());
this._logService.info('WAIT for session to end', editor.getId(), data.session.targetUri.toString());
await this._whenSessionsEnded(Iterable.single(data), token);
}
}
Expand Down
13 changes: 13 additions & 0 deletions src/vs/workbench/contrib/inlineChat/browser/inlineChatSession.ts
Expand Up @@ -142,7 +142,17 @@ export class Session {

constructor(
readonly editMode: EditMode,
/**
* The URI of the document which is being EditorEdit
*/
readonly targetUri: URI,
/**
* A copy of the document at the time the session was started
*/
readonly textModel0: ITextModel,
/**
* The document into which AI edits went, when live this is `targetUri` otherwise it is a temporary document
*/
readonly textModelN: ITextModel,
readonly provider: IInlineChatSessionProvider,
readonly session: IInlineChatSession,
Expand Down Expand Up @@ -707,6 +717,9 @@ export interface HunkInformation {

discardChanges(): void;

/**
* Accept the hunk. Applies the corresponding edits into textModel0
*/
acceptChanges(): void;

getState(): HunkState;
Expand Down
Expand Up @@ -19,6 +19,8 @@ import { raceCancellation } from 'vs/base/common/async';
import { Recording, IInlineChatSessionService, ISessionKeyComputer } from './inlineChatSessionService';
import { HunkData, Session, SessionWholeRange, TelemetryData, TelemetryDataClassification } from './inlineChatSession';
import { IEditorWorkerService } from 'vs/editor/common/services/editorWorker';
import { ITextModel } from 'vs/editor/common/model';
import { Schemas } from 'vs/base/common/network';

type SessionData = {
editor: ICodeEditor;
Expand Down Expand Up @@ -71,9 +73,9 @@ export class InlineChatSessionServiceImpl implements IInlineChatSessionService {

const textModel = editor.getModel();
const selection = editor.getSelection();
let raw: IInlineChatSession | undefined | null;
let rawSession: IInlineChatSession | undefined | null;
try {
raw = await raceCancellation(
rawSession = await raceCancellation(
Promise.resolve(provider.prepareInlineChatSession(textModel, selection, token)),
token
);
Expand All @@ -82,7 +84,7 @@ export class InlineChatSessionServiceImpl implements IInlineChatSessionService {
this._logService.error(error);
return undefined;
}
if (!raw) {
if (!rawSession) {
this._logService.trace('[IE] NO session', provider.debugName);
return undefined;
}
Expand All @@ -91,35 +93,46 @@ export class InlineChatSessionServiceImpl implements IInlineChatSessionService {
this._logService.trace(`[IE] creating NEW session for ${editor.getId()}, ${provider.debugName}`);
const store = new DisposableStore();

// create: keep a reference to prevent disposal of the "actual" model
const refTextModelN = await this._textModelService.createModelReference(textModel.uri);
store.add(refTextModelN);
const targetUri = textModel.uri;

let textModelN: ITextModel;
if (options.editMode === EditMode.Preview) {
// AI edits happen in a copy
textModelN = store.add(this._modelService.createModel(
createTextBufferFactoryFromSnapshot(textModel.createSnapshot()),
{ languageId: textModel.getLanguageId(), onDidChange: Event.None },
targetUri.with({ scheme: Schemas.inMemory, query: 'inline-chat-textModelN' }), true
));
} else {
// AI edits happen in the actual model, keep a reference but make no copy
store.add((await this._textModelService.createModelReference(textModel.uri)));
textModelN = textModel;
}

// create: keep a snapshot of the "actual" model
const textModel0 = this._modelService.createModel(
const textModel0 = store.add(this._modelService.createModel(
createTextBufferFactoryFromSnapshot(textModel.createSnapshot()),
{ languageId: textModel.getLanguageId(), onDidChange: Event.None },
undefined, true
);
store.add(textModel0);
targetUri.with({ scheme: Schemas.inMemory, query: 'inline-chat-textModel0' }), true
));

let wholeRange = options.wholeRange;
if (!wholeRange) {
wholeRange = raw.wholeRange ? Range.lift(raw.wholeRange) : editor.getSelection();
wholeRange = rawSession.wholeRange ? Range.lift(rawSession.wholeRange) : editor.getSelection();
}


// install managed-marker for the decoration range
const wholeRangeMgr = new SessionWholeRange(textModel, wholeRange);
store.add(wholeRangeMgr);

const hunkData = new HunkData(this._editorWorkerService, textModel0, textModel);
store.add(hunkData);

const session = new Session(options.editMode, textModel0, textModel, provider, raw, wholeRangeMgr, hunkData);
const session = new Session(
options.editMode,
targetUri,
textModel0,
textModelN,
provider, rawSession,
store.add(new SessionWholeRange(textModelN, wholeRange)),
store.add(new HunkData(this._editorWorkerService, textModel0, textModelN))
);

// store: key -> session
const key = this._key(editor, textModel.uri);
const key = this._key(editor, session.targetUri);
if (this._sessions.has(key)) {
store.dispose();
throw new Error(`Session already stored for ${key}`);
Expand All @@ -129,7 +142,7 @@ export class InlineChatSessionServiceImpl implements IInlineChatSessionService {
}

moveSession(session: Session, target: ICodeEditor): void {
const newKey = this._key(target, session.textModelN.uri);
const newKey = this._key(target, session.targetUri);
const existing = this._sessions.get(newKey);
if (existing) {
if (existing.session !== session) {
Expand Down