提交 f48e190b 编写于 作者: R rebornix

cursor movement first cut

上级 35b51327
......@@ -425,14 +425,14 @@ export function registerAction2(ctor: { new(): Action2 }): IDisposable {
KeybindingsRegistry.registerKeybindingRule({
...item,
id: command.id,
when: ContextKeyExpr.and(command.precondition, item.when)
when: command.precondition ? ContextKeyExpr.and(command.precondition, item.when) : item.when
});
}
} else if (keybinding) {
KeybindingsRegistry.registerKeybindingRule({
...keybinding,
id: command.id,
when: ContextKeyExpr.and(command.precondition, keybinding.when)
when: command.precondition ? ContextKeyExpr.and(command.precondition, keybinding.when) : keybinding.when
});
}
......
......@@ -12,8 +12,8 @@ import { INotebookService } from 'vs/workbench/contrib/notebook/browser/notebook
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { NOTEBOOK_EDITOR_FOCUSED, NotebookEditor } from 'vs/workbench/contrib/notebook/browser/notebookEditor';
import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
import { KEYBINDING_CONTEXT_NOTEBOOK_FIND_WIDGET_FOCUSED } from 'vs/workbench/contrib/notebook/browser/notebookBrowser';
import { CellKind } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { KEYBINDING_CONTEXT_NOTEBOOK_FIND_WIDGET_FOCUSED, CellState } from 'vs/workbench/contrib/notebook/browser/notebookBrowser';
import { CellKind, NOTEBOOK_EDITOR_CURSOR_BOUNDARY } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { CellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookCellViewModel';
registerAction2(class extends Action2 {
......@@ -153,6 +153,10 @@ registerAction2(class extends Action2 {
let activeCell = editor.getActiveCell();
if (activeCell) {
if (activeCell.cellKind === CellKind.Markdown) {
activeCell.state = CellState.Read;
}
editor.focusNotebookCell(activeCell, false);
}
}
......@@ -361,3 +365,109 @@ function changeActiveCellToKind(kind: CellKind, accessor: ServicesAccessor): voi
editor.focusNotebookCell(newCell, false);
editor.deleteNotebookCell(activeCell);
}
function getActiveCell(accessor: ServicesAccessor): [NotebookEditor, CellViewModel] | undefined {
const editorService = accessor.get(IEditorService);
const notebookService = accessor.get(INotebookService);
const resource = editorService.activeEditor?.resource;
if (!resource) {
return;
}
const editor = getActiveNotebookEditor(editorService);
if (!editor) {
return;
}
const notebookProviders = notebookService.getContributedNotebookProviders(resource);
if (!notebookProviders.length) {
return;
}
const activeCell = editor.getActiveCell();
if (!activeCell) {
return;
}
return [editor, activeCell];
}
registerAction2(class extends Action2 {
constructor() {
super({
id: 'workbench.action.notebook.cursorDown',
title: 'Notebook Cursor Move Down',
keybinding: {
when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED, ContextKeyExpr.has(InputFocusedContextKey), NOTEBOOK_EDITOR_CURSOR_BOUNDARY.notEqualsTo('top'), NOTEBOOK_EDITOR_CURSOR_BOUNDARY.notEqualsTo('none')),
primary: KeyCode.DownArrow,
weight: KeybindingWeight.WorkbenchContrib
}
});
}
async run(accessor: ServicesAccessor): Promise<void> {
const activeCellRet = getActiveCell(accessor);
if (!activeCellRet) {
return;
}
const [editor, activeCell] = activeCellRet;
const idx = editor.viewModel?.getViewCellIndex(activeCell);
if (typeof idx !== 'number') {
return;
}
const newCell = editor.viewModel?.viewCells[idx + 1];
if (!newCell) {
return;
}
editor.focusNotebookCell(newCell, true);
}
});
registerAction2(class extends Action2 {
constructor() {
super({
id: 'workbench.action.notebook.cursorUp',
title: 'Notebook Cursor Move Up',
keybinding: {
when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED, ContextKeyExpr.has(InputFocusedContextKey), NOTEBOOK_EDITOR_CURSOR_BOUNDARY.notEqualsTo('bottom'), NOTEBOOK_EDITOR_CURSOR_BOUNDARY.notEqualsTo('none')),
primary: KeyCode.UpArrow,
weight: KeybindingWeight.WorkbenchContrib
},
});
}
async run(accessor: ServicesAccessor): Promise<void> {
const activeCellRet = getActiveCell(accessor);
if (!activeCellRet) {
return;
}
const [editor, activeCell] = activeCellRet;
const idx = editor.viewModel?.getViewCellIndex(activeCell);
if (typeof idx !== 'number') {
return;
}
if (idx < 1) {
// we don't do loop
return;
}
const newCell = editor.viewModel?.viewCells[idx - 1];
if (!newCell) {
return;
}
editor.focusNotebookCell(newCell, true);
}
});
......@@ -220,3 +220,15 @@ export enum CellState {
*/
Editing
}
export enum CellFocusMode {
Container,
Editor
}
export enum CursorAtBoundary {
None,
Top,
Bottom,
Both
}
......@@ -21,7 +21,7 @@ import { contrastBorder, editorBackground, focusBorder, foreground, textBlockQuo
import { IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService';
import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor';
import { EditorOptions, IEditorMemento, ICompositeCodeEditor, IEditorCloseEvent } from 'vs/workbench/common/editor';
import { INotebookEditor, NotebookLayoutInfo, CellState } from 'vs/workbench/contrib/notebook/browser/notebookBrowser';
import { INotebookEditor, NotebookLayoutInfo, CellState, CellFocusMode } from 'vs/workbench/contrib/notebook/browser/notebookBrowser';
import { NotebookEditorInput, NotebookEditorModel } from 'vs/workbench/contrib/notebook/browser/notebookEditorInput';
import { INotebookService } from 'vs/workbench/contrib/notebook/browser/notebookService';
import { OutputRenderer } from 'vs/workbench/contrib/notebook/browser/view/output/outputRenderer';
......@@ -186,6 +186,7 @@ export class NotebookEditor extends BaseEditor implements INotebookEditor {
this.body,
this.instantiationService.createInstance(NotebookCellListDelegate),
renders,
this.contextKeyService,
{
setRowLineHeight: false,
setRowHeight: false,
......@@ -212,7 +213,7 @@ export class NotebookEditor extends BaseEditor implements INotebookEditor {
listInactiveFocusBackground: editorBackground,
listInactiveFocusOutline: editorBackground,
}
}
},
);
this.control = new NotebookCodeEditors(this.list, this.renderedEditors);
......@@ -595,18 +596,22 @@ export class NotebookEditor extends BaseEditor implements INotebookEditor {
const index = this.notebookViewModel!.getViewCellIndex(cell);
if (focusEditor) {
this.list?.setFocus([index]);
this.list?.setSelection([index]);
this.list?.focusView();
cell.state = CellState.PreviewContent;
cell.focusMode = CellFocusMode.Editor;
} else {
let itemDOM = this.list?.domElementAtIndex(index);
if (document.activeElement && itemDOM && itemDOM.contains(document.activeElement)) {
(document.activeElement as HTMLElement).blur();
}
cell.state = CellState.Read;
this.list?.setFocus([index]);
this.list?.setSelection([index]);
this.list?.focusView();
}
this.list?.setFocus([index]);
this.list?.focusView();
}
//#endregion
......
......@@ -15,11 +15,12 @@ import { IThemeService } from 'vs/platform/theme/common/themeService';
import { IMouseWheelEvent } from 'vs/base/browser/mouseEvent';
import { isMacintosh } from 'vs/base/common/platform';
import { CellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookCellViewModel';
import { EDITOR_TOP_PADDING } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { EDITOR_TOP_PADDING, NOTEBOOK_EDITOR_CURSOR_BOUNDARY } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { Range } from 'vs/editor/common/core/range';
import { CellRevealType, CellRevealPosition } from 'vs/workbench/contrib/notebook/browser/notebookBrowser';
import { CellRevealType, CellRevealPosition, CursorAtBoundary } from 'vs/workbench/contrib/notebook/browser/notebookBrowser';
import { IDisposable, DisposableStore } from 'vs/base/common/lifecycle';
export class NotebookCellList extends WorkbenchList<CellViewModel> implements IDisposable {
get onWillScroll(): Event<ScrollEvent> { return this.view.onWillScroll; }
......@@ -34,8 +35,8 @@ export class NotebookCellList extends WorkbenchList<CellViewModel> implements ID
container: HTMLElement,
delegate: IListVirtualDelegate<CellViewModel>,
renderers: IListRenderer<CellViewModel, any>[],
contextKeyService: IContextKeyService,
options: IWorkbenchListOptions<CellViewModel>,
@IContextKeyService contextKeyService: IContextKeyService,
@IListService listService: IListService,
@IThemeService themeService: IThemeService,
@IConfigurationService configurationService: IConfigurationService,
......@@ -53,6 +54,48 @@ export class NotebookCellList extends WorkbenchList<CellViewModel> implements ID
});
this._previousSelectedElements = e.elements;
}));
const notebookEditorCursorAtBoundaryContext = NOTEBOOK_EDITOR_CURSOR_BOUNDARY.bindTo(contextKeyService);
notebookEditorCursorAtBoundaryContext.set('none');
let cursorSelectionLisener: IDisposable | null = null;
const recomputeContext = (element: CellViewModel) => {
switch (element.cursorAtBoundary()) {
case CursorAtBoundary.Both:
notebookEditorCursorAtBoundaryContext.set('both');
break;
case CursorAtBoundary.Top:
notebookEditorCursorAtBoundaryContext.set('top');
break;
case CursorAtBoundary.Bottom:
notebookEditorCursorAtBoundaryContext.set('bottom');
break;
default:
notebookEditorCursorAtBoundaryContext.set('none');
break;
}
return;
};
// Cursor Boundary context
this._localDisposableStore.add(this.onDidChangeFocus((e) => {
cursorSelectionLisener?.dispose();
if (e.elements.length) {
// we only validate the first focused element
const focusedElement = e.elements[0];
cursorSelectionLisener = focusedElement.onDidChangeCursorSelection(() => {
recomputeContext(focusedElement);
});
recomputeContext(focusedElement);
return;
}
// reset context
notebookEditorCursorAtBoundaryContext.set('none');
}));
}
domElementAtIndex(index: number): HTMLElement | null {
......
......@@ -9,7 +9,7 @@ import { Disposable, DisposableStore } from 'vs/base/common/lifecycle';
import { CellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookCellViewModel';
import { getResizesObserver } from 'vs/workbench/contrib/notebook/browser/view/renderers/sizeObserver';
import { CELL_MARGIN, IOutput, EDITOR_TOP_PADDING, EDITOR_BOTTOM_PADDING, ITransformedDisplayOutputDto, IRenderOutput, CellOutputKind } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { CellRenderTemplate, INotebookEditor } from 'vs/workbench/contrib/notebook/browser/notebookBrowser';
import { CellRenderTemplate, INotebookEditor, CellFocusMode } from 'vs/workbench/contrib/notebook/browser/notebookBrowser';
import { raceCancellation } from 'vs/base/common/async';
import { CancellationTokenSource } from 'vs/base/common/cancellation';
import { IQuickInputService, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput';
......@@ -86,6 +86,12 @@ export class CodeCell extends Disposable {
}
});
this._register(viewCell.onDidChangeFocusMode(() => {
if (viewCell.focusMode === CellFocusMode.Editor) {
templateData.editor?.focus();
}
}));
let cellWidthResizeObserver = getResizesObserver(templateData.cellContainer, {
width: width,
height: totalHeight
......
......@@ -10,7 +10,7 @@ import { IEditorOptions } from 'vs/editor/common/config/editorOptions';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { getResizesObserver } from 'vs/workbench/contrib/notebook/browser/view/renderers/sizeObserver';
import { CELL_MARGIN, EDITOR_TOP_PADDING, EDITOR_BOTTOM_PADDING } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { INotebookEditor, CellRenderTemplate } from 'vs/workbench/contrib/notebook/browser/notebookBrowser';
import { INotebookEditor, CellRenderTemplate, CellFocusMode } from 'vs/workbench/contrib/notebook/browser/notebookBrowser';
import { CancellationTokenSource } from 'vs/base/common/cancellation';
import { raceCancellation } from 'vs/base/common/async';
......@@ -194,6 +194,12 @@ export class StatefullMarkdownCell extends Disposable {
viewUpdate();
}));
this._register(viewCell.onDidChangeFocusMode(() => {
if (viewCell.focusMode === CellFocusMode.Editor) {
this.editor?.focus();
}
}));
viewUpdate();
}
......
......@@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import { Emitter, Event } from 'vs/base/common/event';
import { Disposable } from 'vs/base/common/lifecycle';
import { Disposable, IDisposable } from 'vs/base/common/lifecycle';
import * as UUID from 'vs/base/common/uuid';
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
import { Range } from 'vs/editor/common/core/range';
......@@ -16,7 +16,7 @@ import { PrefixSumComputer } from 'vs/editor/common/viewModel/prefixSumComputer'
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { MarkdownRenderer } from 'vs/workbench/contrib/notebook/browser/view/renderers/mdRenderer';
import { CellKind, EDITOR_BOTTOM_PADDING, EDITOR_TOP_PADDING, ICell, IOutput, NotebookCellOutputsSplice } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { CellFindMatch, CellState } from 'vs/workbench/contrib/notebook/browser/notebookBrowser';
import { CellFindMatch, CellState, CursorAtBoundary, CellFocusMode } from 'vs/workbench/contrib/notebook/browser/notebookBrowser';
export class CellViewModel extends Disposable {
......@@ -26,6 +26,8 @@ export class CellViewModel extends Disposable {
readonly onDidDispose = this._onDidDispose.event;
protected readonly _onDidChangeEditingState = new Emitter<void>();
readonly onDidChangeEditingState = this._onDidChangeEditingState.event;
protected readonly _onDidChangeFocusMode = new Emitter<void>();
readonly onDidChangeFocusMode = this._onDidChangeFocusMode.event;
protected readonly _onDidChangeOutputs = new Emitter<NotebookCellOutputsSplice[]>();
readonly onDidChangeOutputs = this._onDidChangeOutputs.event;
private _outputCollection: number[] = [];
......@@ -74,6 +76,17 @@ export class CellViewModel extends Disposable {
this._onDidChangeEditingState.fire();
}
private _focusMode: CellFocusMode = CellFocusMode.Container;
get focusMode() {
return this._focusMode;
}
set focusMode(newMode: CellFocusMode) {
this._focusMode = newMode;
this._onDidChangeFocusMode.fire();
}
private _selfSizeMonitoring: boolean = false;
set selfSizeMonitoring(newVal: boolean) {
......@@ -106,6 +119,10 @@ export class CellViewModel extends Disposable {
private _editorViewStates: editorCommon.ICodeEditorViewState | null;
private _lastDecorationId: number = 0;
private _resolvedDecorations = new Map<string, { id?: string, options: model.IModelDeltaDecoration }>();
private readonly _onDidChangeCursorSelection: Emitter<void> = this._register(new Emitter<void>());
public readonly onDidChangeCursorSelection: Event<void> = this._onDidChangeCursorSelection.event;
private _cursorChangeListener: IDisposable | null = null;
readonly id: string = UUID.generateUuid();
......@@ -300,6 +317,8 @@ export class CellViewModel extends Disposable {
}
});
this._cursorChangeListener = this._textEditor.onDidChangeCursorSelection(() => this._onDidChangeCursorSelection.fire());
this._onDidChangeCursorSelection.fire();
this._onDidChangeEditorAttachState.fire(true);
}
......@@ -315,6 +334,7 @@ export class CellViewModel extends Disposable {
}
});
this._textEditor = undefined;
this._cursorChangeListener?.dispose();
this._onDidChangeEditorAttachState.fire(false);
}
......@@ -373,11 +393,46 @@ export class CellViewModel extends Disposable {
}
onDeselect() {
if (this.state === CellState.PreviewContent) {
if (this.cellKind === CellKind.Code) {
this.state = CellState.Read;
} else if (this.state === CellState.PreviewContent) {
this.state = CellState.Read;
}
}
cursorAtBoundary(): CursorAtBoundary {
if (!this._textEditor) {
return CursorAtBoundary.None;
}
// only validate primary cursor
const selection = this._textEditor.getSelection();
// only validate empty cursor
if (!selection || !selection.isEmpty()) {
return CursorAtBoundary.None;
}
// we don't allow attaching text editor without a model
const lineCnt = this._textEditor.getModel()!.getLineCount();
if (selection.startLineNumber === lineCnt) {
// bottom
if (selection.startLineNumber === 1) {
return CursorAtBoundary.Both;
} else {
return CursorAtBoundary.Bottom;
}
}
if (selection.startLineNumber === 1) {
return CursorAtBoundary.Top;
}
return CursorAtBoundary.None;
}
getMarkdownRenderer() {
if (!this._mdRenderer) {
this._mdRenderer = this._instaService.createInstance(MarkdownRenderer);
......
......@@ -12,6 +12,7 @@ import { URI } from 'vs/base/common/uri';
import * as editorCommon from 'vs/editor/common/editorCommon';
import { PieceTreeTextBufferFactory } from 'vs/editor/common/model/pieceTreeTextBuffer/pieceTreeTextBufferBuilder';
import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions';
import { RawContextKey } from 'vs/platform/contextkey/common/contextkey';
export enum CellKind {
Markdown = 1,
......@@ -329,3 +330,5 @@ export function diff<T>(before: T[], after: T[], contains: (a: T) => boolean): I
export interface ICellEditorViewState {
selections: editorCommon.ICursorState[];
}
export const NOTEBOOK_EDITOR_CURSOR_BOUNDARY = new RawContextKey<'none' | 'top' | 'bottom' | 'both'>('notebookEditorCursorAtBoundary', 'none');
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册