From 7f3d887990b5015f2bf54e1fa6bbde166cabf7ea Mon Sep 17 00:00:00 2001 From: Jonah Iden Date: Tue, 26 Mar 2024 08:54:00 +0100 Subject: [PATCH 1/4] notebook workspace edits, document and cell metadata edits Signed-off-by: Jonah Iden --- .../notebook-cell-actions-contribution.ts | 20 +++---- .../notebook/src/browser/notebook-types.ts | 14 ++++- .../src/browser/service/notebook-service.ts | 21 +++++++ .../browser/view-model/notebook-cell-model.ts | 5 ++ .../src/browser/view-model/notebook-model.ts | 59 ++++++++++++++++++- .../view/notebook-cell-toolbar-factory.tsx | 3 +- .../plugin-ext/src/common/plugin-api-rpc.ts | 10 ++++ .../src/main/browser/languages-main.ts | 1 - .../src/main/browser/main-context.ts | 4 +- .../main/browser/notebooks/notebooks-main.ts | 19 ++++-- .../src/main/browser/text-editors-main.ts | 31 ++++++++-- 11 files changed, 159 insertions(+), 28 deletions(-) diff --git a/packages/notebook/src/browser/contributions/notebook-cell-actions-contribution.ts b/packages/notebook/src/browser/contributions/notebook-cell-actions-contribution.ts index 1aefe02a12ff3..21cda170dcb63 100644 --- a/packages/notebook/src/browser/contributions/notebook-cell-actions-contribution.ts +++ b/packages/notebook/src/browser/contributions/notebook-cell-actions-contribution.ts @@ -148,12 +148,12 @@ export class NotebookCellActionContribution implements MenuContribution, Command order: '10' }); - menus.registerMenuAction(NotebookCellActionContribution.ACTION_MENU, { - commandId: NotebookCellCommands.SPLIT_CELL_COMMAND.id, - icon: NotebookCellCommands.SPLIT_CELL_COMMAND.iconClass, - label: nls.localizeByDefault('Split Cell'), - order: '20' - }); + // menus.registerMenuAction(NotebookCellActionContribution.ACTION_MENU, { + // commandId: NotebookCellCommands.SPLIT_CELL_COMMAND.id, + // icon: NotebookCellCommands.SPLIT_CELL_COMMAND.iconClass, + // label: nls.localizeByDefault('Split Cell'), + // order: '20' + // }); menus.registerMenuAction(NotebookCellActionContribution.ACTION_MENU, { commandId: NotebookCellCommands.DELETE_COMMAND.id, icon: NotebookCellCommands.DELETE_COMMAND.iconClass, @@ -190,10 +190,10 @@ export class NotebookCellActionContribution implements MenuContribution, Command }); // Notebook Cell extra execution options - // menus.registerIndependentSubmenu(NotebookCellActionContribution.CONTRIBUTED_CELL_EXECUTION_MENU, - // nls.localizeByDefault('More...'), - // { role: CompoundMenuNodeRole.Flat, icon: codicon('chevron-down') }); - // menus.getMenu(NotebookCellActionContribution.CODE_CELL_SIDEBAR_MENU).addNode(menus.getMenuNode(NotebookCellActionContribution.CONTRIBUTED_CELL_EXECUTION_MENU)); + menus.registerIndependentSubmenu(NotebookCellActionContribution.CONTRIBUTED_CELL_EXECUTION_MENU, + nls.localizeByDefault('More...'), + { role: CompoundMenuNodeRole.Flat, icon: codicon('chevron-down') }); + menus.getMenu(NotebookCellActionContribution.CODE_CELL_SIDEBAR_MENU).addNode(menus.getMenuNode(NotebookCellActionContribution.CONTRIBUTED_CELL_EXECUTION_MENU)); // code cell output sidebar menu menus.registerSubmenu( diff --git a/packages/notebook/src/browser/notebook-types.ts b/packages/notebook/src/browser/notebook-types.ts index a1ac52ae52370..1f82f071755e7 100644 --- a/packages/notebook/src/browser/notebook-types.ts +++ b/packages/notebook/src/browser/notebook-types.ts @@ -17,6 +17,7 @@ import { CellData, CellEditType, CellMetadataEdit, CellOutput, CellOutputItem, CellRange, NotebookCellContentChangeEvent, NotebookCellInternalMetadata, + NotebookCellMetadata, NotebookCellsChangeInternalMetadataEvent, NotebookCellsChangeLanguageEvent, NotebookCellsChangeMetadataEvent, @@ -152,13 +153,24 @@ export interface CellReplaceEdit { cells: CellData[]; } +export interface CellPartialMetadataEdit { + editType: CellEditType.PartialMetadata; + index: number; + metadata: NullablePartialNotebookCellMetadata; +} + export type ImmediateCellEditOperation = CellOutputEditByHandle | CellOutputItemEdit | CellPartialInternalMetadataEditByHandle; // add more later on export type CellEditOperation = ImmediateCellEditOperation | CellReplaceEdit | CellOutputEdit | - CellMetadataEdit | CellLanguageEdit | DocumentMetadataEdit | CellMoveEdit; // add more later on + CellMetadataEdit | CellLanguageEdit | DocumentMetadataEdit | CellMoveEdit | CellPartialMetadataEdit; // add more later on export type NullablePartialNotebookCellInternalMetadata = { [Key in keyof Partial]: NotebookCellInternalMetadata[Key] | null }; + +export type NullablePartialNotebookCellMetadata = { + [Key in keyof Partial]: NotebookCellMetadata[Key] | null +}; + export interface CellPartialInternalMetadataEditByHandle { editType: CellEditType.PartialInternalMetadata; handle: number; diff --git a/packages/notebook/src/browser/service/notebook-service.ts b/packages/notebook/src/browser/service/notebook-service.ts index 583dac957a0f5..ee6cd86fa599a 100644 --- a/packages/notebook/src/browser/service/notebook-service.ts +++ b/packages/notebook/src/browser/service/notebook-service.ts @@ -23,6 +23,7 @@ import { FileService } from '@theia/filesystem/lib/browser/file-service'; import { MonacoTextModelService } from '@theia/monaco/lib/browser/monaco-text-model-service'; import { NotebookCellModel, NotebookCellModelFactory, NotebookCellModelProps } from '../view-model/notebook-cell-model'; import { Deferred } from '@theia/core/lib/common/promise-util'; +import { CellEditOperation } from '../notebook-types'; export const NotebookProvider = Symbol('notebook provider'); @@ -37,6 +38,13 @@ export interface NotebookSerializer { fromNotebook(data: NotebookData): Promise; } +export interface NotebookWorkspaceEdit { + edits: { + resource: URI; + edit: CellEditOperation + }[] +} + @injectable() export class NotebookService implements Disposable { @@ -178,4 +186,17 @@ export class NotebookService implements Disposable { listNotebookDocuments(): NotebookModel[] { return [...this.notebookModels.values()]; } + + applyWorkspaceEdit(workspaceEdit: NotebookWorkspaceEdit): boolean { + try { + workspaceEdit.edits.forEach(edit => { + const notebook = this.getNotebookEditorModel(edit.resource); + notebook?.applyEdits([edit.edit], true); + }); + return true; + } catch (e) { + console.error(e); + return false; + } + } } diff --git a/packages/notebook/src/browser/view-model/notebook-cell-model.ts b/packages/notebook/src/browser/view-model/notebook-cell-model.ts index 8020c320c3f53..ead96aea7de09 100644 --- a/packages/notebook/src/browser/view-model/notebook-cell-model.ts +++ b/packages/notebook/src/browser/view-model/notebook-cell-model.ts @@ -118,6 +118,11 @@ export class NotebookCellModel implements NotebookCell, Disposable { return this._metadata; } + set metadata(newMetadata: NotebookCellMetadata) { + this._metadata = newMetadata; + this.onDidChangeMetadataEmitter.fire(); + } + protected _metadata: NotebookCellMetadata; protected toDispose = new DisposableCollection(); diff --git a/packages/notebook/src/browser/view-model/notebook-model.ts b/packages/notebook/src/browser/view-model/notebook-model.ts index 803cbc202e98b..41df43d0fab82 100644 --- a/packages/notebook/src/browser/view-model/notebook-model.ts +++ b/packages/notebook/src/browser/view-model/notebook-model.ts @@ -18,10 +18,15 @@ import { Disposable, Emitter, Event, Resource, URI } from '@theia/core'; import { Saveable, SaveOptions } from '@theia/core/lib/browser'; import { CellData, CellEditType, CellUri, NotebookCellInternalMetadata, + NotebookCellMetadata, NotebookCellsChangeType, NotebookCellTextModelSplice, NotebookData, NotebookDocumentMetadata, } from '../../common'; -import { NotebookContentChangedEvent, NotebookModelWillAddRemoveEvent, CellEditOperation, NullablePartialNotebookCellInternalMetadata } from '../notebook-types'; +import { + NotebookContentChangedEvent, NotebookModelWillAddRemoveEvent, + CellEditOperation, NullablePartialNotebookCellInternalMetadata, + NullablePartialNotebookCellMetadata +} from '../notebook-types'; import { NotebookSerializer } from '../service/notebook-service'; import { FileService } from '@theia/filesystem/lib/browser/file-service'; import { NotebookCellModel, NotebookCellModelFactory } from './notebook-cell-model'; @@ -285,7 +290,10 @@ export class NotebookModel implements Saveable, Disposable { cell.changeOutputItems(edit.outputId, !!edit.append, edit.items); break; case CellEditType.Metadata: - this.updateNotebookMetadata(edit.metadata, computeUndoRedo); + this.changeCellMetadata(this.cells[cellIndex], edit.metadata, computeUndoRedo); + break; + case CellEditType.PartialMetadata: + this.changeCellMetadataPartial(this.cells[cellIndex], edit.metadata, computeUndoRedo); break; case CellEditType.PartialInternalMetadata: this.changeCellInternalMetadataPartial(this.cells[cellIndex], edit.internalMetadata); @@ -294,6 +302,7 @@ export class NotebookModel implements Saveable, Disposable { this.changeCellLanguage(this.cells[cellIndex], edit.language, computeUndoRedo); break; case CellEditType.DocumentMetadata: + this.updateNotebookMetadata(edit.metadata, computeUndoRedo); break; case CellEditType.Move: this.moveCellToIndex(cellIndex, edit.length, edit.newIdx, computeUndoRedo); @@ -379,6 +388,38 @@ export class NotebookModel implements Saveable, Disposable { this.onDidChangeContentEmitter.fire([{ kind: NotebookCellsChangeType.ChangeDocumentMetadata, metadata: this.metadata }]); } + protected changeCellMetadataPartial(cell: NotebookCellModel, metadata: NullablePartialNotebookCellMetadata, computeUndoRedo: boolean): void { + const newMetadata: NotebookCellMetadata = { + ...cell.metadata + }; + let k: keyof NullablePartialNotebookCellMetadata; + // eslint-disable-next-line guard-for-in + for (k in metadata) { + const value = metadata[k] ?? undefined; + newMetadata[k] = value as unknown; + } + + this.changeCellMetadata(cell, newMetadata, computeUndoRedo); + } + + protected changeCellMetadata(cell: NotebookCellModel, metadata: NotebookCellMetadata, computeUndoRedo: boolean): void { + const triggerDirtyChange = this.isCellMetadataChanged(cell.metadata, metadata); + + if (triggerDirtyChange) { + if (computeUndoRedo) { + const oldMetadata = cell.metadata; + cell.metadata = metadata; + this.undoRedoService.pushElement(this.uri, + async () => { cell.metadata = oldMetadata; }, + async () => { cell.metadata = metadata; } + ); + } + } + + cell.metadata = metadata; + this.onDidChangeContentEmitter.fire([{ kind: NotebookCellsChangeType.ChangeCellMetadata, index: this.cells.indexOf(cell), metadata: cell.metadata }]); + } + protected changeCellLanguage(cell: NotebookCellModel, languageId: string, computeUndoRedo: boolean): void { if (cell.language === languageId) { return; @@ -407,4 +448,18 @@ export class NotebookModel implements Saveable, Disposable { protected getCellIndexByHandle(handle: number): number { return this.cells.findIndex(c => c.handle === handle); } + + protected isCellMetadataChanged(a: NotebookCellMetadata, b: NotebookCellMetadata): boolean { + const keys = new Set([...Object.keys(a || {}), ...Object.keys(b || {})]); + for (const key of keys) { + if ( + (a[key as keyof NotebookCellMetadata] !== b[key as keyof NotebookCellMetadata]) + ) { + return true; + } + } + + return false; + } + } diff --git a/packages/notebook/src/browser/view/notebook-cell-toolbar-factory.tsx b/packages/notebook/src/browser/view/notebook-cell-toolbar-factory.tsx index f6798745bcd65..96f5f24e69224 100644 --- a/packages/notebook/src/browser/view/notebook-cell-toolbar-factory.tsx +++ b/packages/notebook/src/browser/view/notebook-cell-toolbar-factory.tsx @@ -89,7 +89,8 @@ export class NotebookCellToolbarFactory { anchor: e.nativeEvent, menuPath, includeAnchorArg: false, - args: [notebookModel, cell, output] + args: [notebookModel, cell, output], + context: this.notebookContextManager.context }) : () => this.commandRegistry.executeCommand(menuNode.command!, notebookModel, cell, output), isVisible: () => menuPath ? true : Boolean(this.commandRegistry.getVisibleHandler(menuNode.command!, notebookModel, cell, output)), diff --git a/packages/plugin-ext/src/common/plugin-api-rpc.ts b/packages/plugin-ext/src/common/plugin-api-rpc.ts index bf40c9d1e7c3f..6df9a32c4a6a9 100644 --- a/packages/plugin-ext/src/common/plugin-api-rpc.ts +++ b/packages/plugin-ext/src/common/plugin-api-rpc.ts @@ -1563,6 +1563,16 @@ export interface WorkspaceNotebookCellEditDto { cellEdit: CellEditOperationDto; } +export namespace WorkspaceNotebookCellEditDto { + export function is(arg: WorkspaceNotebookCellEditDto | WorkspaceFileEditDto | WorkspaceTextEditDto): arg is WorkspaceNotebookCellEditDto { + return !!arg + && 'resource' in arg + && 'cellEdit' in arg + && arg.cellEdit !== null + && typeof arg.cellEdit === 'object'; + } +} + export interface WorkspaceEditDto { edits: Array; } diff --git a/packages/plugin-ext/src/main/browser/languages-main.ts b/packages/plugin-ext/src/main/browser/languages-main.ts index 99347fd5356d5..4051d8cc9f91e 100644 --- a/packages/plugin-ext/src/main/browser/languages-main.ts +++ b/packages/plugin-ext/src/main/browser/languages-main.ts @@ -1434,7 +1434,6 @@ export function toMonacoWorkspaceEdit(data: WorkspaceEditDto | undefined): monac metadata: fileEdit.metadata }; } - // TODO implement WorkspaceNotebookCellEditDto }) }; } diff --git a/packages/plugin-ext/src/main/browser/main-context.ts b/packages/plugin-ext/src/main/browser/main-context.ts index 95cef28663854..ba820d4f335d9 100644 --- a/packages/plugin-ext/src/main/browser/main-context.ts +++ b/packages/plugin-ext/src/main/browser/main-context.ts @@ -44,7 +44,6 @@ import { EditorManager } from '@theia/editor/lib/browser'; import { EditorModelService } from './text-editor-model-service'; import { OpenerService } from '@theia/core/lib/browser/opener-service'; import { ApplicationShell } from '@theia/core/lib/browser/shell/application-shell'; -import { MonacoBulkEditService } from '@theia/monaco/lib/browser/monaco-bulk-edit-service'; import { MainFileSystemEventService } from './main-file-system-event-service'; import { LabelServiceMainImpl } from './label-service-main'; import { TimelineMainImpl } from './timeline-main'; @@ -109,8 +108,7 @@ export function setUpPluginApi(rpc: RPCProtocol, container: interfaces.Container rpc.set(PLUGIN_RPC_CONTEXT.NOTEBOOK_DOCUMENTS_AND_EDITORS_MAIN, new NotebooksAndEditorsMain(rpc, container, notebookDocumentsMain, notebookEditorsMain)); rpc.set(PLUGIN_RPC_CONTEXT.NOTEBOOK_KERNELS_MAIN, new NotebookKernelsMainImpl(rpc, container)); - const bulkEditService = container.get(MonacoBulkEditService); - const editorsMain = new TextEditorsMainImpl(editorsAndDocuments, documentsMain, rpc, bulkEditService); + const editorsMain = new TextEditorsMainImpl(editorsAndDocuments, documentsMain, rpc, container); rpc.set(PLUGIN_RPC_CONTEXT.TEXT_EDITORS_MAIN, editorsMain); // start listening only after all clients are subscribed to events diff --git a/packages/plugin-ext/src/main/browser/notebooks/notebooks-main.ts b/packages/plugin-ext/src/main/browser/notebooks/notebooks-main.ts index 7263af2d788a6..2b8b0012ac9ea 100644 --- a/packages/plugin-ext/src/main/browser/notebooks/notebooks-main.ts +++ b/packages/plugin-ext/src/main/browser/notebooks/notebooks-main.ts @@ -14,12 +14,12 @@ // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0 // ***************************************************************************** -import { CancellationToken, DisposableCollection, Emitter, Event } from '@theia/core'; +import { CancellationToken, DisposableCollection, Emitter, Event, URI } from '@theia/core'; import { BinaryBuffer } from '@theia/core/lib/common/buffer'; -import { NotebookCellStatusBarItem, NotebookData, TransientOptions } from '@theia/notebook/lib/common'; -import { NotebookService } from '@theia/notebook/lib/browser'; +import { CellEditType, NotebookCellStatusBarItem, NotebookData, TransientOptions } from '@theia/notebook/lib/common'; +import { NotebookService, NotebookWorkspaceEdit } from '@theia/notebook/lib/browser'; import { Disposable } from '@theia/plugin'; -import { CommandRegistryMain, MAIN_RPC_CONTEXT, NotebooksExt, NotebooksMain } from '../../../common'; +import { CommandRegistryMain, MAIN_RPC_CONTEXT, NotebooksExt, NotebooksMain, WorkspaceEditDto, WorkspaceNotebookCellEditDto } from '../../../common'; import { RPCProtocol } from '../../../common/rpc-protocol'; import { NotebookDto } from './notebook-dto'; import { UriComponents } from '@theia/core/lib/common/uri'; @@ -148,3 +148,14 @@ export class NotebooksMainImpl implements NotebooksMain { } } +export function toNotebookWorspaceEdit(dto: WorkspaceEditDto): NotebookWorkspaceEdit { + return { + edits: dto.edits.map((edit: WorkspaceNotebookCellEditDto) => ({ + resource: URI.fromComponents(edit.resource), + edit: edit.cellEdit.editType === CellEditType.Replace ? { + ...edit.cellEdit, + cells: edit.cellEdit.cells.map(cell => NotebookDto.fromNotebookCellDataDto(cell)) + } : edit.cellEdit + })) + }; +} diff --git a/packages/plugin-ext/src/main/browser/text-editors-main.ts b/packages/plugin-ext/src/main/browser/text-editors-main.ts index 3786b211e26db..93c2d820c54ef 100644 --- a/packages/plugin-ext/src/main/browser/text-editors-main.ts +++ b/packages/plugin-ext/src/main/browser/text-editors-main.ts @@ -28,6 +28,7 @@ import { ThemeDecorationInstanceRenderOptions, DecorationOptions, WorkspaceEditDto, + WorkspaceNotebookCellEditDto, DocumentsMain, WorkspaceEditMetadataDto, } from '../../common/plugin-api-rpc'; @@ -46,7 +47,10 @@ import { ResourceEdit } from '@theia/monaco-editor-core/esm/vs/editor/browser/se import { IDecorationRenderOptions } from '@theia/monaco-editor-core/esm/vs/editor/common/editorCommon'; import { StandaloneServices } from '@theia/monaco-editor-core/esm/vs/editor/standalone/browser/standaloneServices'; import { ICodeEditorService } from '@theia/monaco-editor-core/esm/vs/editor/browser/services/codeEditorService'; -import { URI } from '@theia/core'; +import { ArrayUtils, URI } from '@theia/core'; +import { toNotebookWorspaceEdit } from './notebooks/notebooks-main'; +import { interfaces } from '@theia/core/shared/inversify'; +import { NotebookService } from '@theia/notebook/lib/browser'; export class TextEditorsMainImpl implements TextEditorsMain, Disposable { @@ -55,13 +59,20 @@ export class TextEditorsMainImpl implements TextEditorsMain, Disposable { private readonly editorsToDispose = new Map(); private readonly fileEndpoint = new Endpoint({ path: 'file' }).getRestUrl(); + private readonly bulkEditService: MonacoBulkEditService; + private readonly notebookService: NotebookService; + constructor( private readonly editorsAndDocuments: EditorsAndDocumentsMain, private readonly documents: DocumentsMain, rpc: RPCProtocol, - private readonly bulkEditService: MonacoBulkEditService, + container: interfaces.Container ) { this.proxy = rpc.getProxy(MAIN_RPC_CONTEXT.TEXT_EDITORS_EXT); + + this.bulkEditService = container.get(MonacoBulkEditService); + this.notebookService = container.get(NotebookService); + this.toDispose.push(editorsAndDocuments); this.toDispose.push(editorsAndDocuments.onTextEditorAdd(editors => editors.forEach(this.onTextEditorAdd, this))); this.toDispose.push(editorsAndDocuments.onTextEditorRemove(editors => editors.forEach(this.onTextEditorRemove, this))); @@ -128,11 +139,19 @@ export class TextEditorsMainImpl implements TextEditorsMain, Disposable { } async $tryApplyWorkspaceEdit(dto: WorkspaceEditDto, metadata?: WorkspaceEditMetadataDto): Promise { - const workspaceEdit = toMonacoWorkspaceEdit(dto); + const [notebookEdits, monacoEdits] = ArrayUtils.partition(dto.edits, edit => WorkspaceNotebookCellEditDto.is(edit)); try { - const edits = ResourceEdit.convert(workspaceEdit); - const { isApplied } = await this.bulkEditService.apply(edits, { respectAutoSaveConfig: metadata?.isRefactoring }); - return isApplied; + if (notebookEdits.length > 0) { + const workspaceEdit = toNotebookWorspaceEdit({ edits: notebookEdits }); + return this.notebookService.applyWorkspaceEdit(workspaceEdit); + } + if (monacoEdits.length > 0) { + const workspaceEdit = toMonacoWorkspaceEdit({ edits: monacoEdits }); + const edits = ResourceEdit.convert(workspaceEdit); + const { isApplied } = await this.bulkEditService.apply(edits, { respectAutoSaveConfig: metadata?.isRefactoring }); + return isApplied; + } + return false; } catch { return false; } From b8228feec5d8cbb462dfdb1c24e752faa63767fb Mon Sep 17 00:00:00 2001 From: Jonah Iden Date: Tue, 26 Mar 2024 15:01:15 +0100 Subject: [PATCH 2/4] fixed metadata edit and added execute above/below commands Signed-off-by: Jonah Iden --- .../notebook-cell-actions-contribution.ts | 46 +++++++++++++++++-- .../view/notebook-cell-toolbar-factory.tsx | 2 +- .../notebook/src/common/notebook-common.ts | 13 ++++++ .../plugin-ext/src/common/plugin-api-rpc.ts | 4 ++ .../notebooks/notebook-documents-main.ts | 6 +++ .../main/browser/notebooks/notebooks-main.ts | 7 ++- .../src/plugin/notebook/notebook-document.ts | 3 +- .../src/plugin/notebook/notebooks.ts | 8 +++- 8 files changed, 81 insertions(+), 8 deletions(-) diff --git a/packages/notebook/src/browser/contributions/notebook-cell-actions-contribution.ts b/packages/notebook/src/browser/contributions/notebook-cell-actions-contribution.ts index 21cda170dcb63..7ca423e47ef4d 100644 --- a/packages/notebook/src/browser/contributions/notebook-cell-actions-contribution.ts +++ b/packages/notebook/src/browser/contributions/notebook-cell-actions-contribution.ts @@ -62,6 +62,18 @@ export namespace NotebookCellCommands { export const EXECUTE_SINGLE_CELL_AND_FOCUS_NEXT_COMMAND = Command.toDefaultLocalizedCommand({ id: 'notebook.cell.execute-cell-and-focus-next', }); + + export const EXECUTE_ABOVE_CELLS_COMMAND = Command.toDefaultLocalizedCommand({ + id: 'notebookActions.executeAbove', + label: 'Execute Above Cells', + iconClass: codicon('run-above') + }); + + export const EXECUTE_CELL_AND_BELOW_COMMAND = Command.toDefaultLocalizedCommand({ + id: 'notebookActions.executeBelow', + label: 'Execute Cell and Below', + iconClass: codicon('run-below') + }); /** Parameters: notebookModel: NotebookModel, cell: NotebookCellModel */ export const STOP_CELL_EXECUTION_COMMAND = Command.toDefaultLocalizedCommand({ id: 'notebook.cell.stop-cell-execution', @@ -140,20 +152,30 @@ export class NotebookCellActionContribution implements MenuContribution, Command label: nls.localizeByDefault('Stop Editing Cell'), order: '10' }); + menus.registerMenuAction(NotebookCellActionContribution.ACTION_MENU, { - commandId: NotebookCellCommands.EXECUTE_SINGLE_CELL_COMMAND.id, - icon: NotebookCellCommands.EXECUTE_SINGLE_CELL_COMMAND.iconClass, + commandId: NotebookCellCommands.EXECUTE_ABOVE_CELLS_COMMAND.id, + icon: NotebookCellCommands.EXECUTE_ABOVE_CELLS_COMMAND.iconClass, when: `${NOTEBOOK_CELL_TYPE} == 'code'`, - label: nls.localizeByDefault('Execute Cell'), + label: nls.localizeByDefault('Execute Above Cells'), order: '10' }); + menus.registerMenuAction(NotebookCellActionContribution.ACTION_MENU, { + commandId: NotebookCellCommands.EXECUTE_CELL_AND_BELOW_COMMAND.id, + icon: NotebookCellCommands.EXECUTE_CELL_AND_BELOW_COMMAND.iconClass, + when: `${NOTEBOOK_CELL_TYPE} == 'code'`, + label: nls.localizeByDefault('Execute Cell and Below'), + order: '20' + }); + // menus.registerMenuAction(NotebookCellActionContribution.ACTION_MENU, { // commandId: NotebookCellCommands.SPLIT_CELL_COMMAND.id, // icon: NotebookCellCommands.SPLIT_CELL_COMMAND.iconClass, // label: nls.localizeByDefault('Split Cell'), // order: '20' // }); + menus.registerMenuAction(NotebookCellActionContribution.ACTION_MENU, { commandId: NotebookCellCommands.DELETE_COMMAND.id, icon: NotebookCellCommands.DELETE_COMMAND.iconClass, @@ -247,6 +269,24 @@ export class NotebookCellActionContribution implements MenuContribution, Command }) ); + commands.registerCommand(NotebookCellCommands.EXECUTE_ABOVE_CELLS_COMMAND, this.editableCellCommandHandler( + (notebookModel, cell) => { + const index = notebookModel.cells.indexOf(cell); + if (index > 0) { + this.notebookExecutionService.executeNotebookCells(notebookModel, notebookModel.cells.slice(0, index).filter(c => c.cellKind === CellKind.Code)); + } + }) + ); + + commands.registerCommand(NotebookCellCommands.EXECUTE_CELL_AND_BELOW_COMMAND, this.editableCellCommandHandler( + (notebookModel, cell) => { + const index = notebookModel.cells.indexOf(cell); + if (index < notebookModel.cells.length - 1) { + this.notebookExecutionService.executeNotebookCells(notebookModel, notebookModel.cells.slice(index + 1).filter(c => c.cellKind === CellKind.Code)); + } + }) + ); + commands.registerCommand(NotebookCellCommands.STOP_CELL_EXECUTION_COMMAND, { execute: (notebookModel: NotebookModel, cell: NotebookCellModel) => { notebookModel = notebookModel ?? this.notebookEditorWidgetService.focusedEditor?.model; diff --git a/packages/notebook/src/browser/view/notebook-cell-toolbar-factory.tsx b/packages/notebook/src/browser/view/notebook-cell-toolbar-factory.tsx index 96f5f24e69224..b57fb6bf326cc 100644 --- a/packages/notebook/src/browser/view/notebook-cell-toolbar-factory.tsx +++ b/packages/notebook/src/browser/view/notebook-cell-toolbar-factory.tsx @@ -89,7 +89,7 @@ export class NotebookCellToolbarFactory { anchor: e.nativeEvent, menuPath, includeAnchorArg: false, - args: [notebookModel, cell, output], + args: [cell], context: this.notebookContextManager.context }) : () => this.commandRegistry.executeCommand(menuNode.command!, notebookModel, cell, output), diff --git a/packages/notebook/src/common/notebook-common.ts b/packages/notebook/src/common/notebook-common.ts index af1b1b4a2905a..c7db67d8f9b67 100644 --- a/packages/notebook/src/common/notebook-common.ts +++ b/packages/notebook/src/common/notebook-common.ts @@ -179,6 +179,19 @@ export namespace NotebookModelResource { } } +export interface NotebookCellModelResource { + notebookCellModelUri: URI; +} + +export namespace NotebookCellModelResource { + export function is(item: unknown): item is NotebookCellModelResource { + return isObject(item) && item.notebookCellModelUri instanceof URI; + } + export function create(uri: URI): NotebookCellModelResource { + return { notebookCellModelUri: uri }; + } +} + export enum NotebookCellExecutionState { Unconfirmed = 1, Pending = 2, diff --git a/packages/plugin-ext/src/common/plugin-api-rpc.ts b/packages/plugin-ext/src/common/plugin-api-rpc.ts index 6df9a32c4a6a9..33d84951c1cdc 100644 --- a/packages/plugin-ext/src/common/plugin-api-rpc.ts +++ b/packages/plugin-ext/src/common/plugin-api-rpc.ts @@ -2474,6 +2474,10 @@ export type NotebookRawContentEventDto = readonly outputItems: NotebookOutputItemDto[]; readonly append: boolean; } + | { + readonly kind: notebookCommon.NotebookCellsChangeType.ChangeDocumentMetadata + readonly metadata: notebookCommon.NotebookDocumentMetadata; + } | notebookCommon.NotebookCellsChangeLanguageEvent | notebookCommon.NotebookCellsChangeMetadataEvent | notebookCommon.NotebookCellsChangeInternalMetadataEvent diff --git a/packages/plugin-ext/src/main/browser/notebooks/notebook-documents-main.ts b/packages/plugin-ext/src/main/browser/notebooks/notebook-documents-main.ts index 04fe85dbcaacc..e7361bf569ed5 100644 --- a/packages/plugin-ext/src/main/browser/notebooks/notebook-documents-main.ts +++ b/packages/plugin-ext/src/main/browser/notebooks/notebook-documents-main.ts @@ -102,6 +102,12 @@ export class NotebookDocumentsMainImpl implements NotebookDocumentsMain { case NotebookCellsChangeType.ChangeCellInternalMetadata: eventDto.rawEvents.push(e); break; + case NotebookCellsChangeType.ChangeDocumentMetadata: + eventDto.rawEvents.push({ + kind: e.kind, + metadata: e.metadata + }); + break; } } diff --git a/packages/plugin-ext/src/main/browser/notebooks/notebooks-main.ts b/packages/plugin-ext/src/main/browser/notebooks/notebooks-main.ts index 2b8b0012ac9ea..87d1027ebf6d5 100644 --- a/packages/plugin-ext/src/main/browser/notebooks/notebooks-main.ts +++ b/packages/plugin-ext/src/main/browser/notebooks/notebooks-main.ts @@ -16,7 +16,7 @@ import { CancellationToken, DisposableCollection, Emitter, Event, URI } from '@theia/core'; import { BinaryBuffer } from '@theia/core/lib/common/buffer'; -import { CellEditType, NotebookCellStatusBarItem, NotebookData, TransientOptions } from '@theia/notebook/lib/common'; +import { CellEditType, NotebookCellModelResource, NotebookCellStatusBarItem, NotebookData, NotebookModelResource, TransientOptions } from '@theia/notebook/lib/common'; import { NotebookService, NotebookWorkspaceEdit } from '@theia/notebook/lib/browser'; import { Disposable } from '@theia/plugin'; import { CommandRegistryMain, MAIN_RPC_CONTEXT, NotebooksExt, NotebooksMain, WorkspaceEditDto, WorkspaceNotebookCellEditDto } from '../../../common'; @@ -25,6 +25,7 @@ import { NotebookDto } from './notebook-dto'; import { UriComponents } from '@theia/core/lib/common/uri'; import { HostedPluginSupport } from '../../../hosted/browser/hosted-plugin'; import { NotebookModel } from '@theia/notebook/lib/browser/view-model/notebook-model'; +import { NotebookCellModel } from '@theia/notebook/lib/browser/view-model/notebook-cell-model'; import { interfaces } from '@theia/core/shared/inversify'; export interface NotebookCellStatusBarItemList { @@ -62,7 +63,9 @@ export class NotebooksMainImpl implements NotebooksMain { commands.registerArgumentProcessor({ processArgument: arg => { if (arg instanceof NotebookModel) { - return arg.uri; + return NotebookModelResource.create(arg.uri); + } else if (arg instanceof NotebookCellModel) { + return NotebookCellModelResource.create(arg.uri); } return arg; } diff --git a/packages/plugin-ext/src/plugin/notebook/notebook-document.ts b/packages/plugin-ext/src/plugin/notebook/notebook-document.ts index 66b63479be6fb..645d8bdbe1dac 100644 --- a/packages/plugin-ext/src/plugin/notebook/notebook-document.ts +++ b/packages/plugin-ext/src/plugin/notebook/notebook-document.ts @@ -287,7 +287,8 @@ export class NotebookDocument implements Disposable { } else if (rawEvent.kind === notebookCommon.NotebookCellsChangeType.Output) { this.setCellOutputs(rawEvent.index, rawEvent.outputs); relaxedCellChanges.push({ cell: this.cells[rawEvent.index].apiCell, outputs: this.cells[rawEvent.index].apiCell.outputs }); - + } else if (rawEvent.kind === notebookCommon.NotebookCellsChangeType.ChangeDocumentMetadata) { + this.metadata = result.metadata ?? {}; // } else if (rawEvent.kind === notebookCommon.NotebookCellsChangeType.OutputItem) { // this._setCellOutputItems(rawEvent.index, rawEvent.outputId, rawEvent.append, rawEvent.outputItems); // relaxedCellChanges.push({ cell: this.cells[rawEvent.index].apiCell, outputs: this.cells[rawEvent.index].apiCell.outputs }); diff --git a/packages/plugin-ext/src/plugin/notebook/notebooks.ts b/packages/plugin-ext/src/plugin/notebook/notebooks.ts index 01034fd711fe8..d76dec7ba71d8 100644 --- a/packages/plugin-ext/src/plugin/notebook/notebooks.ts +++ b/packages/plugin-ext/src/plugin/notebook/notebooks.ts @@ -36,7 +36,7 @@ import { NotebookDocument } from './notebook-document'; import { NotebookEditor } from './notebook-editor'; import { EditorsAndDocumentsExtImpl } from '../editors-and-documents'; import { DocumentsExtImpl } from '../documents'; -import { NotebookModelResource } from '@theia/notebook/lib/common'; +import { CellUri, NotebookCellModelResource, NotebookModelResource } from '@theia/notebook/lib/common'; export class NotebooksExtImpl implements NotebooksExt { @@ -86,6 +86,12 @@ export class NotebooksExtImpl implements NotebooksExt { processArgument: arg => { if (NotebookModelResource.is(arg)) { return this.documents.get(arg.notebookModelUri.toString())?.apiNotebook; + } else if (NotebookCellModelResource.is(arg)) { + const cellUri = CellUri.parse(arg.notebookCellModelUri); + if (cellUri) { + return this.documents.get(cellUri?.notebook.toString())?.getCell(cellUri.handle)?.apiCell; + } + return undefined; } else { return arg; } From e7788565a552735ceeb43d775266af28b4b97c6a Mon Sep 17 00:00:00 2001 From: Jonah Iden Date: Tue, 26 Mar 2024 15:13:12 +0100 Subject: [PATCH 3/4] removed notebook debug functionality because its not yet supported Signed-off-by: Jonah Iden --- .../browser/contributions/notebook-cell-actions-contribution.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/notebook/src/browser/contributions/notebook-cell-actions-contribution.ts b/packages/notebook/src/browser/contributions/notebook-cell-actions-contribution.ts index 7ca423e47ef4d..94a8ede2e2900 100644 --- a/packages/notebook/src/browser/contributions/notebook-cell-actions-contribution.ts +++ b/packages/notebook/src/browser/contributions/notebook-cell-actions-contribution.ts @@ -215,7 +215,7 @@ export class NotebookCellActionContribution implements MenuContribution, Command menus.registerIndependentSubmenu(NotebookCellActionContribution.CONTRIBUTED_CELL_EXECUTION_MENU, nls.localizeByDefault('More...'), { role: CompoundMenuNodeRole.Flat, icon: codicon('chevron-down') }); - menus.getMenu(NotebookCellActionContribution.CODE_CELL_SIDEBAR_MENU).addNode(menus.getMenuNode(NotebookCellActionContribution.CONTRIBUTED_CELL_EXECUTION_MENU)); + // menus.getMenu(NotebookCellActionContribution.CODE_CELL_SIDEBAR_MENU).addNode(menus.getMenuNode(NotebookCellActionContribution.CONTRIBUTED_CELL_EXECUTION_MENU)); // code cell output sidebar menu menus.registerSubmenu( From c10599f0e0e29b9f150434f52a3d6db4221bec18 Mon Sep 17 00:00:00 2001 From: Jonah Iden Date: Wed, 27 Mar 2024 15:43:03 +0000 Subject: [PATCH 4/4] fixed cell and below start index Signed-off-by: Jonah Iden --- .../browser/contributions/notebook-cell-actions-contribution.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/notebook/src/browser/contributions/notebook-cell-actions-contribution.ts b/packages/notebook/src/browser/contributions/notebook-cell-actions-contribution.ts index 94a8ede2e2900..0481b4bd6f6ba 100644 --- a/packages/notebook/src/browser/contributions/notebook-cell-actions-contribution.ts +++ b/packages/notebook/src/browser/contributions/notebook-cell-actions-contribution.ts @@ -282,7 +282,7 @@ export class NotebookCellActionContribution implements MenuContribution, Command (notebookModel, cell) => { const index = notebookModel.cells.indexOf(cell); if (index < notebookModel.cells.length - 1) { - this.notebookExecutionService.executeNotebookCells(notebookModel, notebookModel.cells.slice(index + 1).filter(c => c.cellKind === CellKind.Code)); + this.notebookExecutionService.executeNotebookCells(notebookModel, notebookModel.cells.slice(index).filter(c => c.cellKind === CellKind.Code)); } }) );