Make deleteLeft and deleteRight work in dialogs and inputs#313699
Make deleteLeft and deleteRight work in dialogs and inputs#313699maruthang wants to merge 1 commit intomicrosoft:mainfrom
Conversation
Converts the deleteLeft and deleteRight core commands to the
EditorOrNativeTextInputCommand pattern used by Undo, Redo and SelectAll
in the same file. The MultiCommand is registered with kbExpr: null so
the existing keybindings (Backspace, Delete, plus mac Ctrl+H/Ctrl+D
secondaries) fire regardless of focus context. Dispatch then prefers
the focused code editor, falls back to a focused input or textarea via
execCommand('delete'|'forwardDelete'), and finally to the active editor.
Fixes microsoft#147218
There was a problem hiding this comment.
Pull request overview
This PR fixes keybinding routing for deleteLeft / deleteRight so that when these commands are invoked from non-Monaco text inputs (e.g. QuickInput, Find widget inputs), the focused DOM input is edited instead of the last-active code editor. It does so by switching these commands to the existing EditorOrNativeTextInputCommand dispatch pattern already used by Undo/Redo/Select All.
Changes:
- Adds new
MultiCommands fordeleteLeftanddeleteRightalongside other globally-routed editor/input commands. - Replaces the editor-only
CoreEditingCommandregistrations withEditorOrNativeTextInputCommandimplementations that dispatch to Monaco vs DOM inputs appropriately. - Uses DOM-side
execCommand('delete' | 'forwardDelete')for native input/textarea deletion.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 4 comments.
| File | Description |
|---|---|
| src/vs/editor/browser/editorExtensions.ts | Introduces DeleteLeftCommand / DeleteRightCommand MultiCommands and default keybinding rules for them. |
| src/vs/editor/browser/coreCommands.ts | Rewires CoreEditingCommands.DeleteLeft/Right to route to Monaco or DOM inputs via EditorOrNativeTextInputCommand. |
| weight: KeybindingWeight.EditorCore, | ||
| kbExpr: null, | ||
| primary: KeyCode.Delete, | ||
| mac: { primary: KeyCode.Delete, secondary: [KeyMod.WinCtrl | KeyCode.KeyD, KeyMod.WinCtrl | KeyCode.Delete] } |
There was a problem hiding this comment.
Same as deleteLeft: with kbExpr: null, the Delete keybinding for deleteRight can resolve even when focus is not in a text input/editor, and the command implementation may then redirect focus to the active editor and perform a forward delete there.
It’s safer to add a kbExpr that limits this keybinding to editable contexts (e.g. ContextKeyExpr.or(InputFocusedContext, EditorContextKeys.textInputFocus)).
| public readonly id = 'deleteLeft'; | ||
| constructor() { | ||
| super({ | ||
| id: 'deleteLeft', | ||
| precondition: undefined, | ||
| kbOpts: { | ||
| weight: CORE_WEIGHT, | ||
| kbExpr: EditorContextKeys.textInputFocus, | ||
| primary: KeyCode.Backspace, | ||
| secondary: [KeyMod.Shift | KeyCode.Backspace], | ||
| mac: { primary: KeyCode.Backspace, secondary: [KeyMod.Shift | KeyCode.Backspace, KeyMod.WinCtrl | KeyCode.KeyH, KeyMod.WinCtrl | KeyCode.Backspace] } | ||
| } | ||
| }); | ||
| super(DeleteLeftCommand); | ||
| } |
There was a problem hiding this comment.
this.id is hard-coded here and also duplicated by the DeleteLeftCommand target passed to super(...). To avoid the risk of these drifting apart, consider reusing the MultiCommand’s id (e.g. DeleteLeftCommand.id) when calling executeCommands(...) instead of repeating the string literal.
|
|
||
| export const DeleteRight: EditorCommand = registerEditorCommand(new class extends CoreEditingCommand { | ||
| export const DeleteRight = new class extends EditorOrNativeTextInputCommand { | ||
| public readonly id = 'deleteRight'; |
There was a problem hiding this comment.
Same as deleteLeft: the command id is duplicated (string literal + DeleteRightCommand target). Consider reusing DeleteRightCommand.id when reporting/executing the edit source to reduce maintenance risk.
| public readonly id = 'deleteRight'; | |
| public readonly id = DeleteRightCommand.id; |
| weight: KeybindingWeight.EditorCore, | ||
| kbExpr: null, | ||
| primary: KeyCode.Backspace, | ||
| secondary: [KeyMod.Shift | KeyCode.Backspace], | ||
| mac: { primary: KeyCode.Backspace, secondary: [KeyMod.Shift | KeyCode.Backspace, KeyMod.WinCtrl | KeyCode.KeyH, KeyMod.WinCtrl | KeyCode.Backspace] } |
There was a problem hiding this comment.
kbExpr: null makes the Backspace keybinding for deleteLeft eligible in all UI contexts. Because the EditorOrNativeTextInputCommand has a fallback that focuses the active editor (implementation priority 0), this can cause Backspace to delete in the active editor when focus is somewhere non-editable (e.g. trees/lists), and can also cause workbench dialogs that soft-dispatch keybindings to treat Backspace as a command and potentially block the native input behavior.
Consider restoring a guard expression so the keybinding only resolves when focus is in an editable control (e.g. ContextKeyExpr.or(InputFocusedContext, EditorContextKeys.textInputFocus) or an equivalent context key used elsewhere for text inputs).
Summary
deleteLeftanddeleteRightwere registered as editor-onlyCoreEditingCommands withkbExpr: EditorContextKeys.textInputFocus. As a result, when a user binds a keybinding such asCtrl+H→deleteLeftand runs it from a dialog, QuickInput, or any non-Monaco<input>/<textarea>, the command either silently no-ops or routes to the last focused code editor — bypassing the focused input entirely.This PR converts both commands to the same
EditorOrNativeTextInputCommandpattern thatUndo,Redo, andSelectAllalready use immediately above in the same file. The dispatch order becomes:DeleteOperations.deleteLeft/Right, push undo stop, setEditOperationType.DeletingLeft/Right).<input>/<textarea>→activeElement.ownerDocument.execCommand('delete'|'forwardDelete').InputBoxandQuickInputBoxneed no changes — they already participate in thegeneric-dom-input-textareabranch via the existingisEditableElementcheck.Fixes #147218
Implementation
src/vs/editor/browser/editorExtensions.ts— addedDeleteLeftCommandandDeleteRightCommandMultiCommands with id'deleteLeft'/'deleteRight',kbExpr: null(the line that fixes the routing bug), and the original keybindings preserved verbatim (Backspace / Delete plus macCtrl+H/Ctrl+D/Shift+Backspace/Ctrl+Backspace/Ctrl+Deletesecondaries).src/vs/editor/browser/coreCommands.ts— replaced theregisterEditorCommand(new class extends CoreEditingCommand)blocks for the same two ids withnew class extends EditorOrNativeTextInputCommandinstances bound to the newMultiCommands. The editor-siderunEditorCommandretains the existingDeleteOperationslogic;runDOMCommandcallsexecCommand('delete')/'forwardDelete'. Apublic readonly idfield is exposed for the existing reader ininlineCompletionsController.ts.Out of scope
deleteWordLeft/deleteWordRightlive insrc/vs/editor/contrib/wordOperations/rather than core. They share the same root cause and could be follow-up work, but applying the fix there would broaden this PR substantially. Issue #147218 only mentionsdeleteLeft/deleteRight.Test plan
editor.runCommand(CoreEditingCommands.DeleteLeft|Right, args)(cursor.test.ts, suggestModel.test.ts, linkedEditing.test.ts, cursorUndo.test.ts, inlineCompletions/utils.ts) compile unchanged because the new objects implement the samerunEditorCommand(accessor, editor, args)signature.Ctrl+HtodeleteLeftvia Keyboard Shortcuts. PressCtrl+Hin a Monaco editor — deletes left. PressCtrl+Hin the Find widget input, the Quick Open input, and a Settings search input — also deletes left.Ctrl+DtodeleteRight. Same scenarios — deletes right.