Skip to content

Commit

Permalink
Decorated nb cells inserted via chat edits (#233793)
Browse files Browse the repository at this point in the history
* Decorated nb cells inserted via chat edits

* Highlight entire row green

* Misc
  • Loading branch information
DonJayamanne authored Nov 13, 2024
1 parent 4f8b663 commit 9355625
Show file tree
Hide file tree
Showing 2 changed files with 68 additions and 38 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,25 @@
padding: 12px 16px;
}

/** Cell delete higlight */
.monaco-workbench .notebookOverlay > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .view-zones .cell-inner-container {
background-color: var(--vscode-diffEditor-removedLineBackground);
padding: 8px 0;
margin-bottom: 16px;
}

/** Cell insert higlight */
.monaco-workbench .notebookOverlay .monaco-list .monaco-list-row.code-cell-row.nb-insertHighlight .cell-focus-indicator,
.monaco-workbench .notebookOverlay .monaco-list .monaco-list-row.markdown-cell-row.nb-insertHighlight {
background-color: var(--vscode-diffEditor-insertedLineBackground, var(--vscode-diffEditor-insertedTextBackground)) !important;
}

.notebookOverlay .cell .cell-statusbar-container .monaco-workbench .notebookOverlay .monaco-list .monaco-list-row.code-cell-row.nb-insertHighlight .cell-focus-indicator .cell-inner-container,
.monaco-workbench .notebookOverlay .monaco-list .monaco-list-row.code-cell-row.nb-insertHighlight .monaco-editor-background,
.monaco-workbench .notebookOverlay .monaco-list .monaco-list-row.code-cell-row.nb-insertHighlight .margin-view-overlays,
.monaco-workbench .notebookOverlay .monaco-list .monaco-list-row.code-cell-row.nb-insertHighlight .cell-statusbar-container {
background-color: var(--vscode-diffEditor-insertedLineBackground, var(--vscode-diffEditor-insertedTextBackground)) !important;
}

.monaco-workbench .notebookOverlay .view-zones .cell-editor-part {
outline: solid 1px var(--vscode-notebook-cellBorderColor);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/

import { isEqual } from '../../../../base/common/resources.js';
import { AsyncReferenceCollection, Disposable, DisposableStore, dispose, IReference, ReferenceCollection, toDisposable } from '../../../../base/common/lifecycle.js';
import { AsyncReferenceCollection, Disposable, DisposableStore, dispose, IDisposable, IReference, ReferenceCollection, toDisposable } from '../../../../base/common/lifecycle.js';
import { autorun, autorunWithStore, derived, observableFromEvent, observableValue } from '../../../../base/common/observable.js';
import { IChatEditingService, WorkingSetEntryState, IModifiedFileEntry, ChatEditingSessionState } from '../../chat/common/chatEditingService.js';
import { INotebookService } from '../common/notebookService.js';
Expand Down Expand Up @@ -38,9 +38,6 @@ import { NotebookCellTextModel } from '../common/model/notebookCellTextModel.js'
import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js';
import { EditOperation } from '../../../../editor/common/core/editOperation.js';
import { INotebookLoggingService } from '../common/notebookLoggingService.js';
import { TextEdit } from '../../../../editor/common/core/textEdit.js';
import { Position } from '../../../../editor/common/core/position.js';
import { DetailedLineRangeMapping, RangeMapping } from '../../../../editor/common/diff/rangeMapping.js';
import { tokenizeToString } from '../../../../editor/common/languages/textToHtmlTokenizer.js';
import * as DOM from '../../../../base/browser/dom.js';
import { createTrustedTypesPolicy } from '../../../../base/browser/trustedTypes.js';
Expand Down Expand Up @@ -79,15 +76,17 @@ export class NotebookChatEditorControllerContrib extends Disposable implements I


class NotebookChatEditorController extends Disposable {
private readonly deletedCellOverlayer: NotebookDeletedCellOverlayer;
private readonly deletedCellDecorator: NotebookDeletedCellDecorator;
private readonly insertedCellDecorator: NotebookInsertedCellDecorator;
constructor(
private readonly notebookEditor: INotebookEditor,
@IChatEditingService private readonly _chatEditingService: IChatEditingService,
@INotebookOriginalModelReferenceFactory private readonly originalModelRefFactory: INotebookOriginalModelReferenceFactory,
@IInstantiationService private readonly instantiationService: IInstantiationService,
) {
super();
this.deletedCellOverlayer = this._register(instantiationService.createInstance(NotebookDeletedCellOverlayer, notebookEditor));
this.deletedCellDecorator = this._register(instantiationService.createInstance(NotebookDeletedCellDecorator, notebookEditor));
this.insertedCellDecorator = this._register(instantiationService.createInstance(NotebookInsertedCellDecorator, notebookEditor));
const notebookModel = observableFromEvent(this.notebookEditor.onDidChangeModel, e => e);
const entryObs = observableValue<IModifiedFileEntry | undefined>('fileentry', undefined);
const notebookDiff = observableValue<{ cellDiff: CellDiffInfo[]; modelVersion: number } | undefined>('cellDiffInfo', undefined);
Expand Down Expand Up @@ -131,20 +130,22 @@ class NotebookChatEditorController extends Disposable {
if (e) {
notebookDiff.set(undefined, undefined);
disposeDecorators();
this.deletedCellOverlayer.clear();
this.deletedCellDecorator.clear();
this.insertedCellDecorator.clear();
}
}));
store.add(notebookSynchronizer.onDidAccept(() => {
notebookDiff.set(undefined, undefined);
disposeDecorators();
this.deletedCellOverlayer.clear();
this.deletedCellDecorator.clear();
this.insertedCellDecorator.clear();
}));
const result = this._register(await this.originalModelRefFactory.getOrCreate(entry, model.viewType));
originalModel.set(result.object, undefined);
}));

