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

Notebook: Split cell command implementation #14212

Merged
merged 4 commits into from
Oct 9, 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 @@ -28,12 +28,14 @@ import {
import { ContextKeyService } from '@theia/core/lib/browser/context-key-service';
import { NotebookExecutionService } from '../service/notebook-execution-service';
import { NotebookCellOutputModel } from '../view-model/notebook-cell-output-model';
import { CellEditType, CellKind } from '../../common';
import { CellData, CellEditType, CellKind } from '../../common';
import { NotebookEditorWidgetService } from '../service/notebook-editor-widget-service';
import { NotebookCommands } from './notebook-actions-contribution';
import { changeCellType } from './cell-operations';
import { EditorLanguageQuickPickService } from '@theia/editor/lib/browser/editor-language-quick-pick-service';
import { NotebookService } from '../service/notebook-service';
import { Selection } from '@theia/monaco-editor-core/esm/vs/editor/common/core/selection';
import { Range } from '@theia/core/shared/vscode-languageserver-protocol';
import { NOTEBOOK_EDITOR_ID_PREFIX } from '../notebook-editor-widget';

export namespace NotebookCellCommands {
Expand All @@ -56,7 +58,7 @@ export namespace NotebookCellCommands {
});
/** Parameters: notebookModel: NotebookModel, cell: NotebookCellModel */
export const SPLIT_CELL_COMMAND = Command.toDefaultLocalizedCommand({
id: 'notebook.cell.split-cell',
id: 'notebook.cell.split',
iconClass: codicon('split-vertical'),
});
/** Parameters: notebookModel: NotebookModel, cell: NotebookCellModel */
Expand Down Expand Up @@ -214,12 +216,12 @@ export class NotebookCellActionContribution implements MenuContribution, Command
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.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,
Expand Down Expand Up @@ -294,7 +296,47 @@ export class NotebookCellActionContribution implements MenuContribution, Command
}]
, true);
}));
commands.registerCommand(NotebookCellCommands.SPLIT_CELL_COMMAND);
commands.registerCommand(NotebookCellCommands.SPLIT_CELL_COMMAND, this.editableCellCommandHandler(
async (notebookModel, cell) => {
// selection (0,0,0,0) should also be used in !cell.editing mode, but `cell.editing`
// is not properly implemented for Code cells.
const cellSelection: Range = cell.selection ?? { start: { line: 0, character: 0 }, end: { line: 0, character: 0 } };
const textModel = await cell.resolveTextModel();

// Create new cell with the text after the cursor
const splitOffset = textModel.offsetAt({
line: cellSelection.start.line,
character: cellSelection.start.character
});
const newCell: CellData = {
cellKind: cell.cellKind,
language: cell.language,
outputs: [],
source: textModel.getText().substring(splitOffset),
};

// add new cell below
const index = notebookModel.cells.indexOf(cell);
notebookModel.applyEdits([{ editType: CellEditType.Replace, index: index + 1, count: 0, cells: [newCell] }], true);

// update current cell text (undo-able)
const selection = new Selection(cellSelection.start.line + 1, cellSelection.start.character + 1, cellSelection.end.line + 1, cellSelection.end.character + 1);
const endPosition = textModel.positionAt(textModel.getText().length);
const deleteOp = {
range: {
startLineNumber: selection.startLineNumber,
startColumn: selection.startColumn,
endLineNumber: endPosition.line + 1,
endColumn: endPosition.character + 1
},
// eslint-disable-next-line no-null/no-null
text: null
};
// Create a new undo/redo stack entry
textModel.textEditorModel.pushStackElement();
textModel.textEditorModel.pushEditOperations([selection], [deleteOp], () => [selection]);
})
);

commands.registerCommand(NotebookCellCommands.EXECUTE_SINGLE_CELL_COMMAND, this.editableCellCommandHandler(
(notebookModel, cell) => {
Expand Down Expand Up @@ -511,6 +553,11 @@ export class NotebookCellActionContribution implements MenuContribution, Command
keybinding: 'M',
when: `!editorTextFocus && ${NOTEBOOK_EDITOR_FOCUSED} && ${NOTEBOOK_CELL_FOCUSED} && ${NOTEBOOK_CELL_TYPE} == 'code'`,
},
{
command: NotebookCellCommands.SPLIT_CELL_COMMAND.id,
keybinding: KeyCode.createKeyCode({ first: Key.MINUS, modifiers: [KeyModifier.CtrlCmd, KeyModifier.Shift] }).toString(),
when: `editorTextFocus && ${NOTEBOOK_EDITOR_FOCUSED} && ${NOTEBOOK_CELL_FOCUSED}`,
}
);
}
}
Expand Down
14 changes: 14 additions & 0 deletions packages/notebook/src/browser/view-model/notebook-cell-model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,10 @@ export interface NotebookCell {
metadata: NotebookCellMetadata;
internalMetadata: NotebookCellInternalMetadata;
text: string;
/**
* The selection of the cell. Zero-based line/character coordinates.
*/
selection: Range | undefined;
onDidChangeOutputs?: Event<NotebookCellOutputsSplice>;
onDidChangeOutputItems?: Event<CellOutput>;
onDidChangeLanguage: Event<string>;
Expand Down Expand Up @@ -254,6 +258,16 @@ export class NotebookCellModel implements NotebookCell, Disposable {
}
}

protected _selection: Range | undefined = undefined;

get selection(): Range | undefined {
return this._selection;
}

set selection(selection: Range | undefined) {
this._selection = selection;
}

protected _cellheight: number = 0;
get cellHeight(): number {
return this._cellheight;
Expand Down
5 changes: 5 additions & 0 deletions packages/notebook/src/browser/view/notebook-cell-editor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,11 @@ export class CellEditor extends React.Component<CellEditorProps, {}> {

this.toDispose.push(this.editor.getControl().onDidChangeCursorSelection(e => {
const selectedText = this.editor!.getControl().getModel()!.getValueInRange(e.selection);
// TODO handle secondary selections
this.props.cell.selection = {
start: { line: e.selection.startLineNumber - 1, character: e.selection.startColumn - 1 },
end: { line: e.selection.endLineNumber - 1, character: e.selection.endColumn - 1 }
};
this.props.notebookModel.selectedText = selectedText;
}));
this.toDispose.push(this.editor.getControl().onDidChangeCursorPosition(e => {
Expand Down
Loading