diff --git a/src/vs/workbench/api/browser/mainThreadNotebook.ts b/src/vs/workbench/api/browser/mainThreadNotebook.ts index 0a0f0ed1903..c5b64e0eff4 100644 --- a/src/vs/workbench/api/browser/mainThreadNotebook.ts +++ b/src/vs/workbench/api/browser/mainThreadNotebook.ts @@ -557,7 +557,16 @@ export class MainThreadNotebooks extends Disposable implements MainThreadNoteboo async $updateNotebookCellMetadata(viewType: string, resource: UriComponents, handle: number, metadata: NotebookCellMetadata): Promise { this.logService.debug('MainThreadNotebooks#updateNotebookCellMetadata', resource.path, handle, metadata); const textModel = this._notebookService.getNotebookTextModel(URI.from(resource)); - textModel?.changeCellMetadata(handle, metadata, true); + if (!textModel) { + return; + } + + const index = textModel.cells.findIndex(cell => cell.handle === handle); + if (index < 0) { + return; + } + + textModel.applyEdit(textModel.versionId, [{ editType: CellEditType.Metadata, index, metadata }], true); } async $spliceNotebookCellOutputs(viewType: string, resource: UriComponents, cellHandle: number, splices: NotebookCellOutputsSplice[]): Promise { diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/coreActions.ts b/src/vs/workbench/contrib/notebook/browser/contrib/coreActions.ts index eedc6fd9cea..d7ac66ddb27 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/coreActions.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/coreActions.ts @@ -20,7 +20,7 @@ import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegis import { IQuickInputService, IQuickPickItem, QuickPickInput } from 'vs/platform/quickinput/common/quickInput'; import { BaseCellRenderTemplate, CellEditState, CellFocusMode, ICellViewModel, INotebookEditor, NOTEBOOK_CELL_INPUT_COLLAPSED, NOTEBOOK_CELL_EDITABLE, NOTEBOOK_CELL_HAS_OUTPUTS, NOTEBOOK_CELL_LIST_FOCUSED, NOTEBOOK_CELL_MARKDOWN_EDIT_MODE, NOTEBOOK_CELL_OUTPUT_COLLAPSED, NOTEBOOK_CELL_TYPE, NOTEBOOK_EDITOR_EDITABLE, NOTEBOOK_EDITOR_EXECUTING_NOTEBOOK, NOTEBOOK_EDITOR_FOCUSED, NOTEBOOK_EDITOR_RUNNABLE, NOTEBOOK_IS_ACTIVE_EDITOR, NOTEBOOK_OUTPUT_FOCUSED, EXPAND_CELL_CONTENT_COMMAND_ID, NOTEBOOK_CELL_EDITOR_FOCUSED } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { CellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel'; -import { CellEditType, CellKind, CellUri, NotebookCellRunState, NOTEBOOK_EDITOR_CURSOR_BOUNDARY } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { CellEditType, CellKind, CellUri, NotebookCellMetadata, NotebookCellRunState, NOTEBOOK_EDITOR_CURSOR_BOUNDARY } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; @@ -1300,13 +1300,15 @@ registerAction2(class extends NotebookCellAction { editor.viewModel.notebookDocument.applyEdit(editor.viewModel.notebookDocument.versionId, [{ editType: CellEditType.Output, index, outputs: [] }], true); if (context.cell.metadata && context.cell.metadata?.runState !== NotebookCellRunState.Running) { - context.notebookEditor.viewModel!.notebookDocument.changeCellMetadata(context.cell.handle, { - ...context.cell.metadata, - runState: NotebookCellRunState.Idle, - runStartTime: undefined, - lastRunDuration: undefined, - statusMessage: undefined - }, true); + context.notebookEditor.viewModel!.notebookDocument.applyEdit(context.notebookEditor.viewModel!.notebookDocument.versionId, [{ + editType: CellEditType.Metadata, index, metadata: { + ...context.cell.metadata, + runState: NotebookCellRunState.Idle, + runStartTime: undefined, + lastRunDuration: undefined, + statusMessage: undefined + } + }], true); } } }); @@ -1555,7 +1557,27 @@ registerAction2(class extends NotebookCellAction { } }); -registerAction2(class extends NotebookCellAction { +abstract class ChangeNotebookCellMetadataAction extends NotebookCellAction { + async runWithContext(accessor: ServicesAccessor, context: INotebookCellActionContext): Promise { + const cell = context.cell; + const textModel = context.notebookEditor.viewModel?.notebookDocument; + if (!textModel) { + return; + } + + const index = textModel.cells.indexOf(cell.model); + + if (index < 0) { + return; + } + + textModel.applyEdit(textModel.versionId, [{ editType: CellEditType.Metadata, index, metadata: { ...context.cell.metadata, ...this.getMetadataDelta() } }], true); + } + + abstract getMetadataDelta(): NotebookCellMetadata; +} + +registerAction2(class extends ChangeNotebookCellMetadataAction { constructor() { super({ id: COLLAPSE_CELL_INPUT_COMMAND_ID, @@ -1573,12 +1595,12 @@ registerAction2(class extends NotebookCellAction { }); } - async runWithContext(accessor: ServicesAccessor, context: INotebookCellActionContext): Promise { - context.notebookEditor.viewModel!.notebookDocument.changeCellMetadata(context.cell.handle, { ...context.cell.metadata, inputCollapsed: true }, true); + getMetadataDelta(): NotebookCellMetadata { + return { inputCollapsed: true }; } }); -registerAction2(class extends NotebookCellAction { +registerAction2(class extends ChangeNotebookCellMetadataAction { constructor() { super({ id: EXPAND_CELL_CONTENT_COMMAND_ID, @@ -1596,12 +1618,12 @@ registerAction2(class extends NotebookCellAction { }); } - async runWithContext(accessor: ServicesAccessor, context: INotebookCellActionContext): Promise { - context.notebookEditor.viewModel!.notebookDocument.changeCellMetadata(context.cell.handle, { ...context.cell.metadata, inputCollapsed: false }, true); + getMetadataDelta(): NotebookCellMetadata { + return { inputCollapsed: false }; } }); -registerAction2(class extends NotebookCellAction { +registerAction2(class extends ChangeNotebookCellMetadataAction { constructor() { super({ id: COLLAPSE_CELL_OUTPUT_COMMAND_ID, @@ -1619,12 +1641,12 @@ registerAction2(class extends NotebookCellAction { }); } - async runWithContext(accessor: ServicesAccessor, context: INotebookCellActionContext): Promise { - context.notebookEditor.viewModel!.notebookDocument.changeCellMetadata(context.cell.handle, { ...context.cell.metadata, outputCollapsed: true }, true); + getMetadataDelta(): NotebookCellMetadata { + return { outputCollapsed: true }; } }); -registerAction2(class extends NotebookCellAction { +registerAction2(class extends ChangeNotebookCellMetadataAction { constructor() { super({ id: EXPAND_CELL_OUTPUT_COMMAND_ID, @@ -1642,8 +1664,8 @@ registerAction2(class extends NotebookCellAction { }); } - async runWithContext(accessor: ServicesAccessor, context: INotebookCellActionContext): Promise { - context.notebookEditor.viewModel!.notebookDocument.changeCellMetadata(context.cell.handle, { ...context.cell.metadata, outputCollapsed: false }, true); + getMetadataDelta(): NotebookCellMetadata { + return { outputCollapsed: false }; } }); diff --git a/src/vs/workbench/contrib/notebook/browser/diff/cellComponents.ts b/src/vs/workbench/contrib/notebook/browser/diff/cellComponents.ts index 6eace8686e1..327c3ee0721 100644 --- a/src/vs/workbench/contrib/notebook/browser/diff/cellComponents.ts +++ b/src/vs/workbench/contrib/notebook/browser/diff/cellComponents.ts @@ -17,7 +17,7 @@ import { IModelService } from 'vs/editor/common/services/modelService'; import { IModeService } from 'vs/editor/common/services/modeService'; import { format } from 'vs/base/common/jsonFormatter'; import { applyEdits } from 'vs/base/common/jsonEdit'; -import { CellUri, NotebookCellMetadata } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { CellEditType, CellUri, NotebookCellMetadata } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { hash } from 'vs/base/common/hash'; import { ToolBar } from 'vs/base/browser/ui/toolbar/toolbar'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; @@ -451,7 +451,15 @@ abstract class AbstractCellRenderer extends Disposable { if (newLangauge !== undefined && newLangauge !== this.cell.modified!.language) { this.notebookEditor.textModel!.changeCellLanguage(this.cell.modified!.handle, newLangauge); } - this.notebookEditor.textModel!.changeCellMetadata(this.cell.modified!.handle, result, false); + const index = this.notebookEditor.textModel!.cells.indexOf(this.cell.modified!); + + if (index < 0) { + return; + } + + this.notebookEditor.textModel!.applyEdit(this.notebookEditor.textModel!.versionId, [ + { editType: CellEditType.Metadata, index, metadata: result } + ], true); } catch { } } diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/cellRenderer.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/cellRenderer.ts index 71d8ba227e5..fcffc8ee0fd 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/cellRenderer.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/cellRenderer.ts @@ -48,7 +48,7 @@ import { StatefulMarkdownCell } from 'vs/workbench/contrib/notebook/browser/view import { CodeCellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/codeCellViewModel'; import { MarkdownCellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/markdownCellViewModel'; import { CellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel'; -import { CellKind, NotebookCellMetadata, NotebookCellRunState, ShowCellStatusBarKey } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { CellEditType, CellKind, NotebookCellMetadata, NotebookCellRunState, ShowCellStatusBarKey } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { createAndFillInActionBarActionsWithVerticalSeparators, VerticalSeparator, VerticalSeparatorViewItem } from './cellActionView'; const $ = DOM.$; @@ -332,11 +332,21 @@ abstract class AbstractCellRenderer { return; } - if (templateData.currentRenderedCell.metadata?.inputCollapsed) { + const textModel = this.notebookEditor.viewModel!.notebookDocument; + const index = textModel.cells.indexOf(templateData.currentRenderedCell.model); - this.notebookEditor.viewModel!.notebookDocument.changeCellMetadata(templateData.currentRenderedCell.handle, { ...templateData.currentRenderedCell.metadata, inputCollapsed: false }, true); + if (index < 0) { + return; + } + + if (templateData.currentRenderedCell.metadata?.inputCollapsed) { + textModel.applyEdit(textModel.versionId, [ + { editType: CellEditType.Metadata, index, metadata: { ...templateData.currentRenderedCell.metadata, inputCollapsed: false } } + ], true); } else if (templateData.currentRenderedCell.metadata?.outputCollapsed) { - this.notebookEditor.viewModel!.notebookDocument.changeCellMetadata(templateData.currentRenderedCell.handle, { ...templateData.currentRenderedCell.metadata, outputCollapsed: false }, true); + textModel.applyEdit(textModel.versionId, [ + { editType: CellEditType.Metadata, index, metadata: { ...templateData.currentRenderedCell.metadata, outputCollapsed: false } } + ], true); } })); } diff --git a/src/vs/workbench/contrib/notebook/common/model/notebookTextModel.ts b/src/vs/workbench/contrib/notebook/common/model/notebookTextModel.ts index 343add4f707..da68e228b34 100644 --- a/src/vs/workbench/contrib/notebook/common/model/notebookTextModel.ts +++ b/src/vs/workbench/contrib/notebook/common/model/notebookTextModel.ts @@ -254,7 +254,7 @@ export class NotebookTextModel extends Disposable implements INotebookTextModel break; case CellEditType.Metadata: this._assertIndex(edit.index); - this.changeCellMetadata(this.cells[edit.index].handle, { ...this.cells[edit.index].metadata, ...edit.metadata }, true); + this._changeCellMetadata(this.cells[edit.index].handle, edit.metadata, true); break; } } @@ -483,6 +483,55 @@ export class NotebookTextModel extends Disposable implements INotebookTextModel this._onDidChangeCells.fire({ synchronous: synchronous, splices: [[index, 1, []]] }); } + private _isCellMetadataChanged(a: NotebookCellMetadata, b: NotebookCellMetadata) { + const keys = new Set([...Object.keys(a || {}), ...Object.keys(b || {})]); + for (let key of keys) { + if ( + (a[key as keyof NotebookCellMetadata] !== b[key as keyof NotebookCellMetadata]) + && + !(this.transientOptions.transientMetadata[key as keyof NotebookCellMetadata]) + ) { + return true; + } + } + + return false; + } + + private _changeCellMetadata(handle: number, metadata: NotebookCellMetadata, pushUndoStop: boolean) { + const cell = this.cells.find(cell => cell.handle === handle); + + if (!cell) { + return; + } + + const triggerDirtyChange = this._isCellMetadataChanged(cell.metadata, metadata); + + if (triggerDirtyChange) { + if (pushUndoStop) { + const index = this.cells.indexOf(cell); + this._operationManager.pushEditOperation(new CellMetadataEdit(this.uri, index, Object.freeze(cell.metadata), Object.freeze(metadata), { + updateCellMetadata: (index, newMetadata) => { + const cell = this.cells[index]; + if (!cell) { + return; + } + this._changeCellMetadata(cell.handle, newMetadata, false); + }, + emitSelections: (selections) => { this._emitSelections.fire(selections); } + })); + } + cell.metadata = metadata; + this.setDirty(true); + this._onDidChangeContent.fire(NotebookCellsChangeType.ChangeCellMetadata); + } else { + cell.metadata = metadata; + } + + this._increaseVersionId(); + this._onDidModelChangeProxy.fire({ kind: NotebookCellsChangeType.ChangeCellMetadata, versionId: this._versionId, index: this.cells.indexOf(cell), metadata: cell.metadata }); + } + // TODO@rebornix, once adopted the new Edit API in ext host, the method should be private. _spliceNotebookCellOutputs(cellHandle: number, splices: NotebookCellOutputsSplice[]): void { const cell = this._mapping.get(cellHandle); @@ -523,55 +572,6 @@ export class NotebookTextModel extends Disposable implements INotebookTextModel } } - private _isCellMetadataChanged(a: NotebookCellMetadata, b: NotebookCellMetadata) { - const keys = new Set([...Object.keys(a || {}), ...Object.keys(b || {})]); - for (let key of keys) { - if ( - (a[key as keyof NotebookCellMetadata] !== b[key as keyof NotebookCellMetadata]) - && - !(this.transientOptions.transientMetadata[key as keyof NotebookCellMetadata]) - ) { - return true; - } - } - - return false; - } - - changeCellMetadata(handle: number, metadata: NotebookCellMetadata, pushUndoStop: boolean) { - const cell = this.cells.find(cell => cell.handle === handle); - - if (!cell) { - return; - } - - const triggerDirtyChange = this._isCellMetadataChanged(cell.metadata, metadata); - - if (triggerDirtyChange) { - if (pushUndoStop) { - const index = this.cells.indexOf(cell); - this._operationManager.pushEditOperation(new CellMetadataEdit(this.uri, index, Object.freeze(cell.metadata), Object.freeze(metadata), { - updateCellMetadata: (index, newMetadata) => { - const cell = this.cells[index]; - if (!cell) { - return; - } - this.changeCellMetadata(cell.handle, newMetadata, false); - }, - emitSelections: (selections) => { this._emitSelections.fire(selections); } - })); - } - cell.metadata = metadata; - this.setDirty(true); - this._onDidChangeContent.fire(NotebookCellsChangeType.ChangeCellMetadata); - } else { - cell.metadata = metadata; - } - - this._increaseVersionId(); - this._onDidModelChangeProxy.fire({ kind: NotebookCellsChangeType.ChangeCellMetadata, versionId: this._versionId, index: this.cells.indexOf(cell), metadata: cell.metadata }); - } - insertCell(index: number, cell: NotebookCellTextModel, synchronous: boolean, pushUndoStop: boolean, beforeSelections: number[] | undefined, endSelections: number[] | undefined): void { if (pushUndoStop) { this._operationManager.pushEditOperation(new InsertCellEdit(this.uri, index, cell, {