diff --git a/src/vs/editor/browser/controller/coreCommands.ts b/src/vs/editor/browser/controller/coreCommands.ts index f56dff9bd9f..2013ad47528 100644 --- a/src/vs/editor/browser/controller/coreCommands.ts +++ b/src/vs/editor/browser/controller/coreCommands.ts @@ -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'; @@ -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'; @@ -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 = 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 { @@ -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() { @@ -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 = 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(command: T): T { command.register(); return command; @@ -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 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 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 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(); + } + }(); } /** @@ -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)); diff --git a/src/vs/editor/browser/editorExtensions.ts b/src/vs/editor/browser/editorExtensions.ts index faf4f3ebc8d..50a7f678d5a 100644 --- a/src/vs/editor/browser/editorExtensions.ts +++ b/src/vs/editor/browser/editorExtensions.ts @@ -3,6 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import * as nls from 'vs/nls'; import { IPosition } from 'vs/base/browser/ui/contextview/contextview'; import { illegalArgument } from 'vs/base/common/errors'; import { URI } from 'vs/base/common/uri'; @@ -17,11 +18,13 @@ import { MenuId, MenuRegistry } from 'vs/platform/actions/common/actions'; import { CommandsRegistry, ICommandHandlerDescription } from 'vs/platform/commands/common/commands'; import { ContextKeyExpr, IContextKeyService, ContextKeyExpression } from 'vs/platform/contextkey/common/contextkey'; import { IConstructorSignature1, ServicesAccessor as InstantiationServicesAccessor, BrandedService } from 'vs/platform/instantiation/common/instantiation'; -import { IKeybindings, KeybindingsRegistry } from 'vs/platform/keybinding/common/keybindingsRegistry'; +import { IKeybindings, KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { Registry } from 'vs/platform/registry/common/platform'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { withNullAsUndefined, assertType } from 'vs/base/common/types'; import { ThemeIcon } from 'vs/platform/theme/common/themeService'; +import { IDisposable } from 'vs/base/common/lifecycle'; +import { KeyMod, KeyCode } from 'vs/base/common/keyCodes'; export type ServicesAccessor = InstantiationServicesAccessor; @@ -139,6 +142,66 @@ export abstract class Command { //#endregion Command +//#region MultiplexingCommand + +/** + * Potential override for a command. + * + * @return `true` if the command was successfully run. This stops other overrides from being executed. + */ +export type CommandImplementation = (accessor: ServicesAccessor, args: unknown) => boolean; + +export class MultiCommand extends Command { + + private readonly _implementations: [number, CommandImplementation][] = []; + + /** + * A higher priority gets to be looked at first + */ + public addImplementation(priority: number, implementation: CommandImplementation): IDisposable { + this._implementations.push([priority, implementation]); + this._implementations.sort((a, b) => b[0] - a[0]); + return { + dispose: () => { + for (let i = 0; i < this._implementations.length; i++) { + if (this._implementations[i][1] === implementation) { + this._implementations.splice(i, 1); + return; + } + } + } + }; + } + + public runCommand(accessor: ServicesAccessor, args: any): void | Promise { + for (const impl of this._implementations) { + if (impl[1](accessor, args)) { + return; + } + } + } +} + +//#endregion + +/** + * A command that delegates to another command's implementation. + * + * This lets different commands be registered but share the same implementation + */ +export class ProxyCommand extends Command { + constructor( + private readonly command: Command, + opts: ICommandOptions + ) { + super(opts); + } + + public runCommand(accessor: ServicesAccessor, args: any): void | Promise { + return this.command.runCommand(accessor, args); + } +} + //#region EditorCommand export interface IContributionCommandOptions extends ICommandOptions { @@ -379,8 +442,10 @@ export function registerEditorCommand(editorCommand: T) return editorCommand; } -export function registerEditorAction(ctor: { new(): EditorAction; }): void { - EditorContributionRegistry.INSTANCE.registerEditorAction(new ctor()); +export function registerEditorAction(ctor: { new(): T; }): T { + const action = new ctor(); + EditorContributionRegistry.INSTANCE.registerEditorAction(action); + return action; } export function registerInstantiatedEditorAction(editorAction: EditorAction): void { @@ -475,3 +540,75 @@ class EditorContributionRegistry { } Registry.add(Extensions.EditorCommonContributions, EditorContributionRegistry.INSTANCE); + +function registerCommand(command: T): T { + command.register(); + return command; +} + +export const UndoCommand = registerCommand(new MultiCommand({ + id: 'undo', + precondition: undefined, + kbOpts: { + weight: KeybindingWeight.EditorCore, + 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 + }] +})); + +registerCommand(new ProxyCommand(UndoCommand, { id: 'default:undo', precondition: undefined })); + +export const RedoCommand = registerCommand(new MultiCommand({ + id: 'redo', + precondition: undefined, + kbOpts: { + weight: KeybindingWeight.EditorCore, + 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 + }] +})); + +registerCommand(new ProxyCommand(RedoCommand, { id: 'default:redo', precondition: undefined })); + +export const SelectAllCommand = registerCommand(new MultiCommand({ + id: 'editor.action.selectAll', + precondition: undefined, + kbOpts: { + weight: KeybindingWeight.EditorCore, + 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 + }] +})); diff --git a/src/vs/editor/browser/view/viewController.ts b/src/vs/editor/browser/view/viewController.ts index 6fc163d9c36..69880b0133d 100644 --- a/src/vs/editor/browser/view/viewController.ts +++ b/src/vs/editor/browser/view/viewController.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent'; -import { CoreEditorCommand, CoreNavigationCommands } from 'vs/editor/browser/controller/coreCommands'; +import { CoreNavigationCommands } from 'vs/editor/browser/controller/coreCommands'; import { IEditorMouseEvent, IPartialEditorMouseEvent } from 'vs/editor/browser/editorBrowser'; import { ViewUserInputEvents } from 'vs/editor/browser/view/viewUserInputEvents'; import { Position } from 'vs/editor/common/core/position'; @@ -35,8 +35,6 @@ export interface IMouseDispatchData { } export interface ICommandDelegate { - executeEditorCommand(editorCommand: CoreEditorCommand, args: any): void; - paste(text: string, pasteOnNewLine: boolean, multicursorText: string[] | null, mode: string | null): void; type(text: string): void; replacePreviousChar(text: string, replaceCharCnt: number): void; @@ -64,11 +62,6 @@ export class ViewController { this.commandDelegate = commandDelegate; } - private _execMouseCommand(editorCommand: CoreEditorCommand, args: any): void { - args.source = 'mouse'; - this.commandDelegate.executeEditorCommand(editorCommand, args); - } - public paste(text: string, pasteOnNewLine: boolean, multicursorText: string[] | null, mode: string | null): void { this.commandDelegate.paste(text, pasteOnNewLine, multicursorText, mode); } @@ -94,7 +87,7 @@ export class ViewController { } public setSelection(modelSelection: Selection): void { - this.commandDelegate.executeEditorCommand(CoreNavigationCommands.SetSelection, { + CoreNavigationCommands.SetSelection.runCoreEditorCommand(this.viewModel, { source: 'keyboard', selection: modelSelection }); @@ -214,22 +207,24 @@ export class ViewController { private _usualArgs(viewPosition: Position) { viewPosition = this._validateViewColumn(viewPosition); return { + source: 'mouse', position: this._convertViewToModelPosition(viewPosition), viewPosition: viewPosition }; } public moveTo(viewPosition: Position): void { - this._execMouseCommand(CoreNavigationCommands.MoveTo, this._usualArgs(viewPosition)); + CoreNavigationCommands.MoveTo.runCoreEditorCommand(this.viewModel, this._usualArgs(viewPosition)); } private _moveToSelect(viewPosition: Position): void { - this._execMouseCommand(CoreNavigationCommands.MoveToSelect, this._usualArgs(viewPosition)); + CoreNavigationCommands.MoveToSelect.runCoreEditorCommand(this.viewModel, this._usualArgs(viewPosition)); } private _columnSelect(viewPosition: Position, mouseColumn: number, doColumnSelect: boolean): void { viewPosition = this._validateViewColumn(viewPosition); - this._execMouseCommand(CoreNavigationCommands.ColumnSelect, { + CoreNavigationCommands.ColumnSelect.runCoreEditorCommand(this.viewModel, { + source: 'mouse', position: this._convertViewToModelPosition(viewPosition), viewPosition: viewPosition, mouseColumn: mouseColumn, @@ -239,7 +234,8 @@ export class ViewController { private _createCursor(viewPosition: Position, wholeLine: boolean): void { viewPosition = this._validateViewColumn(viewPosition); - this._execMouseCommand(CoreNavigationCommands.CreateCursor, { + CoreNavigationCommands.CreateCursor.runCoreEditorCommand(this.viewModel, { + source: 'mouse', position: this._convertViewToModelPosition(viewPosition), viewPosition: viewPosition, wholeLine: wholeLine @@ -247,39 +243,39 @@ export class ViewController { } private _lastCursorMoveToSelect(viewPosition: Position): void { - this._execMouseCommand(CoreNavigationCommands.LastCursorMoveToSelect, this._usualArgs(viewPosition)); + CoreNavigationCommands.LastCursorMoveToSelect.runCoreEditorCommand(this.viewModel, this._usualArgs(viewPosition)); } private _wordSelect(viewPosition: Position): void { - this._execMouseCommand(CoreNavigationCommands.WordSelect, this._usualArgs(viewPosition)); + CoreNavigationCommands.WordSelect.runCoreEditorCommand(this.viewModel, this._usualArgs(viewPosition)); } private _wordSelectDrag(viewPosition: Position): void { - this._execMouseCommand(CoreNavigationCommands.WordSelectDrag, this._usualArgs(viewPosition)); + CoreNavigationCommands.WordSelectDrag.runCoreEditorCommand(this.viewModel, this._usualArgs(viewPosition)); } private _lastCursorWordSelect(viewPosition: Position): void { - this._execMouseCommand(CoreNavigationCommands.LastCursorWordSelect, this._usualArgs(viewPosition)); + CoreNavigationCommands.LastCursorWordSelect.runCoreEditorCommand(this.viewModel, this._usualArgs(viewPosition)); } private _lineSelect(viewPosition: Position): void { - this._execMouseCommand(CoreNavigationCommands.LineSelect, this._usualArgs(viewPosition)); + CoreNavigationCommands.LineSelect.runCoreEditorCommand(this.viewModel, this._usualArgs(viewPosition)); } private _lineSelectDrag(viewPosition: Position): void { - this._execMouseCommand(CoreNavigationCommands.LineSelectDrag, this._usualArgs(viewPosition)); + CoreNavigationCommands.LineSelectDrag.runCoreEditorCommand(this.viewModel, this._usualArgs(viewPosition)); } private _lastCursorLineSelect(viewPosition: Position): void { - this._execMouseCommand(CoreNavigationCommands.LastCursorLineSelect, this._usualArgs(viewPosition)); + CoreNavigationCommands.LastCursorLineSelect.runCoreEditorCommand(this.viewModel, this._usualArgs(viewPosition)); } private _lastCursorLineSelectDrag(viewPosition: Position): void { - this._execMouseCommand(CoreNavigationCommands.LastCursorLineSelectDrag, this._usualArgs(viewPosition)); + CoreNavigationCommands.LastCursorLineSelectDrag.runCoreEditorCommand(this.viewModel, this._usualArgs(viewPosition)); } private _selectAll(): void { - this._execMouseCommand(CoreNavigationCommands.SelectAll, {}); + CoreNavigationCommands.SelectAll.runCoreEditorCommand(this.viewModel, { source: 'mouse' }); } // ---------------------- diff --git a/src/vs/editor/browser/widget/codeEditorWidget.ts b/src/vs/editor/browser/widget/codeEditorWidget.ts index f57ab7693d6..32cbd06f69e 100644 --- a/src/vs/editor/browser/widget/codeEditorWidget.ts +++ b/src/vs/editor/browser/widget/codeEditorWidget.ts @@ -15,7 +15,6 @@ import { hash } from 'vs/base/common/hash'; import { Disposable, IDisposable, dispose } from 'vs/base/common/lifecycle'; import { Schemas } from 'vs/base/common/network'; import { Configuration } from 'vs/editor/browser/config/configuration'; -import { CoreEditorCommand } from 'vs/editor/browser/controller/coreCommands'; import * as editorBrowser from 'vs/editor/browser/editorBrowser'; import { EditorExtensionsRegistry, IEditorContributionDescription } from 'vs/editor/browser/editorExtensions'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; @@ -1552,9 +1551,6 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE let commandDelegate: ICommandDelegate; if (this.isSimpleWidget) { commandDelegate = { - executeEditorCommand: (editorCommand: CoreEditorCommand, args: any): void => { - editorCommand.runCoreEditorCommand(viewModel, args); - }, paste: (text: string, pasteOnNewLine: boolean, multicursorText: string[] | null, mode: string | null) => { this._paste('keyboard', text, pasteOnNewLine, multicursorText, mode); }, @@ -1576,9 +1572,6 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE }; } else { commandDelegate = { - executeEditorCommand: (editorCommand: CoreEditorCommand, args: any): void => { - editorCommand.runCoreEditorCommand(viewModel, args); - }, paste: (text: string, pasteOnNewLine: boolean, multicursorText: string[] | null, mode: string | null) => { const payload: editorCommon.PastePayload = { text, pasteOnNewLine, multicursorText, mode }; this._commandService.executeCommand(editorCommon.Handler.Paste, payload); diff --git a/src/vs/editor/contrib/clipboard/clipboard.ts b/src/vs/editor/contrib/clipboard/clipboard.ts index 4ad6007754a..0f685415d4b 100644 --- a/src/vs/editor/contrib/clipboard/clipboard.ts +++ b/src/vs/editor/contrib/clipboard/clipboard.ts @@ -9,7 +9,7 @@ import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import * as platform from 'vs/base/common/platform'; import { CopyOptions } from 'vs/editor/browser/controller/textAreaInput'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; -import { EditorAction, IActionOptions, ICommandKeybindingsOptions, registerEditorAction } from 'vs/editor/browser/editorExtensions'; +import { EditorAction, registerEditorAction, Command, MultiCommand } from 'vs/editor/browser/editorExtensions'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; import { MenuId } from 'vs/platform/actions/common/actions'; @@ -28,171 +28,111 @@ const supportsCopyWithSyntaxHighlighting = (supportsCopy && !browser.isEdge); // privileges to actually perform the action const supportsPaste = (platform.isNative || (!browser.isChrome && document.queryCommandSupported('paste'))); -type ExecCommand = 'cut' | 'copy' | 'paste'; - -abstract class ExecCommandAction extends EditorAction { - - private readonly browserCommand: ExecCommand; - - constructor(browserCommand: ExecCommand, opts: IActionOptions) { - super(opts); - this.browserCommand = browserCommand; - } - - public runCommand(accessor: ServicesAccessor, args: any): void { - let focusedEditor = accessor.get(ICodeEditorService).getFocusedCodeEditor(); - // Only if editor text focus (i.e. not if editor has widget focus). - if (focusedEditor && focusedEditor.hasTextFocus()) { - focusedEditor.trigger('keyboard', this.id, args); - return; - } - - document.execCommand(this.browserCommand); - } - - public run(accessor: ServicesAccessor, editor: ICodeEditor): void { - editor.focus(); - document.execCommand(this.browserCommand); - } +function registerCommand(command: T): T { + command.register(); + return command; } -class ExecCommandCutAction extends ExecCommandAction { - - constructor() { - let kbOpts: ICommandKeybindingsOptions | undefined = { +export const CutAction = supportsCut ? registerCommand(new MultiCommand({ + id: 'editor.action.clipboardCutAction', + precondition: undefined, + kbOpts: ( + // Do not bind cut keybindings in the browser, + // since browsers do that for us and it avoids security prompts + platform.isNative ? { kbExpr: EditorContextKeys.textInputFocus, primary: KeyMod.CtrlCmd | KeyCode.KEY_X, win: { primary: KeyMod.CtrlCmd | KeyCode.KEY_X, secondary: [KeyMod.Shift | KeyCode.Delete] }, weight: KeybindingWeight.EditorContrib - }; - // Do not bind cut keybindings in the browser, + } : undefined + ), + menuOpts: [{ + menuId: MenuId.MenubarEditMenu, + group: '2_ccp', + title: nls.localize({ key: 'miCut', comment: ['&& denotes a mnemonic'] }, "Cu&&t"), + order: 1 + }, { + menuId: MenuId.EditorContext, + group: CLIPBOARD_CONTEXT_MENU_GROUP, + title: nls.localize('actions.clipboard.cutLabel', "Cut"), + when: EditorContextKeys.writable, + order: 1, + }, { + menuId: MenuId.CommandPalette, + group: '', + title: nls.localize('actions.clipboard.cutLabel', "Cut"), + order: 1 + }] +})) : undefined; + +export const CopyAction = supportsCopy ? registerCommand(new MultiCommand({ + id: 'editor.action.clipboardCopyAction', + precondition: undefined, + kbOpts: ( + // Do not bind copy keybindings in the browser, // since browsers do that for us and it avoids security prompts - if (!platform.isNative) { - kbOpts = undefined; - } - super('cut', { - id: 'editor.action.clipboardCutAction', - label: nls.localize('actions.clipboard.cutLabel', "Cut"), - alias: 'Cut', - precondition: EditorContextKeys.writable, - kbOpts: kbOpts, - contextMenuOpts: { - group: CLIPBOARD_CONTEXT_MENU_GROUP, - order: 1 - }, - menuOpts: { - menuId: MenuId.MenubarEditMenu, - group: '2_ccp', - title: nls.localize({ key: 'miCut', comment: ['&& denotes a mnemonic'] }, "Cu&&t"), - order: 1 - } - }); - } - - public run(accessor: ServicesAccessor, editor: ICodeEditor): void { - if (!editor.hasModel()) { - return; - } - - const emptySelectionClipboard = editor.getOption(EditorOption.emptySelectionClipboard); - - if (!emptySelectionClipboard && editor.getSelection().isEmpty()) { - return; - } - - super.run(accessor, editor); - } -} - -class ExecCommandCopyAction extends ExecCommandAction { - - constructor() { - let kbOpts: ICommandKeybindingsOptions | undefined = { + platform.isNative ? { kbExpr: EditorContextKeys.textInputFocus, primary: KeyMod.CtrlCmd | KeyCode.KEY_C, win: { primary: KeyMod.CtrlCmd | KeyCode.KEY_C, secondary: [KeyMod.CtrlCmd | KeyCode.Insert] }, weight: KeybindingWeight.EditorContrib - }; - // Do not bind copy keybindings in the browser, + } : undefined + ), + menuOpts: [{ + menuId: MenuId.MenubarEditMenu, + group: '2_ccp', + title: nls.localize({ key: 'miCopy', comment: ['&& denotes a mnemonic'] }, "&&Copy"), + order: 2 + }, { + menuId: MenuId.EditorContext, + group: CLIPBOARD_CONTEXT_MENU_GROUP, + title: nls.localize('actions.clipboard.copyLabel', "Copy"), + order: 2, + }, { + menuId: MenuId.CommandPalette, + group: '', + title: nls.localize('actions.clipboard.copyLabel', "Copy"), + order: 1 + }] +})) : undefined; + +export const PasteAction = supportsPaste ? registerCommand(new MultiCommand({ + id: 'editor.action.clipboardPasteAction', + precondition: undefined, + kbOpts: ( + // Do not bind paste keybindings in the browser, // since browsers do that for us and it avoids security prompts - if (!platform.isNative) { - kbOpts = undefined; - } - - super('copy', { - id: 'editor.action.clipboardCopyAction', - label: nls.localize('actions.clipboard.copyLabel', "Copy"), - alias: 'Copy', - precondition: undefined, - kbOpts: kbOpts, - contextMenuOpts: { - group: CLIPBOARD_CONTEXT_MENU_GROUP, - order: 2 - }, - menuOpts: { - menuId: MenuId.MenubarEditMenu, - group: '2_ccp', - title: nls.localize({ key: 'miCopy', comment: ['&& denotes a mnemonic'] }, "&&Copy"), - order: 2 - } - }); - } - - public run(accessor: ServicesAccessor, editor: ICodeEditor): void { - if (!editor.hasModel()) { - return; - } - - const emptySelectionClipboard = editor.getOption(EditorOption.emptySelectionClipboard); - - if (!emptySelectionClipboard && editor.getSelection().isEmpty()) { - return; - } - - super.run(accessor, editor); - } -} - -class ExecCommandPasteAction extends ExecCommandAction { - - constructor() { - let kbOpts: ICommandKeybindingsOptions | undefined = { + platform.isNative ? { kbExpr: EditorContextKeys.textInputFocus, primary: KeyMod.CtrlCmd | KeyCode.KEY_V, win: { primary: KeyMod.CtrlCmd | KeyCode.KEY_V, secondary: [KeyMod.Shift | KeyCode.Insert] }, linux: { primary: KeyMod.CtrlCmd | KeyCode.KEY_V, secondary: [KeyMod.Shift | KeyCode.Insert] }, weight: KeybindingWeight.EditorContrib - }; - // Do not bind paste keybindings in the browser, - // since browsers do that for us and it avoids security prompts - if (!platform.isNative) { - kbOpts = undefined; - } + } : undefined + ), + menuOpts: [{ + menuId: MenuId.MenubarEditMenu, + group: '2_ccp', + title: nls.localize({ key: 'miPaste', comment: ['&& denotes a mnemonic'] }, "&&Paste"), + order: 3 + }, { + menuId: MenuId.EditorContext, + group: CLIPBOARD_CONTEXT_MENU_GROUP, + title: nls.localize('actions.clipboard.pasteLabel', "Paste"), + when: EditorContextKeys.writable, + order: 3, + }, { + menuId: MenuId.CommandPalette, + group: '', + title: nls.localize('actions.clipboard.pasteLabel', "Paste"), + order: 1 + }] +})) : undefined; - super('paste', { - id: 'editor.action.clipboardPasteAction', - label: nls.localize('actions.clipboard.pasteLabel', "Paste"), - alias: 'Paste', - precondition: EditorContextKeys.writable, - kbOpts: kbOpts, - contextMenuOpts: { - group: CLIPBOARD_CONTEXT_MENU_GROUP, - order: 3 - }, - menuOpts: { - menuId: MenuId.MenubarEditMenu, - group: '2_ccp', - title: nls.localize({ key: 'miPaste', comment: ['&& denotes a mnemonic'] }, "&&Paste"), - order: 3 - } - }); - } -} - -class ExecCommandCopyWithSyntaxHighlightingAction extends ExecCommandAction { +class ExecCommandCopyWithSyntaxHighlightingAction extends EditorAction { constructor() { - super('copy', { + super({ id: 'editor.action.clipboardCopyWithSyntaxHighlightingAction', label: nls.localize('actions.clipboard.copyWithSyntaxHighlightingLabel', "Copy With Syntax Highlighting"), alias: 'Copy With Syntax Highlighting', @@ -217,20 +157,48 @@ class ExecCommandCopyWithSyntaxHighlightingAction extends ExecCommandAction { } CopyOptions.forceCopyWithSyntaxHighlighting = true; - super.run(accessor, editor); + editor.focus(); + document.execCommand('copy'); CopyOptions.forceCopyWithSyntaxHighlighting = false; } } -if (supportsCut) { - registerEditorAction(ExecCommandCutAction); -} -if (supportsCopy) { - registerEditorAction(ExecCommandCopyAction); -} -if (supportsPaste) { - registerEditorAction(ExecCommandPasteAction); +function registerExecCommandImpl(target: MultiCommand | undefined, browserCommand: 'cut' | 'copy' | 'paste'): void { + if (!target) { + return; + } + + // 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()) { + if (browserCommand === 'cut' || browserCommand === 'copy') { + // Do not execute if there is no selection and empty selection clipboard is off + const emptySelectionClipboard = focusedEditor.getOption(EditorOption.emptySelectionClipboard); + const selection = focusedEditor.getSelection(); + if (selection && selection.isEmpty() && !emptySelectionClipboard) { + return true; + } + } + document.execCommand(browserCommand); + return true; + } + return false; + }); + + // 2. (default) handle case when focus is somewhere else. + target.addImplementation(0, (accessor: ServicesAccessor, args: any) => { + // Only if editor text focus (i.e. not if editor has widget focus). + document.execCommand(browserCommand); + return true; + }); } + +registerExecCommandImpl(CutAction, 'cut'); +registerExecCommandImpl(CopyAction, 'copy'); +registerExecCommandImpl(PasteAction, 'paste'); + if (supportsCopyWithSyntaxHighlighting) { registerEditorAction(ExecCommandCopyWithSyntaxHighlightingAction); } diff --git a/src/vs/workbench/contrib/customEditor/browser/commands.ts b/src/vs/workbench/contrib/customEditor/browser/commands.ts deleted file mode 100644 index d96619b257a..00000000000 --- a/src/vs/workbench/contrib/customEditor/browser/commands.ts +++ /dev/null @@ -1,68 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; -import { Command } from 'vs/editor/browser/editorExtensions'; -import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; -import { InputFocusedContextKey } from 'vs/platform/contextkey/common/contextkeys'; -import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; -import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; -import { CustomEditorInput } from 'vs/workbench/contrib/customEditor/browser/customEditorInput'; -import { CONTEXT_FOCUSED_CUSTOM_EDITOR_IS_EDITABLE } from 'vs/workbench/contrib/customEditor/common/customEditor'; -import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; - - -(new class UndoCustomEditorCommand extends Command { - public static readonly ID = 'editor.action.customEditor.undo'; - - constructor() { - super({ - id: UndoCustomEditorCommand.ID, - precondition: ContextKeyExpr.and( - CONTEXT_FOCUSED_CUSTOM_EDITOR_IS_EDITABLE, - ContextKeyExpr.not(InputFocusedContextKey)), - kbOpts: { - primary: KeyMod.CtrlCmd | KeyCode.KEY_Z, - weight: KeybindingWeight.EditorContrib - } - }); - } - - public runCommand(accessor: ServicesAccessor): void { - const editorService = accessor.get(IEditorService); - const activeInput = editorService.activeEditorPane?.input; - if (activeInput instanceof CustomEditorInput) { - activeInput.undo(); - } - } -}).register(); - -(new class RedoWebviewEditorCommand extends Command { - public static readonly ID = 'editor.action.customEditor.redo'; - - constructor() { - super({ - id: RedoWebviewEditorCommand.ID, - precondition: ContextKeyExpr.and( - CONTEXT_FOCUSED_CUSTOM_EDITOR_IS_EDITABLE, - ContextKeyExpr.not(InputFocusedContextKey)), - kbOpts: { - primary: KeyMod.CtrlCmd | KeyCode.KEY_Y, - secondary: [KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_Z], - mac: { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_Z }, - weight: KeybindingWeight.EditorContrib - } - }); - } - - public runCommand(accessor: ServicesAccessor): void { - const editorService = accessor.get(IEditorService); - const activeInput = editorService.activeEditorPane?.input; - if (activeInput instanceof CustomEditorInput) { - activeInput.redo(); - } - } -}).register(); - diff --git a/src/vs/workbench/contrib/customEditor/browser/customEditor.contribution.ts b/src/vs/workbench/contrib/customEditor/browser/customEditor.contribution.ts index 4189d2899a7..25069373665 100644 --- a/src/vs/workbench/contrib/customEditor/browser/customEditor.contribution.ts +++ b/src/vs/workbench/contrib/customEditor/browser/customEditor.contribution.ts @@ -14,7 +14,6 @@ import { Extensions as EditorInputExtensions, IEditorInputFactoryRegistry } from import { CustomEditorInputFactory } from 'vs/workbench/contrib/customEditor/browser/customEditorInputFactory'; import { ICustomEditorService } from 'vs/workbench/contrib/customEditor/common/customEditor'; import { WebviewEditor } from 'vs/workbench/contrib/webview/browser/webviewEditor'; -import './commands'; import { CustomEditorInput } from './customEditorInput'; import { CustomEditorContribution, CustomEditorService } from './customEditors'; diff --git a/src/vs/workbench/contrib/customEditor/browser/customEditors.ts b/src/vs/workbench/contrib/customEditor/browser/customEditors.ts index 984b6c6a777..afec917d62d 100644 --- a/src/vs/workbench/contrib/customEditor/browser/customEditors.ts +++ b/src/vs/workbench/contrib/customEditor/browser/customEditors.ts @@ -66,7 +66,6 @@ export class CustomEditorService extends Disposable implements ICustomEditorServ this._focusedCustomEditorIsEditable = CONTEXT_FOCUSED_CUSTOM_EDITOR_IS_EDITABLE.bindTo(contextKeyService); this._webviewHasOwnEditFunctions = webviewHasOwnEditFunctionsContext.bindTo(contextKeyService); - this._contributedEditors = this._register(new ContributedCustomEditors(storageService)); this._register(this._contributedEditors.onChange(() => { this.updateContexts(); diff --git a/src/vs/workbench/contrib/notebook/electron-browser/notebook.contribution.ts b/src/vs/workbench/contrib/notebook/electron-browser/notebook.contribution.ts index 9c6734c9afe..d0e8f6b32f5 100644 --- a/src/vs/workbench/contrib/notebook/electron-browser/notebook.contribution.ts +++ b/src/vs/workbench/contrib/notebook/electron-browser/notebook.contribution.ts @@ -4,40 +4,56 @@ *--------------------------------------------------------------------------------------------*/ import { isMacintosh } from 'vs/base/common/platform'; -import { registerAction2 } from 'vs/platform/actions/common/actions'; -import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; -import * as webviewCommands from 'vs/workbench/contrib/webview/electron-browser/webviewCommands'; -import { NotebookEditor } from 'vs/workbench/contrib/notebook/browser/notebookEditor'; +import { CopyAction, CutAction, PasteAction } from 'vs/editor/contrib/clipboard/clipboard'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; +import { getActiveNotebookEditor } from 'vs/workbench/contrib/notebook/browser/contrib/coreActions'; import { ElectronWebviewBasedWebview } from 'vs/workbench/contrib/webview/electron-browser/webviewElement'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { getActiveNotebookEditor } from 'vs/workbench/contrib/notebook/browser/contrib/coreActions'; - -function getActiveElectronBasedWebviewDelegate(accessor: ServicesAccessor): ElectronWebviewBasedWebview | undefined { +import { UndoCommand, RedoCommand } from 'vs/editor/browser/editorExtensions'; +function getFocusedElectronBasedWebviewDelegate(accessor: ServicesAccessor): ElectronWebviewBasedWebview | undefined { const editorService = accessor.get(IEditorService); const editor = getActiveNotebookEditor(editorService); + if (!editor?.hasFocus()) { + return; + } const webview = editor?.getInnerWebview(); - if (webview && webview instanceof ElectronWebviewBasedWebview) { return webview; } - return; } -function registerNotebookCommands(editorId: string): void { - const contextKeyExpr = ContextKeyExpr.and(ContextKeyExpr.equals('activeEditor', editorId), ContextKeyExpr.not('editorFocus') /* https://github.com/Microsoft/vscode/issues/58668 */)!; - - // These commands are only needed on MacOS where we have to disable the menu bar commands - if (isMacintosh) { - registerAction2(class extends webviewCommands.CopyWebviewEditorCommand { constructor() { super(contextKeyExpr, getActiveElectronBasedWebviewDelegate); } }); - registerAction2(class extends webviewCommands.PasteWebviewEditorCommand { constructor() { super(contextKeyExpr, getActiveElectronBasedWebviewDelegate); } }); - registerAction2(class extends webviewCommands.CutWebviewEditorCommand { constructor() { super(contextKeyExpr, getActiveElectronBasedWebviewDelegate); } }); - registerAction2(class extends webviewCommands.UndoWebviewEditorCommand { constructor() { super(contextKeyExpr, getActiveElectronBasedWebviewDelegate); } }); - registerAction2(class extends webviewCommands.RedoWebviewEditorCommand { constructor() { super(contextKeyExpr, getActiveElectronBasedWebviewDelegate); } }); +if (isMacintosh) { + function withWebview(accessor: ServicesAccessor, f: (webviewe: ElectronWebviewBasedWebview) => void) { + const webview = getFocusedElectronBasedWebviewDelegate(accessor); + if (webview) { + f(webview); + return true; + } + return false; } -} -registerNotebookCommands(NotebookEditor.ID); + const PRIORITY = 100; + + UndoCommand.addImplementation(PRIORITY, accessor => { + return withWebview(accessor, webview => webview.undo()); + }); + + RedoCommand.addImplementation(PRIORITY, accessor => { + return withWebview(accessor, webview => webview.redo()); + }); + + CopyAction?.addImplementation(PRIORITY, accessor => { + return withWebview(accessor, webview => webview.copy()); + }); + + PasteAction?.addImplementation(PRIORITY, accessor => { + return withWebview(accessor, webview => webview.paste()); + }); + + CutAction?.addImplementation(PRIORITY, accessor => { + return withWebview(accessor, webview => webview.cut()); + }); +} diff --git a/src/vs/workbench/contrib/webview/browser/webviewCommands.ts b/src/vs/workbench/contrib/webview/browser/webviewCommands.ts index 9f949d73926..3ed7a1964cb 100644 --- a/src/vs/workbench/contrib/webview/browser/webviewCommands.ts +++ b/src/vs/workbench/contrib/webview/browser/webviewCommands.ts @@ -32,7 +32,7 @@ export class ShowWebViewEditorFindWidgetAction extends Action2 { } public run(accessor: ServicesAccessor): void { - getActiveWebviewEditor(accessor)?.showFind(); + getFocusedWebviewEditor(accessor)?.showFind(); } } @@ -53,7 +53,7 @@ export class HideWebViewEditorFindCommand extends Action2 { } public run(accessor: ServicesAccessor): void { - getActiveWebviewEditor(accessor)?.hideFind(); + getFocusedWebviewEditor(accessor)?.hideFind(); } } @@ -74,7 +74,7 @@ export class WebViewEditorFindNextCommand extends Action2 { } public run(accessor: ServicesAccessor): void { - getActiveWebviewEditor(accessor)?.runFindAction(false); + getFocusedWebviewEditor(accessor)?.runFindAction(false); } } @@ -95,7 +95,7 @@ export class WebViewEditorFindPreviousCommand extends Action2 { } public run(accessor: ServicesAccessor): void { - getActiveWebviewEditor(accessor)?.runFindAction(true); + getFocusedWebviewEditor(accessor)?.runFindAction(true); } } @@ -117,7 +117,7 @@ export class SelectAllWebviewEditorCommand extends Action2 { } public run(accessor: ServicesAccessor): void { - getActiveWebviewEditor(accessor)?.selectAll(); + getFocusedWebviewEditor(accessor)?.selectAll(); } } @@ -142,8 +142,8 @@ export class ReloadWebviewAction extends Action { } } -export function getActiveWebviewEditor(accessor: ServicesAccessor): Webview | undefined { +export function getFocusedWebviewEditor(accessor: ServicesAccessor): Webview | undefined { const editorService = accessor.get(IEditorService); - const activeEditor = editorService.activeEditor; + const activeEditor = editorService.activeEditorPane; return activeEditor instanceof WebviewInput ? activeEditor.webview : undefined; } diff --git a/src/vs/workbench/contrib/webview/electron-browser/webview.contribution.ts b/src/vs/workbench/contrib/webview/electron-browser/webview.contribution.ts index ed4ad60e2ed..56d95808d70 100644 --- a/src/vs/workbench/contrib/webview/electron-browser/webview.contribution.ts +++ b/src/vs/workbench/contrib/webview/electron-browser/webview.contribution.ts @@ -4,15 +4,18 @@ *--------------------------------------------------------------------------------------------*/ import { isMacintosh } from 'vs/base/common/platform'; -import { SyncActionDescriptor, registerAction2 } from 'vs/platform/actions/common/actions'; -import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; +import { CopyAction, CutAction, PasteAction } from 'vs/editor/contrib/clipboard/clipboard'; +import { SyncActionDescriptor } from 'vs/platform/actions/common/actions'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { Registry } from 'vs/platform/registry/common/platform'; import { Extensions as ActionExtensions, IWorkbenchActionRegistry } from 'vs/workbench/common/actions'; -import { IWebviewService, webviewDeveloperCategory } from 'vs/workbench/contrib/webview/browser/webview'; -import { WebviewEditor } from 'vs/workbench/contrib/webview/browser/webviewEditor'; +import { IWebviewService, webviewDeveloperCategory, WebviewOverlay } from 'vs/workbench/contrib/webview/browser/webview'; +import { getFocusedWebviewEditor } from 'vs/workbench/contrib/webview/browser/webviewCommands'; import * as webviewCommands from 'vs/workbench/contrib/webview/electron-browser/webviewCommands'; +import { ElectronWebviewBasedWebview } from 'vs/workbench/contrib/webview/electron-browser/webviewElement'; import { ElectronWebviewService } from 'vs/workbench/contrib/webview/electron-browser/webviewService'; +import { UndoCommand, RedoCommand } from 'vs/editor/browser/editorExtensions'; registerSingleton(IWebviewService, ElectronWebviewService, true); @@ -23,17 +26,53 @@ actionRegistry.registerWorkbenchAction( webviewCommands.OpenWebviewDeveloperToolsAction.ALIAS, webviewDeveloperCategory); -function registerWebViewCommands(editorId: string): void { - const contextKeyExpr = ContextKeyExpr.and(ContextKeyExpr.equals('activeEditor', editorId), ContextKeyExpr.not('editorFocus') /* https://github.com/Microsoft/vscode/issues/58668 */)!; +if (isMacintosh) { + function getActiveElectronBasedWebview(accessor: ServicesAccessor): ElectronWebviewBasedWebview | undefined { + const webview = getFocusedWebviewEditor(accessor); + if (!webview) { + return undefined; + } - // These commands are only needed on MacOS where we have to disable the menu bar commands - if (isMacintosh) { - registerAction2(class extends webviewCommands.CopyWebviewEditorCommand { constructor() { super(contextKeyExpr); } }); - registerAction2(class extends webviewCommands.PasteWebviewEditorCommand { constructor() { super(contextKeyExpr); } }); - registerAction2(class extends webviewCommands.CutWebviewEditorCommand { constructor() { super(contextKeyExpr); } }); - registerAction2(class extends webviewCommands.UndoWebviewEditorCommand { constructor() { super(contextKeyExpr); } }); - registerAction2(class extends webviewCommands.RedoWebviewEditorCommand { constructor() { super(contextKeyExpr); } }); + if (webview instanceof ElectronWebviewBasedWebview) { + return webview; + } else if ('getInnerWebview' in (webview as WebviewOverlay)) { + const innerWebview = (webview as WebviewOverlay).getInnerWebview(); + if (innerWebview instanceof ElectronWebviewBasedWebview) { + return innerWebview; + } + } + + return undefined; } -} -registerWebViewCommands(WebviewEditor.ID); + function withWebview(accessor: ServicesAccessor, f: (webviewe: ElectronWebviewBasedWebview) => void) { + const webview = getActiveElectronBasedWebview(accessor); + if (webview) { + f(webview); + return true; + } + return false; + } + + const PRIORITY = 100; + + UndoCommand.addImplementation(PRIORITY, accessor => { + return withWebview(accessor, webview => webview.undo()); + }); + + RedoCommand.addImplementation(PRIORITY, accessor => { + return withWebview(accessor, webview => webview.redo()); + }); + + CopyAction?.addImplementation(PRIORITY, accessor => { + return withWebview(accessor, webview => webview.copy()); + }); + + PasteAction?.addImplementation(PRIORITY, accessor => { + return withWebview(accessor, webview => webview.paste()); + }); + + CutAction?.addImplementation(PRIORITY, accessor => { + return withWebview(accessor, webview => webview.cut()); + }); +} diff --git a/src/vs/workbench/contrib/webview/electron-browser/webviewCommands.ts b/src/vs/workbench/contrib/webview/electron-browser/webviewCommands.ts index 1486280626c..08ccb9be4d8 100644 --- a/src/vs/workbench/contrib/webview/electron-browser/webviewCommands.ts +++ b/src/vs/workbench/contrib/webview/electron-browser/webviewCommands.ts @@ -5,16 +5,7 @@ import { WebviewTag } from 'electron'; import { Action } from 'vs/base/common/actions'; -import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; -import { ServicesAccessor } from 'vs/editor/browser/editorExtensions'; import * as nls from 'vs/nls'; -import { Action2 } from 'vs/platform/actions/common/actions'; -import { ContextKeyExpr, ContextKeyExpression } from 'vs/platform/contextkey/common/contextkey'; -import { InputFocusedContextKey } from 'vs/platform/contextkey/common/contextkeys'; -import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; -import { WebviewOverlay, webviewHasOwnEditFunctionsContextKey } from 'vs/workbench/contrib/webview/browser/webview'; -import { getActiveWebviewEditor } from 'vs/workbench/contrib/webview/browser/webviewCommands'; -import { ElectronWebviewBasedWebview } from 'vs/workbench/contrib/webview/electron-browser/webviewElement'; export class OpenWebviewDeveloperToolsAction extends Action { static readonly ID = 'workbench.action.webview.openDeveloperTools'; @@ -37,128 +28,3 @@ export class OpenWebviewDeveloperToolsAction extends Action { return true; } } - -export class CopyWebviewEditorCommand extends Action2 { - public static readonly ID = 'editor.action.webvieweditor.copy'; - public static readonly LABEL = nls.localize('editor.action.webvieweditor.copy', "Copy2"); - - constructor(contextKeyExpr: ContextKeyExpression, readonly getActiveElectronBasedWebviewDelegate: (accessor: ServicesAccessor) => ElectronWebviewBasedWebview | undefined = getActiveElectronBasedWebview) { - super({ - id: CopyWebviewEditorCommand.ID, - title: CopyWebviewEditorCommand.LABEL, - keybinding: { - when: ContextKeyExpr.and(contextKeyExpr, ContextKeyExpr.not(InputFocusedContextKey)), - primary: KeyMod.CtrlCmd | KeyCode.KEY_C, - weight: KeybindingWeight.EditorContrib - } - }); - } - - public run(accessor: ServicesAccessor): void { - this.getActiveElectronBasedWebviewDelegate(accessor)?.copy(); - } -} - -export class PasteWebviewEditorCommand extends Action2 { - public static readonly ID = 'editor.action.webvieweditor.paste'; - public static readonly LABEL = nls.localize('editor.action.webvieweditor.paste', 'Paste'); - - constructor(contextKeyExpr: ContextKeyExpression, readonly getActiveElectronBasedWebviewDelegate: (accessor: ServicesAccessor) => ElectronWebviewBasedWebview | undefined = getActiveElectronBasedWebview) { - super({ - id: PasteWebviewEditorCommand.ID, - title: PasteWebviewEditorCommand.LABEL, - keybinding: { - when: ContextKeyExpr.and(contextKeyExpr, ContextKeyExpr.not(InputFocusedContextKey)), - primary: KeyMod.CtrlCmd | KeyCode.KEY_V, - weight: KeybindingWeight.EditorContrib - } - }); - } - - public run(accessor: ServicesAccessor): void { - this.getActiveElectronBasedWebviewDelegate(accessor)?.paste(); - } -} - -export class CutWebviewEditorCommand extends Action2 { - public static readonly ID = 'editor.action.webvieweditor.cut'; - public static readonly LABEL = nls.localize('editor.action.webvieweditor.cut', 'Cut'); - - constructor(contextKeyExpr: ContextKeyExpression, readonly getActiveElectronBasedWebviewDelegate: (accessor: ServicesAccessor) => ElectronWebviewBasedWebview | undefined = getActiveElectronBasedWebview) { - super({ - id: CutWebviewEditorCommand.ID, - title: CutWebviewEditorCommand.LABEL, - keybinding: { - when: ContextKeyExpr.and(contextKeyExpr, ContextKeyExpr.not(InputFocusedContextKey)), - primary: KeyMod.CtrlCmd | KeyCode.KEY_X, - weight: KeybindingWeight.EditorContrib - } - }); - } - - public run(accessor: ServicesAccessor): void { - this.getActiveElectronBasedWebviewDelegate(accessor)?.cut(); - } -} - -export class UndoWebviewEditorCommand extends Action2 { - public static readonly ID = 'editor.action.webvieweditor.undo'; - public static readonly LABEL = nls.localize('editor.action.webvieweditor.undo', "Undo"); - - constructor(contextKeyExpr: ContextKeyExpression, readonly getActiveElectronBasedWebviewDelegate: (accessor: ServicesAccessor) => ElectronWebviewBasedWebview | undefined = getActiveElectronBasedWebview) { - super({ - id: UndoWebviewEditorCommand.ID, - title: UndoWebviewEditorCommand.LABEL, - keybinding: { - when: ContextKeyExpr.and(contextKeyExpr, ContextKeyExpr.not(InputFocusedContextKey), ContextKeyExpr.not(webviewHasOwnEditFunctionsContextKey)), - primary: KeyMod.CtrlCmd | KeyCode.KEY_Z, - weight: KeybindingWeight.EditorContrib - } - }); - } - - public run(accessor: ServicesAccessor): void { - this.getActiveElectronBasedWebviewDelegate(accessor)?.undo(); - } -} - -export class RedoWebviewEditorCommand extends Action2 { - public static readonly ID = 'editor.action.webvieweditor.redo'; - public static readonly LABEL = nls.localize('editor.action.webvieweditor.redo', "Redo"); - - constructor(contextKeyExpr: ContextKeyExpression, readonly getActiveElectronBasedWebviewDelegate: (accessor: ServicesAccessor) => ElectronWebviewBasedWebview | undefined = getActiveElectronBasedWebview) { - super({ - id: RedoWebviewEditorCommand.ID, - title: RedoWebviewEditorCommand.LABEL, - keybinding: { - when: ContextKeyExpr.and(contextKeyExpr, ContextKeyExpr.not(InputFocusedContextKey), ContextKeyExpr.not(webviewHasOwnEditFunctionsContextKey)), - primary: KeyMod.CtrlCmd | KeyCode.KEY_Y, - secondary: [KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_Z], - mac: { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_Z }, - weight: KeybindingWeight.EditorContrib - } - }); - } - - public run(accessor: ServicesAccessor): void { - this.getActiveElectronBasedWebviewDelegate(accessor)?.redo(); - } -} - -function getActiveElectronBasedWebview(accessor: ServicesAccessor): ElectronWebviewBasedWebview | undefined { - const webview = getActiveWebviewEditor(accessor); - if (!webview) { - return undefined; - } - - if (webview instanceof ElectronWebviewBasedWebview) { - return webview; - } else if ('getInnerWebview' in (webview as WebviewOverlay)) { - const innerWebview = (webview as WebviewOverlay).getInnerWebview(); - if (innerWebview instanceof ElectronWebviewBasedWebview) { - return innerWebview; - } - } - - return undefined; -}