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

added alot more keybings to the notebook editor #13594

Merged
merged 4 commits into from
Apr 11, 2024
Merged
Show file tree
Hide file tree
Changes from 2 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
2 changes: 1 addition & 1 deletion packages/monaco/src/browser/monaco-editor-provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -211,7 +211,7 @@ export class MonacoEditorProvider {
return editor;
}

protected updateReadOnlyMessage(options: MonacoEditor.IOptions, readOnly: boolean | MarkdownString ): void {
protected updateReadOnlyMessage(options: MonacoEditor.IOptions, readOnly: boolean | MarkdownString): void {
options.readOnlyMessage = MarkdownString.is(readOnly) ? readOnly : undefined;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import { NotebookExecutionService } from '../service/notebook-execution-service'
import { NotebookEditorWidget } from '../notebook-editor-widget';
import { NotebookEditorWidgetService } from '../service/notebook-editor-widget-service';
import { NOTEBOOK_CELL_FOCUSED, NOTEBOOK_EDITOR_FOCUSED, NOTEBOOK_HAS_OUTPUTS } from './notebook-context-keys';
import { NotebookClipboardService } from '../service/notebook-clipboard-service';

export namespace NotebookCommands {
export const ADD_NEW_CELL_COMMAND = Command.toDefaultLocalizedCommand({
Expand Down Expand Up @@ -66,6 +67,26 @@ export namespace NotebookCommands {
id: 'notebook.change-selected-cell',
category: 'Notebook',
});

export const CUT_SELECTED_CELL = Command.toDefaultLocalizedCommand({
id: 'notebook.cut-selected-cell',
category: 'Notebook',
});

export const COPY_SELECTED_CELL = Command.toDefaultLocalizedCommand({
id: 'notebook.copy-selected-cell',
category: 'Notebook',
});

export const PASTE_CELL = Command.toDefaultLocalizedCommand({
id: 'notebook.paste-cell',
category: 'Notebook',
});

export const NOTEBOOK_UNDO = Command.toDefaultLocalizedCommand({
id: 'notebook.undo',
category: 'Notebook',
});
}

export enum CellChangeDirection {
Expand All @@ -91,6 +112,9 @@ export class NotebookActionsContribution implements CommandContribution, MenuCon
@inject(NotebookEditorWidgetService)
protected notebookEditorWidgetService: NotebookEditorWidgetService;

@inject(NotebookClipboardService)
protected notebookClipboardService: NotebookClipboardService;

registerCommands(commands: CommandRegistry): void {
commands.registerCommand(NotebookCommands.ADD_NEW_CELL_COMMAND, {
execute: (notebookModel: NotebookModel, cellKind: CellKind = CellKind.Markup, index?: number | 'above' | 'below') => {
Expand Down Expand Up @@ -180,6 +204,43 @@ export class NotebookActionsContribution implements CommandContribution, MenuCon
execute: () => (this.shell.activeWidget as NotebookEditorWidget).redo()
});

commands.registerCommand(NotebookCommands.CUT_SELECTED_CELL, this.editableCommandHandler(
() => {
const model = this.notebookEditorWidgetService.focusedEditor?.model;
const selectedCell = model?.selectedCell;
if (selectedCell) {
model.applyEdits([{ editType: CellEditType.Replace, index: model.cells.indexOf(selectedCell), count: 1, cells: [] }], true);
this.notebookClipboardService.copyCell(selectedCell);
}
}));

commands.registerCommand(NotebookCommands.COPY_SELECTED_CELL, {
execute: () => {
const model = this.notebookEditorWidgetService.focusedEditor?.model;
const selectedCell = model?.selectedCell;
if (selectedCell) {
this.notebookClipboardService.copyCell(selectedCell);
}
}
});

commands.registerCommand(NotebookCommands.PASTE_CELL, {
isEnabled: () => !Boolean(this.notebookEditorWidgetService.focusedEditor?.model?.readOnly),
isVisible: () => !Boolean(this.notebookEditorWidgetService.focusedEditor?.model?.readOnly),
execute: (position: 'above' | 'below') => {
jonah-iden marked this conversation as resolved.
Show resolved Hide resolved
const copiedCell = this.notebookClipboardService.getCell();
if (copiedCell) {
const model = this.notebookEditorWidgetService.focusedEditor?.model;
const insertIndex = model?.selectedCell ? model.cells.indexOf(model.selectedCell) + (position === 'above' ? 0 : 1) : 0;
model?.applyEdits([{ editType: CellEditType.Replace, index: insertIndex, count: 0, cells: [copiedCell] }], true);
}
}
});

commands.registerCommand(NotebookCommands.NOTEBOOK_UNDO, {
isEnabled: () => !Boolean(this.notebookEditorWidgetService.focusedEditor?.model?.readOnly),
execute: () => this.notebookEditorWidgetService.focusedEditor?.undo()
});
}

protected editableCommandHandler(execute: (notebookModel: NotebookModel) => void): CommandHandler {
Expand Down Expand Up @@ -223,7 +284,6 @@ export class NotebookActionsContribution implements CommandContribution, MenuCon
order: '30',
when: NOTEBOOK_HAS_OUTPUTS
});
// other items
}

registerKeybindings(keybindings: KeybindingRegistry): void {
Expand All @@ -240,6 +300,34 @@ export class NotebookActionsContribution implements CommandContribution, MenuCon
args: CellChangeDirection.Down,
when: `!editorTextFocus && ${NOTEBOOK_EDITOR_FOCUSED} && ${NOTEBOOK_CELL_FOCUSED}`
},
{
command: NotebookCommands.CUT_SELECTED_CELL.id,
keybinding: 'x',
when: `!editorTextFocus && ${NOTEBOOK_EDITOR_FOCUSED} && ${NOTEBOOK_CELL_FOCUSED}`
},
{
command: NotebookCommands.COPY_SELECTED_CELL.id,
keybinding: 'c',
when: `!editorTextFocus && ${NOTEBOOK_EDITOR_FOCUSED} && ${NOTEBOOK_CELL_FOCUSED}`
},
{
command: NotebookCommands.PASTE_CELL.id,
keybinding: 'v',
when: `!editorTextFocus && ${NOTEBOOK_EDITOR_FOCUSED} && ${NOTEBOOK_CELL_FOCUSED}`
},
{
command: NotebookCommands.PASTE_CELL.id,
keybinding: 'shift+v',
when: `!editorTextFocus && ${NOTEBOOK_EDITOR_FOCUSED} && ${NOTEBOOK_CELL_FOCUSED}`,
args: 'above'
},
{
command: NotebookCommands.NOTEBOOK_UNDO.id,
keybinding: 'z',
// notebook type check is to check that we are using the editors context
when: `!editorTextFocus && ${NOTEBOOK_EDITOR_FOCUSED} && notebookType`
},

);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import { NotebookCellModel } from '../view-model/notebook-cell-model';
import {
NOTEBOOK_CELL_MARKDOWN_EDIT_MODE, NOTEBOOK_CELL_TYPE,
NotebookContextKeys, NOTEBOOK_CELL_EXECUTING, NOTEBOOK_EDITOR_FOCUSED,
NOTEBOOK_CELL_FOCUSED, NOTEBOOK_CELL_EDITABLE
NOTEBOOK_CELL_FOCUSED
} from './notebook-context-keys';
import { ContextKeyService } from '@theia/core/lib/browser/context-key-service';
import { NotebookExecutionService } from '../service/notebook-execution-service';
Expand Down Expand Up @@ -118,6 +118,17 @@ export namespace NotebookCellCommands {
id: 'notebook.cell.to-markdown-cell',
label: 'Change Cell to Mardown'
});

export const TOGGLE_LINE_NUMBERS = Command.toDefaultLocalizedCommand({
id: 'notebook.toggle-line-numbers',
category: 'Notebook',
});

export const TOOGLE_OUTPUTS = Command.toDefaultLocalizedCommand({
id: 'notebook.toggle-outputs',
category: 'Notebook',
});

}

