Skip to content

Make deleteLeft and deleteRight work in dialogs and inputs#313699

Open
maruthang wants to merge 1 commit intomicrosoft:mainfrom
maruthang:fix/issue-147218-delete-commands-in-dialogs
Open

Make deleteLeft and deleteRight work in dialogs and inputs#313699
maruthang wants to merge 1 commit intomicrosoft:mainfrom
maruthang:fix/issue-147218-delete-commands-in-dialogs

Conversation

@maruthang
Copy link
Copy Markdown
Contributor

Summary

deleteLeft and deleteRight were registered as editor-only CoreEditingCommands with kbExpr: EditorContextKeys.textInputFocus. As a result, when a user binds a keybinding such as Ctrl+HdeleteLeft and 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 EditorOrNativeTextInputCommand pattern that Undo, Redo, and SelectAll already use immediately above in the same file. The dispatch order becomes:

  1. Focused code editor → existing edit logic (DeleteOperations.deleteLeft/Right, push undo stop, set EditOperationType.DeletingLeft/Right).
  2. Focused <input> / <textarea>activeElement.ownerDocument.execCommand('delete'|'forwardDelete').
  3. Active editor as fallback.

InputBox and QuickInputBox need no changes — they already participate in the generic-dom-input-textarea branch via the existing isEditableElement check.

Fixes #147218

Implementation

  • src/vs/editor/browser/editorExtensions.ts — added DeleteLeftCommand and DeleteRightCommand MultiCommands with id 'deleteLeft' / 'deleteRight', kbExpr: null (the line that fixes the routing bug), and the original keybindings preserved verbatim (Backspace / Delete plus mac Ctrl+H / Ctrl+D / Shift+Backspace / Ctrl+Backspace / Ctrl+Delete secondaries).
  • src/vs/editor/browser/coreCommands.ts — replaced the registerEditorCommand(new class extends CoreEditingCommand) blocks for the same two ids with new class extends EditorOrNativeTextInputCommand instances bound to the new MultiCommands. The editor-side runEditorCommand retains the existing DeleteOperations logic; runDOMCommand calls execCommand('delete') / 'forwardDelete'. A public readonly id field is exposed for the existing reader in inlineCompletionsController.ts.

Out of scope

deleteWordLeft / deleteWordRight live in src/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 mentions deleteLeft / deleteRight.

Test plan

  • Existing tests using 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 same runEditorCommand(accessor, editor, args) signature.
  • Manual: bind Ctrl+H to deleteLeft via Keyboard Shortcuts. Press Ctrl+H in a Monaco editor — deletes left. Press Ctrl+H in the Find widget input, the Quick Open input, and a Settings search input — also deletes left.
  • Manual: bind Ctrl+D to deleteRight. Same scenarios — deletes right.
  • Manual: native Backspace and Delete behavior in editors and inputs is unchanged.

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
Copilot AI review requested due to automatic review settings May 1, 2026 09:46
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 for deleteLeft and deleteRight alongside other globally-routed editor/input commands.
  • Replaces the editor-only CoreEditingCommand registrations with EditorOrNativeTextInputCommand implementations 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.

Comment on lines +744 to +747
weight: KeybindingWeight.EditorCore,
kbExpr: null,
primary: KeyCode.Delete,
mac: { primary: KeyCode.Delete, secondary: [KeyMod.WinCtrl | KeyCode.KeyD, KeyMod.WinCtrl | KeyCode.Delete] }
Copy link

Copilot AI May 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)).

Copilot uses AI. Check for mistakes.
Comment on lines +2048 to 2051
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);
}
Copy link

Copilot AI May 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.

export const DeleteRight: EditorCommand = registerEditorCommand(new class extends CoreEditingCommand {
export const DeleteRight = new class extends EditorOrNativeTextInputCommand {
public readonly id = 'deleteRight';
Copy link

Copilot AI May 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
public readonly id = 'deleteRight';
public readonly id = DeleteRightCommand.id;

Copilot uses AI. Check for mistakes.
Comment on lines +732 to +736
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] }
Copy link

Copilot AI May 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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).

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Delete commands (deleteLeft, deleteRight) don't work in dialogs

3 participants