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

Let core command such as undo/redo be overridden by webviews and notebooks #98288

Merged
merged 9 commits into from
Jun 19, 2020
235 changes: 86 additions & 149 deletions src/vs/editor/browser/controller/coreCommands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import * as nls from 'vs/nls';
import { KeyCode, KeyMod } from 'vs/base/common/keyCodes';
import * as types from 'vs/base/common/types';
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
import { Command, EditorCommand, ICommandOptions, registerEditorCommand } from 'vs/editor/browser/editorExtensions';
import { Command, EditorCommand, ICommandOptions, registerEditorCommand, MultiCommand, UndoCommand, RedoCommand, SelectAllCommand } from 'vs/editor/browser/editorExtensions';
import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService';
import { ColumnSelection, IColumnSelectResult } from 'vs/editor/common/controller/cursorColumnSelection';
import { CursorState, EditOperationType, IColumnSelectData, PartialCursorState } from 'vs/editor/common/controller/cursorCommon';
Expand All @@ -20,7 +20,6 @@ import { Range } from 'vs/editor/common/core/range';
import { Handler, ScrollType } from 'vs/editor/common/editorCommon';
import { EditorContextKeys } from 'vs/editor/common/editorContextKeys';
import { VerticalRevealType } from 'vs/editor/common/view/viewEvents';
import { MenuId } from 'vs/platform/actions/common/actions';
import { ICommandHandlerDescription } from 'vs/platform/commands/common/commands';
import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
Expand Down Expand Up @@ -276,6 +275,48 @@ export namespace RevealLine_ {
};
}

abstract class EditorOrNativeTextInputCommand {

constructor(target: MultiCommand) {
// 1. handle case when focus is in editor.
target.addImplementation(10000, (accessor: ServicesAccessor, args: any) => {
// Only if editor text focus (i.e. not if editor has widget focus).
const focusedEditor = accessor.get(ICodeEditorService).getFocusedCodeEditor();
if (focusedEditor && focusedEditor.hasTextFocus()) {
this.runEditorCommand(accessor, focusedEditor, args);
return true;
}
return false;
});

// 2. handle case when focus is in some other `input` / `textarea`.
target.addImplementation(1000, (accessor: ServicesAccessor, args: any) => {
// Only if focused on an element that allows for entering text
const activeElement = <HTMLElement>document.activeElement;
if (activeElement && ['input', 'textarea'].indexOf(activeElement.tagName.toLowerCase()) >= 0) {
this.runDOMCommand();
return true;
}
return false;
});

// 3. (default) handle case when focus is somewhere else.
target.addImplementation(0, (accessor: ServicesAccessor, args: any) => {
// Redirecting to active editor
const activeEditor = accessor.get(ICodeEditorService).getActiveCodeEditor();
if (activeEditor) {
activeEditor.focus();
this.runEditorCommand(accessor, activeEditor, args);
return true;
}
return false;
});
}

public abstract runDOMCommand(): void;
public abstract runEditorCommand(accessor: ServicesAccessor | null, editor: ICodeEditor, args: any): void;
}

export namespace CoreNavigationCommands {

class BaseMoveToCommand extends CoreEditorCommand {
Expand Down Expand Up @@ -1594,25 +1635,32 @@ export namespace CoreNavigationCommands {
}
});

export const SelectAll: CoreEditorCommand = registerEditorCommand(new class extends CoreEditorCommand {
export const SelectAll = new class extends EditorOrNativeTextInputCommand {
constructor() {
super({
id: 'selectAll',
precondition: undefined
});
super(SelectAllCommand);
}
public runDOMCommand(): void {
document.execCommand('selectAll');
}
public runEditorCommand(accessor: ServicesAccessor, editor: ICodeEditor, args: any): void {
const viewModel = editor._getViewModel();
if (!viewModel) {
// the editor has no view => has no cursors
return;
}
this.runCoreEditorCommand(viewModel, args);
}

public runCoreEditorCommand(viewModel: IViewModel, args: any): void {
viewModel.model.pushStackElement();
viewModel.setCursorStates(
args.source,
'keyboard',
CursorChangeReason.Explicit,
[
CursorMoveCommands.selectAll(viewModel, viewModel.getPrimaryCursorState())
]
);
}
});
}();

