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

Select next notebook cell on first or last line of editor #13656

Merged
merged 1 commit into from
Apr 29, 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 @@ -24,8 +24,9 @@ import { NotebookKernelQuickPickService } from '../service/notebook-kernel-quick
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 { NOTEBOOK_CELL_CURSOR_FIRST_LINE, NOTEBOOK_CELL_CURSOR_LAST_LINE, NOTEBOOK_CELL_FOCUSED, NOTEBOOK_EDITOR_FOCUSED, NOTEBOOK_HAS_OUTPUTS } from './notebook-context-keys';
import { NotebookClipboardService } from '../service/notebook-clipboard-service';
import { ContextKeyService } from '@theia/core/lib/browser/context-key-service';

export namespace NotebookCommands {
export const ADD_NEW_CELL_COMMAND = Command.toDefaultLocalizedCommand({
Expand Down Expand Up @@ -110,6 +111,9 @@ export class NotebookActionsContribution implements CommandContribution, MenuCon
@inject(NotebookClipboardService)
protected notebookClipboardService: NotebookClipboardService;

@inject(ContextKeyService)
protected contextKeyService: ContextKeyService;

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 @@ -169,15 +173,29 @@ export class NotebookActionsContribution implements CommandContribution, MenuCon
commands.registerCommand(NotebookCommands.CHANGE_SELECTED_CELL,
{
execute: (change: number | CellChangeDirection) => {
const model = this.notebookEditorWidgetService.focusedEditor?.model;
const focusedEditor = this.notebookEditorWidgetService.focusedEditor;
const model = focusedEditor?.model;
if (model && typeof change === 'number') {
model.setSelectedCell(model.cells[change]);
} else if (model && model.selectedCell) {
const currentIndex = model.cells.indexOf(model.selectedCell);
const shouldFocusEditor = this.contextKeyService.match('editorTextFocus');

if (change === CellChangeDirection.Up && currentIndex > 0) {
model.setSelectedCell(model.cells[currentIndex - 1]);
if (model.selectedCell?.cellKind === CellKind.Code && shouldFocusEditor) {
model.selectedCell.requestFocusEditor('lastLine');
}
} else if (change === CellChangeDirection.Down && currentIndex < model.cells.length - 1) {
model.setSelectedCell(model.cells[currentIndex + 1]);
if (model.selectedCell?.cellKind === CellKind.Code && shouldFocusEditor) {
model.selectedCell.requestFocusEditor();
}
}

if (model.selectedCell.cellKind === CellKind.Markup) {
// since were losing focus from the cell editor, we need to focus the notebook editor again
focusedEditor?.node.focus();
}
}
}
Expand Down Expand Up @@ -294,13 +312,13 @@ export class NotebookActionsContribution implements CommandContribution, MenuCon
command: NotebookCommands.CHANGE_SELECTED_CELL.id,
keybinding: 'up',
args: CellChangeDirection.Up,
when: `!editorTextFocus && ${NOTEBOOK_EDITOR_FOCUSED} && ${NOTEBOOK_CELL_FOCUSED}`
when: `(!editorTextFocus || ${NOTEBOOK_CELL_CURSOR_FIRST_LINE}) && ${NOTEBOOK_EDITOR_FOCUSED} && ${NOTEBOOK_CELL_FOCUSED}`
},
{
command: NotebookCommands.CHANGE_SELECTED_CELL.id,
keybinding: 'down',
args: CellChangeDirection.Down,
when: `!editorTextFocus && ${NOTEBOOK_EDITOR_FOCUSED} && ${NOTEBOOK_CELL_FOCUSED}`
when: `(!editorTextFocus || ${NOTEBOOK_CELL_CURSOR_LAST_LINE}) && ${NOTEBOOK_EDITOR_FOCUSED} && ${NOTEBOOK_CELL_FOCUSED}`
},
{
command: NotebookCommands.CUT_SELECTED_CELL.id,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,9 @@ export const NOTEBOOK_INTERRUPTIBLE_KERNEL = 'notebookInterruptibleKernel';
export const NOTEBOOK_MISSING_KERNEL_EXTENSION = 'notebookMissingKernelExtension';
export const NOTEBOOK_HAS_OUTPUTS = 'notebookHasOutputs';

export const NOTEBOOK_CELL_CURSOR_FIRST_LINE = 'cellEditorCursorPositionFirstLine';
export const NOTEBOOK_CELL_CURSOR_LAST_LINE = 'cellEditorCursorPositionLastLine';

export namespace NotebookContextKeys {
export function initNotebookContextKeys(service: ContextKeyService): void {
service.createKey(HAS_OPENED_NOTEBOOK, false);
Expand Down Expand Up @@ -93,6 +96,8 @@ export namespace NotebookContextKeys {
service.createKey(NOTEBOOK_CELL_INPUT_COLLAPSED, false);
service.createKey(NOTEBOOK_CELL_OUTPUT_COLLAPSED, false);
service.createKey(NOTEBOOK_CELL_RESOURCE, '');
service.createKey(NOTEBOOK_CELL_CURSOR_FIRST_LINE, false);
service.createKey(NOTEBOOK_CELL_CURSOR_LAST_LINE, false);

// Kernels
service.createKey(NOTEBOOK_KERNEL, undefined);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ import { LanguageService } from '@theia/core/lib/browser/language-service';
export const NotebookCellModelFactory = Symbol('NotebookModelFactory');
export type NotebookCellModelFactory = (props: NotebookCellModelProps) => NotebookCellModel;

export type CellEditorFocusRequest = number | 'lastLine' | undefined;

export function createNotebookCellModelContainer(parent: interfaces.Container, props: NotebookCellModelProps): interfaces.Container {
const child = parent.createChild();

Expand Down Expand Up @@ -104,7 +106,7 @@ export class NotebookCellModel implements NotebookCell, Disposable {
protected readonly onDidRequestCellEditChangeEmitter = new Emitter<boolean>();
readonly onDidRequestCellEditChange = this.onDidRequestCellEditChangeEmitter.event;

protected readonly onWillFocusCellEditorEmitter = new Emitter<void>();
protected readonly onWillFocusCellEditorEmitter = new Emitter<CellEditorFocusRequest>();
readonly onWillFocusCellEditor = this.onWillFocusCellEditorEmitter.event;

protected readonly onWillBlurCellEditorEmitter = new Emitter<void>();
Expand Down Expand Up @@ -278,9 +280,9 @@ export class NotebookCellModel implements NotebookCell, Disposable {
this.onDidRequestCellEditChangeEmitter.fire(false);
}

requestFocusEditor(): void {
requestFocusEditor(focusRequest?: CellEditorFocusRequest): void {
this.requestEdit();
this.onWillFocusCellEditorEmitter.fire();
this.onWillFocusCellEditorEmitter.fire(focusRequest);
}

requestBlurEditor(): void {
Expand Down
25 changes: 24 additions & 1 deletion packages/notebook/src/browser/view/notebook-cell-editor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import { NotebookContextManager } from '../service/notebook-context-manager';
import { DisposableCollection, OS } from '@theia/core';
import { NotebookViewportService } from './notebook-viewport-service';
import { BareFontInfo } from '@theia/monaco-editor-core/esm/vs/editor/common/config/fontInfo';
import { NOTEBOOK_CELL_CURSOR_FIRST_LINE, NOTEBOOK_CELL_CURSOR_LAST_LINE } from '../contributions/notebook-context-keys';

interface CellEditorProps {
notebookModel: NotebookModel,
Expand Down Expand Up @@ -54,8 +55,19 @@ export class CellEditor extends React.Component<CellEditorProps, {}> {

override componentDidMount(): void {
this.disposeEditor();
this.toDispose.push(this.props.cell.onWillFocusCellEditor(() => {
this.toDispose.push(this.props.cell.onWillFocusCellEditor(focusRequest => {
this.editor?.getControl().focus();
const lineCount = this.editor?.getControl().getModel()?.getLineCount();
if (focusRequest && lineCount) {
this.editor?.getControl().setPosition(focusRequest === 'lastLine' ?
{ lineNumber: lineCount, column: 1 } :
{ lineNumber: focusRequest, column: 1 },
'keyboard');
}
const currentLine = this.editor?.getControl().getPosition()?.lineNumber;
this.props.notebookContextManager.scopedStore.setContext(NOTEBOOK_CELL_CURSOR_FIRST_LINE, currentLine === 1);
this.props.notebookContextManager.scopedStore.setContext(NOTEBOOK_CELL_CURSOR_LAST_LINE, currentLine === lineCount);

}));

this.toDispose.push(this.props.cell.onDidChangeEditorOptions(options => {
Expand Down Expand Up @@ -119,10 +131,21 @@ export class CellEditor extends React.Component<CellEditorProps, {}> {
}));
this.toDispose.push(this.editor.getControl().onDidFocusEditorText(() => {
this.props.notebookContextManager.onDidEditorTextFocus(true);
this.props.notebookModel.setSelectedCell(cell);
}));
this.toDispose.push(this.editor.getControl().onDidBlurEditorText(() => {
this.props.notebookContextManager.onDidEditorTextFocus(false);
}));
this.toDispose.push(this.editor.getControl().onDidChangeCursorPosition(e => {
if (e.secondaryPositions.length === 0) {
this.props.notebookContextManager.scopedStore.setContext(NOTEBOOK_CELL_CURSOR_FIRST_LINE, e.position.lineNumber === 1);
this.props.notebookContextManager.scopedStore.setContext(NOTEBOOK_CELL_CURSOR_LAST_LINE,
e.position.lineNumber === this.editor!.getControl().getModel()!.getLineCount());
} else {
this.props.notebookContextManager.scopedStore.setContext(NOTEBOOK_CELL_CURSOR_FIRST_LINE, false);
this.props.notebookContextManager.scopedStore.setContext(NOTEBOOK_CELL_CURSOR_LAST_LINE, false);
}
}));
if (cell.editing && notebookModel.selectedCell === cell) {
this.editor.getControl().focus();
}
Expand Down
Loading