Skip to content

Commit f48e190

Browse files
committed
cursor movement first cut
1 parent 35b5132 commit f48e190

9 files changed

Lines changed: 258 additions & 18 deletions

File tree

src/vs/platform/actions/common/actions.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -425,14 +425,14 @@ export function registerAction2(ctor: { new(): Action2 }): IDisposable {
425425
KeybindingsRegistry.registerKeybindingRule({
426426
...item,
427427
id: command.id,
428-
when: ContextKeyExpr.and(command.precondition, item.when)
428+
when: command.precondition ? ContextKeyExpr.and(command.precondition, item.when) : item.when
429429
});
430430
}
431431
} else if (keybinding) {
432432
KeybindingsRegistry.registerKeybindingRule({
433433
...keybinding,
434434
id: command.id,
435-
when: ContextKeyExpr.and(command.precondition, keybinding.when)
435+
when: command.precondition ? ContextKeyExpr.and(command.precondition, keybinding.when) : keybinding.when
436436
});
437437
}
438438

src/vs/workbench/contrib/notebook/browser/contrib/notebookActions.ts

Lines changed: 112 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@ import { INotebookService } from 'vs/workbench/contrib/notebook/browser/notebook
1212
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
1313
import { NOTEBOOK_EDITOR_FOCUSED, NotebookEditor } from 'vs/workbench/contrib/notebook/browser/notebookEditor';
1414
import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
15-
import { KEYBINDING_CONTEXT_NOTEBOOK_FIND_WIDGET_FOCUSED } from 'vs/workbench/contrib/notebook/browser/notebookBrowser';
16-
import { CellKind } from 'vs/workbench/contrib/notebook/common/notebookCommon';
15+
import { KEYBINDING_CONTEXT_NOTEBOOK_FIND_WIDGET_FOCUSED, CellState } from 'vs/workbench/contrib/notebook/browser/notebookBrowser';
16+
import { CellKind, NOTEBOOK_EDITOR_CURSOR_BOUNDARY } from 'vs/workbench/contrib/notebook/common/notebookCommon';
1717
import { CellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookCellViewModel';
1818

1919
registerAction2(class extends Action2 {
@@ -153,6 +153,10 @@ registerAction2(class extends Action2 {
153153

154154
let activeCell = editor.getActiveCell();
155155
if (activeCell) {
156+
if (activeCell.cellKind === CellKind.Markdown) {
157+
activeCell.state = CellState.Read;
158+
}
159+
156160
editor.focusNotebookCell(activeCell, false);
157161
}
158162
}
@@ -361,3 +365,109 @@ function changeActiveCellToKind(kind: CellKind, accessor: ServicesAccessor): voi
361365
editor.focusNotebookCell(newCell, false);
362366
editor.deleteNotebookCell(activeCell);
363367
}
368+
369+
function getActiveCell(accessor: ServicesAccessor): [NotebookEditor, CellViewModel] | undefined {
370+
const editorService = accessor.get(IEditorService);
371+
const notebookService = accessor.get(INotebookService);
372+
373+
const resource = editorService.activeEditor?.resource;
374+
if (!resource) {
375+
return;
376+
}
377+
378+
const editor = getActiveNotebookEditor(editorService);
379+
if (!editor) {
380+
return;
381+
}
382+
383+
const notebookProviders = notebookService.getContributedNotebookProviders(resource);
384+
if (!notebookProviders.length) {
385+
return;
386+
}
387+
388+
const activeCell = editor.getActiveCell();
389+
if (!activeCell) {
390+
return;
391+
}
392+
393+
return [editor, activeCell];
394+
}
395+
396+
397+
registerAction2(class extends Action2 {
398+
constructor() {
399+
super({
400+
id: 'workbench.action.notebook.cursorDown',
401+
title: 'Notebook Cursor Move Down',
402+
keybinding: {
403+
when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED, ContextKeyExpr.has(InputFocusedContextKey), NOTEBOOK_EDITOR_CURSOR_BOUNDARY.notEqualsTo('top'), NOTEBOOK_EDITOR_CURSOR_BOUNDARY.notEqualsTo('none')),
404+
primary: KeyCode.DownArrow,
405+
weight: KeybindingWeight.WorkbenchContrib
406+
}
407+
});
408+
}
409+
410+
async run(accessor: ServicesAccessor): Promise<void> {
411+
const activeCellRet = getActiveCell(accessor);
412+
413+
if (!activeCellRet) {
414+
return;
415+
}
416+
417+
const [editor, activeCell] = activeCellRet;
418+
419+
const idx = editor.viewModel?.getViewCellIndex(activeCell);
420+
if (typeof idx !== 'number') {
421+
return;
422+
}
423+
424+
const newCell = editor.viewModel?.viewCells[idx + 1];
425+
426+
if (!newCell) {
427+
return;
428+
}
429+
430+
editor.focusNotebookCell(newCell, true);
431+
}
432+
});
433+
434+
registerAction2(class extends Action2 {
435+
constructor() {
436+
super({
437+
id: 'workbench.action.notebook.cursorUp',
438+
title: 'Notebook Cursor Move Up',
439+
keybinding: {
440+
when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED, ContextKeyExpr.has(InputFocusedContextKey), NOTEBOOK_EDITOR_CURSOR_BOUNDARY.notEqualsTo('bottom'), NOTEBOOK_EDITOR_CURSOR_BOUNDARY.notEqualsTo('none')),
441+
primary: KeyCode.UpArrow,
442+
weight: KeybindingWeight.WorkbenchContrib
443+
},
444+
});
445+
}
446+
447+
async run(accessor: ServicesAccessor): Promise<void> {
448+
const activeCellRet = getActiveCell(accessor);
449+
450+
if (!activeCellRet) {
451+
return;
452+
}
453+
454+
const [editor, activeCell] = activeCellRet;
455+
const idx = editor.viewModel?.getViewCellIndex(activeCell);
456+
if (typeof idx !== 'number') {
457+
return;
458+
}
459+
460+
if (idx < 1) {
461+
// we don't do loop
462+
return;
463+
}
464+
465+
const newCell = editor.viewModel?.viewCells[idx - 1];
466+
467+
if (!newCell) {
468+
return;
469+
}
470+
471+
editor.focusNotebookCell(newCell, true);
472+
}
473+
});

src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -220,3 +220,15 @@ export enum CellState {
220220
*/
221221
Editing
222222
}
223+
224+
export enum CellFocusMode {
225+
Container,
226+
Editor
227+
}
228+
229+
export enum CursorAtBoundary {
230+
None,
231+
Top,
232+
Bottom,
233+
Both
234+
}

src/vs/workbench/contrib/notebook/browser/notebookEditor.ts

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ import { contrastBorder, editorBackground, focusBorder, foreground, textBlockQuo
2121
import { IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService';
2222
import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor';
2323
import { EditorOptions, IEditorMemento, ICompositeCodeEditor, IEditorCloseEvent } from 'vs/workbench/common/editor';
24-
import { INotebookEditor, NotebookLayoutInfo, CellState } from 'vs/workbench/contrib/notebook/browser/notebookBrowser';
24+
import { INotebookEditor, NotebookLayoutInfo, CellState, CellFocusMode } from 'vs/workbench/contrib/notebook/browser/notebookBrowser';
2525
import { NotebookEditorInput, NotebookEditorModel } from 'vs/workbench/contrib/notebook/browser/notebookEditorInput';
2626
import { INotebookService } from 'vs/workbench/contrib/notebook/browser/notebookService';
2727
import { OutputRenderer } from 'vs/workbench/contrib/notebook/browser/view/output/outputRenderer';
@@ -186,6 +186,7 @@ export class NotebookEditor extends BaseEditor implements INotebookEditor {
186186
this.body,
187187
this.instantiationService.createInstance(NotebookCellListDelegate),
188188
renders,
189+
this.contextKeyService,
189190
{
190191
setRowLineHeight: false,
191192
setRowHeight: false,
@@ -212,7 +213,7 @@ export class NotebookEditor extends BaseEditor implements INotebookEditor {
212213
listInactiveFocusBackground: editorBackground,
213214
listInactiveFocusOutline: editorBackground,
214215
}
215-
}
216+
},
216217
);
217218

218219
this.control = new NotebookCodeEditors(this.list, this.renderedEditors);
@@ -595,18 +596,22 @@ export class NotebookEditor extends BaseEditor implements INotebookEditor {
595596
const index = this.notebookViewModel!.getViewCellIndex(cell);
596597

597598
if (focusEditor) {
599+
this.list?.setFocus([index]);
600+
this.list?.setSelection([index]);
601+
this.list?.focusView();
598602

603+
cell.state = CellState.PreviewContent;
604+
cell.focusMode = CellFocusMode.Editor;
599605
} else {
600606
let itemDOM = this.list?.domElementAtIndex(index);
601607
if (document.activeElement && itemDOM && itemDOM.contains(document.activeElement)) {
602608
(document.activeElement as HTMLElement).blur();
603609
}
604610

605-
cell.state = CellState.Read;
611+
this.list?.setFocus([index]);
612+
this.list?.setSelection([index]);
613+
this.list?.focusView();
606614
}
607-
608-
this.list?.setFocus([index]);
609-
this.list?.focusView();
610615
}
611616

612617
//#endregion

src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts

Lines changed: 46 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,12 @@ import { IThemeService } from 'vs/platform/theme/common/themeService';
1515
import { IMouseWheelEvent } from 'vs/base/browser/mouseEvent';
1616
import { isMacintosh } from 'vs/base/common/platform';
1717
import { CellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookCellViewModel';
18-
import { EDITOR_TOP_PADDING } from 'vs/workbench/contrib/notebook/common/notebookCommon';
18+
import { EDITOR_TOP_PADDING, NOTEBOOK_EDITOR_CURSOR_BOUNDARY } from 'vs/workbench/contrib/notebook/common/notebookCommon';
1919
import { Range } from 'vs/editor/common/core/range';
20-
import { CellRevealType, CellRevealPosition } from 'vs/workbench/contrib/notebook/browser/notebookBrowser';
20+
import { CellRevealType, CellRevealPosition, CursorAtBoundary } from 'vs/workbench/contrib/notebook/browser/notebookBrowser';
2121
import { IDisposable, DisposableStore } from 'vs/base/common/lifecycle';
2222

23+
2324
export class NotebookCellList extends WorkbenchList<CellViewModel> implements IDisposable {
2425
get onWillScroll(): Event<ScrollEvent> { return this.view.onWillScroll; }
2526

@@ -34,8 +35,8 @@ export class NotebookCellList extends WorkbenchList<CellViewModel> implements ID
3435
container: HTMLElement,
3536
delegate: IListVirtualDelegate<CellViewModel>,
3637
renderers: IListRenderer<CellViewModel, any>[],
38+
contextKeyService: IContextKeyService,
3739
options: IWorkbenchListOptions<CellViewModel>,
38-
@IContextKeyService contextKeyService: IContextKeyService,
3940
@IListService listService: IListService,
4041
@IThemeService themeService: IThemeService,
4142
@IConfigurationService configurationService: IConfigurationService,
@@ -53,6 +54,48 @@ export class NotebookCellList extends WorkbenchList<CellViewModel> implements ID
5354
});
5455
this._previousSelectedElements = e.elements;
5556
}));
57+
58+
const notebookEditorCursorAtBoundaryContext = NOTEBOOK_EDITOR_CURSOR_BOUNDARY.bindTo(contextKeyService);
59+
notebookEditorCursorAtBoundaryContext.set('none');
60+
61+
let cursorSelectionLisener: IDisposable | null = null;
62+
63+
const recomputeContext = (element: CellViewModel) => {
64+
switch (element.cursorAtBoundary()) {
65+
case CursorAtBoundary.Both:
66+
notebookEditorCursorAtBoundaryContext.set('both');
67+
break;
68+
case CursorAtBoundary.Top:
69+
notebookEditorCursorAtBoundaryContext.set('top');
70+
break;
71+
case CursorAtBoundary.Bottom:
72+
notebookEditorCursorAtBoundaryContext.set('bottom');
73+
break;
74+
default:
75+
notebookEditorCursorAtBoundaryContext.set('none');
76+
break;
77+
}
78+
return;
79+
};
80+
81+
// Cursor Boundary context
82+
this._localDisposableStore.add(this.onDidChangeFocus((e) => {
83+
cursorSelectionLisener?.dispose();
84+
if (e.elements.length) {
85+
// we only validate the first focused element
86+
const focusedElement = e.elements[0];
87+
88+
cursorSelectionLisener = focusedElement.onDidChangeCursorSelection(() => {
89+
recomputeContext(focusedElement);
90+
});
91+
recomputeContext(focusedElement);
92+
return;
93+
}
94+
95+
// reset context
96+
notebookEditorCursorAtBoundaryContext.set('none');
97+
}));
98+
5699
}
57100