@injectable()
Expand Down Expand Up @@ -237,8 +248,8 @@ export class NotebookCellActionContribution implements MenuContribution, Command
}

registerCommands(commands: CommandRegistry): void {
commands.registerCommand(NotebookCellCommands.EDIT_COMMAND, this.editableCellCommandHandler((_, cell) => cell.requestEdit()));
commands.registerCommand(NotebookCellCommands.STOP_EDIT_COMMAND, { execute: (_, cell: NotebookCellModel) => (cell ?? this.getSelectedCell()).requestStopEdit() });
commands.registerCommand(NotebookCellCommands.EDIT_COMMAND, this.editableCellCommandHandler((_, cell) => cell.requestFocusEditor()));
commands.registerCommand(NotebookCellCommands.STOP_EDIT_COMMAND, { execute: (_, cell: NotebookCellModel) => (cell ?? this.getSelectedCell()).requestBlurEditor() });
commands.registerCommand(NotebookCellCommands.DELETE_COMMAND,
this.editableCellCommandHandler((notebookModel, cell) => {
notebookModel.applyEdits([{
Expand Down Expand Up @@ -327,6 +338,25 @@ export class NotebookCellActionContribution implements MenuContribution, Command
commands.registerCommand(NotebookCellCommands.TO_MARKDOWN_CELL_COMMAND, this.editableCellCommandHandler((notebookModel, cell) => {
changeCellType(notebookModel, cell, CellKind.Markup);
}));

commands.registerCommand(NotebookCellCommands.TOGGLE_LINE_NUMBERS, {
execute: () => {
const selectedCell = this.notebookEditorWidgetService.focusedEditor?.model?.selectedCell;
if (selectedCell) {
selectedCell.editorOptions = { ...selectedCell.editorOptions, lineNumbers: selectedCell.editorOptions?.lineNumbers === 'on' ? 'off' : 'on' };
}
}
});

commands.registerCommand(NotebookCellCommands.TOOGLE_OUTPUTS, {
execute: () => {
const selectedCell = this.notebookEditorWidgetService.focusedEditor?.model?.selectedCell;
if (selectedCell) {
selectedCell.outputVisible = !selectedCell.outputVisible;
}
}
});

}

protected editableCellCommandHandler(execute: (notebookModel: NotebookModel, cell: NotebookCellModel, output?: NotebookCellOutputModel) => void): CommandHandler {
Expand All @@ -350,13 +380,18 @@ export class NotebookCellActionContribution implements MenuContribution, Command
{
command: NotebookCellCommands.EDIT_COMMAND.id,
keybinding: 'Enter',
when: `${NOTEBOOK_EDITOR_FOCUSED} && ${NOTEBOOK_CELL_FOCUSED} && ${NOTEBOOK_CELL_EDITABLE}`,
when: `${NOTEBOOK_EDITOR_FOCUSED} && ${NOTEBOOK_CELL_FOCUSED}`,
},
{
command: NotebookCellCommands.STOP_EDIT_COMMAND.id,
keybinding: KeyCode.createKeyCode({ first: Key.ENTER, modifiers: [KeyModifier.Alt] }).toString(),
when: `editorTextFocus && ${NOTEBOOK_EDITOR_FOCUSED}`,
},
{
command: NotebookCellCommands.STOP_EDIT_COMMAND.id,
keybinding: 'esc',
when: `editorTextFocus && ${NOTEBOOK_EDITOR_FOCUSED}`,
},
{
command: NotebookCellCommands.EXECUTE_SINGLE_CELL_COMMAND.id,
keybinding: KeyCode.createKeyCode({ first: Key.ENTER, modifiers: [KeyModifier.CtrlCmd] }).toString(),
Expand Down Expand Up @@ -386,6 +421,16 @@ export class NotebookCellActionContribution implements MenuContribution, Command
command: NotebookCellCommands.TO_MARKDOWN_CELL_COMMAND.id,
keybinding: 'M',
when: `!editorTextFocus && ${NOTEBOOK_EDITOR_FOCUSED} && ${NOTEBOOK_CELL_FOCUSED} && ${NOTEBOOK_CELL_TYPE} == 'code'`,
},
{
command: NotebookCellCommands.TOGGLE_LINE_NUMBERS.id,
keybinding: 'L',
when: `!editorTextFocus && ${NOTEBOOK_EDITOR_FOCUSED} && ${NOTEBOOK_CELL_FOCUSED} && ${NOTEBOOK_CELL_TYPE} == 'code'`,
},
{
command: NotebookCellCommands.TOOGLE_OUTPUTS.id,
keybinding: 'O',
when: `!editorTextFocus && ${NOTEBOOK_EDITOR_FOCUSED} && ${NOTEBOOK_CELL_FOCUSED} && ${NOTEBOOK_CELL_TYPE} == 'code'`,
}
);
}
Expand Down
2 changes: 2 additions & 0 deletions packages/notebook/src/browser/notebook-editor-widget.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,8 @@ export class NotebookEditorWidget extends ReactWidget implements Navigatable, Sa
this.commandRegistry.executeCommand(NotebookCellCommands.EDIT_COMMAND.id, model, model.cells[0]);
model.setSelectedCell(model.cells[0]);
}
model.cells.forEach(cell => cell.onWillBlurCellEditor(() => this.node.focus()));
model.onDidAddOrRemoveCell(e => e.newCellIds?.forEach(cellId => model.cells.find(cell => cell.handle === cellId)?.onWillBlurCellEditor(() => this.node.focus())));
});
}

Expand Down
2 changes: 2 additions & 0 deletions packages/notebook/src/browser/notebook-frontend-module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ import { NotebookMonacoTextModelService } from './service/notebook-monaco-text-m
import { NotebookOutlineContribution } from './contributions/notebook-outline-contribution';
import { NotebookLabelProviderContribution } from './contributions/notebook-label-provider-contribution';
import { NotebookOutputActionContribution } from './contributions/notebook-output-action-contribution';
import { NotebookClipboardService } from './service/notebook-clipboard-service';

export default new ContainerModule((bind, unbind, isBound, rebind) => {
bind(NotebookColorContribution).toSelf().inSingletonScope();
Expand All @@ -64,6 +65,7 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
bind(NotebookRendererMessagingService).toSelf().inSingletonScope();
bind(NotebookKernelHistoryService).toSelf().inSingletonScope();
bind(NotebookKernelQuickPickService).toSelf().inSingletonScope();
bind(NotebookClipboardService).toSelf().inSingletonScope();

bind(NotebookCellResourceResolver).toSelf().inSingletonScope();
bind(ResourceResolver).toService(NotebookCellResourceResolver);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
// *****************************************************************************
// Copyright (C) 2024 Typefox and others.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// http://www.eclipse.org/legal/epl-2.0.
//
// This Source Code may also be made available under the following Secondary
// Licenses when the conditions for such availability set forth in the Eclipse
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
// with the GNU Classpath Exception which is available at
// https://www.gnu.org/software/classpath/license.html.
//
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
// *****************************************************************************

import { inject, injectable } from '@theia/core/shared/inversify';
import { ClipboardService } from '@theia/core/lib/browser/clipboard-service';
import { NotebookCellModel } from '../view-model/notebook-cell-model';
import { environment } from '@theia/core';
import { CellData } from '../../common';

@injectable()
export class NotebookClipboardService {

protected copiedCell: CellData | undefined;

@inject(ClipboardService)
protected readonly clipboardService: ClipboardService;

copyCell(cell: NotebookCellModel): void {
this.copiedCell = cell.getData();

if (environment.electron.is()) {
this.clipboardService.writeText(cell.text);
}
}

getCell(): CellData | undefined {
return this.copiedCell;
}

}
37 changes: 37 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 @@ -21,6 +21,7 @@
import { Disposable, DisposableCollection, Emitter, Event, URI } from '@theia/core';
import { inject, injectable, interfaces, postConstruct } from '@theia/core/shared/inversify';
import { MonacoEditorModel } from '@theia/monaco/lib/browser/monaco-editor-model';
import { type MonacoEditor } from '@theia/monaco/lib/browser/monaco-editor';
import {
CellKind, NotebookCellCollapseState, NotebookCellInternalMetadata,
NotebookCellMetadata, CellOutput, CellData, CellOutputItem
Expand Down Expand Up @@ -103,6 +104,15 @@ export class NotebookCellModel implements NotebookCell, Disposable {
protected readonly onWillFocusCellEditorEmitter = new Emitter<void>();
readonly onWillFocusCellEditor = this.onWillFocusCellEditorEmitter.event;

protected readonly onWillBlurCellEditorEmitter = new Emitter<void>();
readonly onWillBlurCellEditor = this.onWillBlurCellEditorEmitter.event;

protected readonly onDidChangeEditorOptionsEmitter = new Emitter<MonacoEditor.IOptions>();
readonly onDidChangeEditorOptions: Event<MonacoEditor.IOptions> = this.onDidChangeEditorOptionsEmitter.event;

protected readonly outputVisibilityChangeEmitter = new Emitter<boolean>();
readonly onDidChangeOutputVisibility: Event<boolean> = this.outputVisibilityChangeEmitter.event;

@inject(NotebookCellModelProps)
protected readonly props: NotebookCellModelProps;

Expand Down Expand Up @@ -192,6 +202,28 @@ export class NotebookCellModel implements NotebookCell, Disposable {
return this._editing;
}

protected _editorOptions: MonacoEditor.IOptions = {};
get editorOptions(): Readonly<MonacoEditor.IOptions> {
return this._editorOptions;
}

set editorOptions(options: MonacoEditor.IOptions) {
this._editorOptions = options;
this.onDidChangeEditorOptionsEmitter.fire(options);
}

protected _outputVisible: boolean = true;
get outputVisible(): boolean {
return this._outputVisible;
}

set outputVisible(visible: boolean) {
if (this._outputVisible !== visible) {
this._outputVisible = visible;
this.outputVisibilityChangeEmitter.fire(visible);
}
}

@postConstruct()
protected init(): void {
this._outputs = this.props.outputs.map(op => new NotebookCellOutputModel(op));
Expand Down Expand Up @@ -227,6 +259,11 @@ export class NotebookCellModel implements NotebookCell, Disposable {
this.onWillFocusCellEditorEmitter.fire();
}

requestBlurEditor(): void {
this.requestStopEdit();
this.onWillBlurCellEditorEmitter.fire();
}

spliceNotebookCellOutputs(splice: NotebookCellOutputsSplice): void {
if (splice.deleteCount > 0 && splice.newOutputs.length > 0) {
const commonLen = Math.min(splice.deleteCount, splice.newOutputs.length);
Expand Down
2 changes: 1 addition & 1 deletion packages/notebook/src/browser/view-model/notebook-model.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// *****************************************************************************
// Copyright (C) 20023 Typefox and others.
// Copyright (C) 2023 Typefox and others.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
Expand Down
Loading
Loading