export const SetSelection: CoreEditorCommand = registerEditorCommand(new class extends CoreEditorCommand {
constructor() {
Expand Down Expand Up @@ -1655,97 +1703,6 @@ registerColumnSelection(CoreNavigationCommands.CursorColumnSelectPageUp.id, KeyM
registerColumnSelection(CoreNavigationCommands.CursorColumnSelectDown.id, KeyMod.Shift | KeyCode.DownArrow);
registerColumnSelection(CoreNavigationCommands.CursorColumnSelectPageDown.id, KeyMod.Shift | KeyCode.PageDown);

/**
* A command that will:
* 1. invoke a command on the focused editor.
* 2. otherwise, invoke a browser built-in command on the `activeElement`.
* 3. otherwise, invoke a command on the workbench active editor.
*/
abstract class EditorOrNativeTextInputCommand extends Command {

public runCommand(accessor: ServicesAccessor, args: any): void {

const focusedEditor = accessor.get(ICodeEditorService).getFocusedCodeEditor();
// Only if editor text focus (i.e. not if editor has widget focus).
if (focusedEditor && focusedEditor.hasTextFocus()) {
return this.runEditorCommand(accessor, focusedEditor, args);
}

// Ignore this action when user is focused on an element that allows for entering text
const activeElement = <HTMLElement>document.activeElement;
if (activeElement && ['input', 'textarea'].indexOf(activeElement.tagName.toLowerCase()) >= 0) {
return this.runDOMCommand();
}

// Redirecting to active editor
const activeEditor = accessor.get(ICodeEditorService).getActiveCodeEditor();
if (activeEditor) {
activeEditor.focus();
return this.runEditorCommand(accessor, activeEditor, args);
}
}

public abstract runDOMCommand(): void;
public abstract runEditorCommand(accessor: ServicesAccessor, editor: ICodeEditor, args: any): void;
}

class SelectAllCommand extends EditorOrNativeTextInputCommand {
constructor() {
super({
id: 'editor.action.selectAll',
precondition: EditorContextKeys.textInputFocus,
kbOpts: {
weight: CORE_WEIGHT,
kbExpr: null,
primary: KeyMod.CtrlCmd | KeyCode.KEY_A
},
menuOpts: [{
menuId: MenuId.MenubarSelectionMenu,
group: '1_basic',
title: nls.localize({ key: 'miSelectAll', comment: ['&& denotes a mnemonic'] }, "&&Select All"),
order: 1
}, {
menuId: MenuId.CommandPalette,
group: '',
title: nls.localize('selectAll', "Select All"),
order: 1
}]
});
}
public runDOMCommand(): void {
document.execCommand('selectAll');
}
public runEditorCommand(accessor: ServicesAccessor, editor: ICodeEditor, args: any): void {
args = args || {};
args.source = 'keyboard';
CoreNavigationCommands.SelectAll.runEditorCommand(accessor, editor, args);
}
}

class UndoCommand extends EditorOrNativeTextInputCommand {
public runDOMCommand(): void {
document.execCommand('undo');
}
public runEditorCommand(accessor: ServicesAccessor | null, editor: ICodeEditor, args: any): void {
if (!editor.hasModel() || editor.getOption(EditorOption.readOnly) === true) {
return;
}
editor.getModel().undo();
}
}

class RedoCommand extends EditorOrNativeTextInputCommand {
public runDOMCommand(): void {
document.execCommand('redo');
}
public runEditorCommand(accessor: ServicesAccessor | null, editor: ICodeEditor, args: any): void {
if (!editor.hasModel() || editor.getOption(EditorOption.readOnly) === true) {
return;
}
editor.getModel().redo();
}
}

function registerCommand<T extends Command>(command: T): T {
command.register();
return command;
Expand Down Expand Up @@ -1881,53 +1838,35 @@ export namespace CoreEditingCommands {
}
});

export const Undo: UndoCommand = registerCommand(new UndoCommand({
id: 'undo',
precondition: EditorContextKeys.writable,
kbOpts: {
weight: CORE_WEIGHT,
kbExpr: EditorContextKeys.textInputFocus,
primary: KeyMod.CtrlCmd | KeyCode.KEY_Z
},
menuOpts: [{
menuId: MenuId.MenubarEditMenu,
group: '1_do',
title: nls.localize({ key: 'miUndo', comment: ['&& denotes a mnemonic'] }, "&&Undo"),
order: 1
}, {
menuId: MenuId.CommandPalette,
group: '',
title: nls.localize('undo', "Undo"),
order: 1
}]
}));

export const DefaultUndo: UndoCommand = registerCommand(new UndoCommand({ id: 'default:undo', precondition: EditorContextKeys.writable }));

export const Redo: RedoCommand = registerCommand(new RedoCommand({
id: 'redo',
precondition: EditorContextKeys.writable,
kbOpts: {
weight: CORE_WEIGHT,
kbExpr: EditorContextKeys.textInputFocus,
primary: KeyMod.CtrlCmd | KeyCode.KEY_Y,
secondary: [KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_Z],
mac: { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_Z }
},
menuOpts: [{
menuId: MenuId.MenubarEditMenu,
group: '1_do',
title: nls.localize({ key: 'miRedo', comment: ['&& denotes a mnemonic'] }, "&&Redo"),
order: 2
}, {
menuId: MenuId.CommandPalette,
group: '',
title: nls.localize('redo', "Redo"),
order: 1
}]
}));
export const Undo = new class extends EditorOrNativeTextInputCommand {
constructor() {
super(UndoCommand);
}
public runDOMCommand(): void {
document.execCommand('undo');
}
public runEditorCommand(accessor: ServicesAccessor | null, editor: ICodeEditor, args: any): void {
if (!editor.hasModel() || editor.getOption(EditorOption.readOnly) === true) {
return;
}
editor.getModel().undo();
}
}();

export const DefaultRedo: RedoCommand = registerCommand(new RedoCommand({ id: 'default:redo', precondition: EditorContextKeys.writable }));
export const Redo = new class extends EditorOrNativeTextInputCommand {
constructor() {
super(RedoCommand);
}
public runDOMCommand(): void {
document.execCommand('redo');
}
public runEditorCommand(accessor: ServicesAccessor | null, editor: ICodeEditor, args: any): void {
if (!editor.hasModel() || editor.getOption(EditorOption.readOnly) === true) {
return;
}
editor.getModel().redo();
}
}();
}

/**
Expand Down Expand Up @@ -1956,8 +1895,6 @@ class EditorHandlerCommand extends Command {
}
}

registerCommand(new SelectAllCommand());

function registerOverwritableCommand(handlerId: string, description?: ICommandHandlerDescription): void {
registerCommand(new EditorHandlerCommand('default:' + handlerId, handlerId));
registerCommand(new EditorHandlerCommand(handlerId, handlerId, description));
Expand Down
Loading