58101
domElementAtIndex(index: number): HTMLElement | null {

src/vs/workbench/contrib/notebook/browser/view/renderers/codeCell.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import { Disposable, DisposableStore } from 'vs/base/common/lifecycle';
99
import { CellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookCellViewModel';
1010
import { getResizesObserver } from 'vs/workbench/contrib/notebook/browser/view/renderers/sizeObserver';
1111
import { CELL_MARGIN, IOutput, EDITOR_TOP_PADDING, EDITOR_BOTTOM_PADDING, ITransformedDisplayOutputDto, IRenderOutput, CellOutputKind } from 'vs/workbench/contrib/notebook/common/notebookCommon';
12-
import { CellRenderTemplate, INotebookEditor } from 'vs/workbench/contrib/notebook/browser/notebookBrowser';
12+
import { CellRenderTemplate, INotebookEditor, CellFocusMode } from 'vs/workbench/contrib/notebook/browser/notebookBrowser';
1313
import { raceCancellation } from 'vs/base/common/async';
1414
import { CancellationTokenSource } from 'vs/base/common/cancellation';
1515
import { IQuickInputService, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput';
@@ -86,6 +86,12 @@ export class CodeCell extends Disposable {
8686
}
8787
});
8888

89+
this._register(viewCell.onDidChangeFocusMode(() => {
90+
if (viewCell.focusMode === CellFocusMode.Editor) {
91+
templateData.editor?.focus();
92+
}
93+
}));
94+
8995
let cellWidthResizeObserver = getResizesObserver(templateData.cellContainer, {
9096
width: width,
9197
height: totalHeight

src/vs/workbench/contrib/notebook/browser/view/renderers/markdownCell.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import { IEditorOptions } from 'vs/editor/common/config/editorOptions';
1010
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
1111
import { getResizesObserver } from 'vs/workbench/contrib/notebook/browser/view/renderers/sizeObserver';
1212
import { CELL_MARGIN, EDITOR_TOP_PADDING, EDITOR_BOTTOM_PADDING } from 'vs/workbench/contrib/notebook/common/notebookCommon';
13-
import { INotebookEditor, CellRenderTemplate } from 'vs/workbench/contrib/notebook/browser/notebookBrowser';
13+
import { INotebookEditor, CellRenderTemplate, CellFocusMode } from 'vs/workbench/contrib/notebook/browser/notebookBrowser';
1414
import { CancellationTokenSource } from 'vs/base/common/cancellation';
1515
import { raceCancellation } from 'vs/base/common/async';
1616

@@ -194,6 +194,12 @@ export class StatefullMarkdownCell extends Disposable {
194194
viewUpdate();
195195
}));
196196

197+
this._register(viewCell.onDidChangeFocusMode(() => {
198+
if (viewCell.focusMode === CellFocusMode.Editor) {
199+
this.editor?.focus();
200+
}
201+
}));
202+
197203
viewUpdate();
198204
}
199205

0 commit comments

Comments
 (0)