const onDidChangeVisibleRanges = observableFromEvent(this.notebookEditor.onDidChangeVisibleRanges, () => this.notebookEditor.visibleRanges);
const decorators = new Map<NotebookCellTextModel, NotebookCellDiffDecorator>();
const decorators = new Map<NotebookCellTextModel, { editor: ICodeEditor } & IDisposable>();
const disposeDecorators = () => {
dispose(Array.from(decorators.values()));
decorators.clear();
Expand All @@ -161,9 +162,9 @@ class NotebookChatEditorController extends Disposable {
}

diffInfo.cellDiff.forEach((diff) => {
if (diff.type === 'modified' || diff.type === 'insert') {
if (diff.type === 'modified') {
const modifiedCell = modified.cells[diff.modifiedCellIndex];
const originalCellValue = diff.type === 'modified' ? original.cells[diff.originalCellIndex].getValue() : undefined;
const originalCellValue = original.cells[diff.originalCellIndex].getValue();
const editor = this.notebookEditor.codeEditors.find(([vm,]) => vm.handle === modifiedCell.handle)?.[1];
if (editor && decorators.get(modifiedCell)?.editor !== editor) {
decorators.get(modifiedCell)?.dispose();
Expand All @@ -189,10 +190,12 @@ class NotebookChatEditorController extends Disposable {
}
if (!diffInfo) {
// User reverted or accepted the changes, hence original === modified.
this.deletedCellOverlayer.clear();
this.deletedCellDecorator.clear();
this.insertedCellDecorator.clear();
return;
}
this.deletedCellOverlayer.createNecessaryOverlays(diffInfo.cellDiff, original);
this.insertedCellDecorator.apply(diffInfo.cellDiff);
this.deletedCellDecorator.apply(diffInfo.cellDiff, original);
}));
}
}
Expand All @@ -205,7 +208,7 @@ class NotebookCellDiffDecorator extends DisposableStore {

constructor(
public readonly editor: ICodeEditor,
private readonly originalCellValue: string | undefined,
private readonly originalCellValue: string,
private readonly cellKind: CellKind,
@IChatEditingService private readonly _chatEditingService: IChatEditingService,
@IModelService private readonly modelService: IModelService,
Expand Down Expand Up @@ -291,22 +294,10 @@ class NotebookCellDiffDecorator extends DisposableStore {
return;
}

if ((originalModel && !diff) || model !== this.editor.getModel() || this.editor.getModel()?.getVersionId() !== version) {
this._clearRendering();
}

if (diff && originalModel) {
if (diff && originalModel && model === this.editor.getModel() && this.editor.getModel()?.getVersionId() === version) {
this._updateWithDiff(originalModel, diff);
} else {
const edit = TextEdit.insert(new Position(0, 0), model.getValue());
const rangeMapping = RangeMapping.fromEdit(edit);
const insertDiff: IDocumentDiff = {
identical: false,
moves: [],
quitEarly: false,
changes: [DetailedLineRangeMapping.fromRangeMappings(rangeMapping)],
};
this._updateWithDiff(undefined, insertDiff);
this._clearRendering();
}
}

Expand All @@ -325,9 +316,6 @@ class NotebookCellDiffDecorator extends DisposableStore {
if (this._originalModel) {
return this._originalModel;
}
if (!this.originalCellValue) {
return;
}
const model = this.editor.getModel();
if (!model) {
return;
Expand Down Expand Up @@ -457,6 +445,35 @@ class NotebookCellDiffDecorator extends DisposableStore {
}
}

class NotebookInsertedCellDecorator extends Disposable {
private readonly decorators = this._register(new DisposableStore());
constructor(
private readonly notebookEditor: INotebookEditor,
) {
super();

}
public apply(diffInfo: CellDiffInfo[]) {
const model = this.notebookEditor.textModel;
if (!model) {
return;
}
const cells = diffInfo.filter(diff => diff.type === 'insert').map((diff) => model.cells[diff.modifiedCellIndex]);
const ids = this.notebookEditor.deltaCellDecorations([], cells.map(cell => ({
handle: cell.handle,
options: { className: 'nb-insertHighlight', outputClassName: 'nb-insertHighlight' }
})));
this.clear();
this.decorators.add(toDisposable(() => {
if (!this.notebookEditor.isDisposed) {
this.notebookEditor.deltaCellDecorations(ids, []);
}
}));
}
public clear() {
this.decorators.clear();
}
}

class NotebookModelSynchronizer extends Disposable {
private readonly throttledUpdateNotebookModel = new ThrottledDelayer(200);
Expand Down Expand Up @@ -587,11 +604,11 @@ class NotebookModelSynchronizer extends Disposable {
if (!this.snapshot) {
return false;
}
await this.updateNotebook(this.snapshot.bytes, this.snapshot.dirty);
await this.updateNotebook(this.snapshot.bytes, !this.snapshot.dirty);
return true;
}

private async updateNotebook(bytes: VSBuffer, dirty: boolean) {
private async updateNotebook(bytes: VSBuffer, save: boolean) {
if (!this.notebookEditor.textModel) {
return;
}
Expand All @@ -602,7 +619,7 @@ class NotebookModelSynchronizer extends Disposable {
const data = await serializer.dataToNotebook(bytes);
this.notebookEditor.textModel.reset(data.cells, data.metadata, serializer.options);

if (!dirty) {
if (save) {
// save the file after discarding so that the dirty indicator goes away
// and so that an intermediate saved state gets reverted
// await this.notebookEditor.textModel.save({ reason: SaveReason.EXPLICIT });
Expand Down Expand Up @@ -763,7 +780,7 @@ class NotebookModelSynchronizer extends Disposable {

const ttPolicy = createTrustedTypesPolicy('notebookChatEditController', { createHTML: value => value });

class NotebookDeletedCellOverlayer extends Disposable {
class NotebookDeletedCellDecorator extends Disposable {
private readonly zoneRemover = this._register(new DisposableStore());
private readonly createdViewZones = new Map<number, string>();
constructor(
Expand All @@ -774,7 +791,7 @@ class NotebookDeletedCellOverlayer extends Disposable {
}


public createNecessaryOverlays(diffInfo: CellDiffInfo[], original: NotebookTextModel): void {
public apply(diffInfo: CellDiffInfo[], original: NotebookTextModel): void {
this.clear();

let currentIndex = 0;
Expand Down Expand Up @@ -816,7 +833,7 @@ class NotebookDeletedCellOverlayer extends Disposable {
this._notebookEditor.changeViewZones(accessor => {
const notebookViewZone = {
afterModelPosition: index,
heightInPx: totalHeight,
heightInPx: totalHeight + 4,
domNode: rootContainer
};

Expand Down Expand Up @@ -944,7 +961,7 @@ class NotebookDeletedCellWidget extends Disposable {

const lineCount = splitLines(code).length;
const height = (lineCount * (fontInfo.lineHeight || DefaultLineHeight)) + 12 + 12; // We have 12px top and bottom in generated code HTML;
const totalHeight = height + 16;
const totalHeight = height + 16 + 16;

return totalHeight;
}
Expand Down

0 comments on commit 9355625

Please sign in to comment.