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

fixed notebook document metadata edit and added execute cells above/below commands #13528

Merged
merged 4 commits into from
Mar 28, 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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down Expand Up @@ -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.SPLIT_CELL_COMMAND.id,
icon: NotebookCellCommands.SPLIT_CELL_COMMAND.iconClass,
label: nls.localizeByDefault('Split Cell'),
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,
Expand Down Expand Up @@ -190,9 +212,9 @@ 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.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
Expand Down Expand Up @@ -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).filter(c => c.cellKind === CellKind.Code));
}
})
);

commands.registerCommand(NotebookCellCommands.STOP_CELL_EXECUTION_COMMAND, {
execute: (notebookModel: NotebookModel, cell: NotebookCellModel) => {
notebookModel = notebookModel ?? this.notebookEditorWidgetService.focusedEditor?.model;
Expand Down
14 changes: 13 additions & 1 deletion packages/notebook/src/browser/notebook-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import {
CellData, CellEditType, CellMetadataEdit, CellOutput, CellOutputItem, CellRange, NotebookCellContentChangeEvent,
NotebookCellInternalMetadata,
NotebookCellMetadata,
NotebookCellsChangeInternalMetadataEvent,
NotebookCellsChangeLanguageEvent,
NotebookCellsChangeMetadataEvent,
Expand Down Expand Up @@ -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>]: NotebookCellInternalMetadata[Key] | null
};

export type NullablePartialNotebookCellMetadata = {
[Key in keyof Partial<NotebookCellMetadata>]: NotebookCellMetadata[Key] | null
};

export interface CellPartialInternalMetadataEditByHandle {
editType: CellEditType.PartialInternalMetadata;
handle: number;
Expand Down
21 changes: 21 additions & 0 deletions packages/notebook/src/browser/service/notebook-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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');

Expand All @@ -37,6 +38,13 @@ export interface NotebookSerializer {
fromNotebook(data: NotebookData): Promise<BinaryBuffer>;
}

export interface NotebookWorkspaceEdit {
edits: {
resource: URI;
edit: CellEditOperation
}[]
}

@injectable()
export class NotebookService implements Disposable {

Expand Down Expand Up @@ -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;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
59 changes: 57 additions & 2 deletions packages/notebook/src/browser/view-model/notebook-model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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);
Expand All @@ -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);
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,8 @@ 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),
isVisible: () => menuPath ? true : Boolean(this.commandRegistry.getVisibleHandler(menuNode.command!, notebookModel, cell, output)),
Expand Down
13 changes: 13 additions & 0 deletions packages/notebook/src/common/notebook-common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<NotebookCellModelResource>(item) && item.notebookCellModelUri instanceof URI;
}
export function create(uri: URI): NotebookCellModelResource {
return { notebookCellModelUri: uri };
}
}

export enum NotebookCellExecutionState {
Unconfirmed = 1,
Pending = 2,
Expand Down
14 changes: 14 additions & 0 deletions packages/plugin-ext/src/common/plugin-api-rpc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<WorkspaceTextEditDto | WorkspaceFileEditDto | WorkspaceNotebookCellEditDto>;
}
Expand Down Expand Up @@ -2464,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
Expand Down
1 change: 0 additions & 1 deletion packages/plugin-ext/src/main/browser/languages-main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1434,7 +1434,6 @@ export function toMonacoWorkspaceEdit(data: WorkspaceEditDto | undefined): monac
metadata: fileEdit.metadata
};
}
// TODO implement WorkspaceNotebookCellEditDto
})
};
}
4 changes: 1 addition & 3 deletions packages/plugin-ext/src/main/browser/main-context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}

Expand Down
Loading
Loading