From d0ca06d624cea8dc3584d64bc62bee2c1f445b2b Mon Sep 17 00:00:00 2001 From: Alexey Zimarev Date: Wed, 7 Oct 2026 17:56:08 +0200 Subject: [PATCH] Send ESC DEL for Alt+Backspace and BS for Ctrl+Backspace The legacy generator mapped Backspace to DEL whatever the modifiers, so a shell deleted one character for Alt+Backspace. Mirrors xterm.js Keyboard.ts, case 8. --- src/XTerm.NET.Tests/KeyboardInputTests.cs | 21 +++++++++++++++++++ src/XTerm.NET/Input/KeyboardInputGenerator.cs | 10 ++++++++- 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/src/XTerm.NET.Tests/KeyboardInputTests.cs b/src/XTerm.NET.Tests/KeyboardInputTests.cs index 70abc2b0..9f1a83a8 100644 --- a/src/XTerm.NET.Tests/KeyboardInputTests.cs +++ b/src/XTerm.NET.Tests/KeyboardInputTests.cs @@ -200,6 +200,27 @@ public void ControlKeys_GenerateCorrectSequences() Assert.Equal(" ", terminal.GenerateKeyInput(Key.Space)); } + [Fact] + public void Backspace_WithAlt_PrefixesEscape() + { + // Arrange + var terminal = CreateTerminal(); + + // Act & Assert + Assert.Equal("\x1B\x7F", terminal.GenerateKeyInput(Key.Backspace, KeyModifiers.Alt)); + } + + [Fact] + public void Backspace_WithControl_GeneratesBS() + { + // Arrange + var terminal = CreateTerminal(); + + // Act & Assert + Assert.Equal("\b", terminal.GenerateKeyInput(Key.Backspace, KeyModifiers.Control)); + Assert.Equal("\x1B\b", terminal.GenerateKeyInput(Key.Backspace, KeyModifiers.Control | KeyModifiers.Alt)); + } + [Fact] public void Tab_WithShift_GeneratesBackTab() { diff --git a/src/XTerm.NET/Input/KeyboardInputGenerator.cs b/src/XTerm.NET/Input/KeyboardInputGenerator.cs index 1751f46e..1fce2a89 100644 --- a/src/XTerm.NET/Input/KeyboardInputGenerator.cs +++ b/src/XTerm.NET/Input/KeyboardInputGenerator.cs @@ -33,7 +33,7 @@ public string GenerateKeySequence(Key key, KeyModifiers modifiers = KeyModifiers // Control keys Key.Enter => "\r", Key.Tab => (modifiers & KeyModifiers.Shift) != 0 ? "\u001b[Z" : "\t", - Key.Backspace => "\u007f", // DEL (127) + Key.Backspace => GetBackspace(modifiers), // Escape sends ESC whatever the modifiers. CSI 27 ; mod ~ is not a sequence xterm // emits -- it belongs to the modifyOtherKeys and Kitty protocols, which an application // opts into. Sending it unasked meant a program that reads a bare ESC (vim leaving @@ -161,6 +161,14 @@ public string GenerateCharSequence(char c, KeyModifiers modifiers = KeyModifiers }; } + // xterm.js sends BS for Ctrl+Backspace and DEL otherwise, with an ESC prefix when Alt is + // held, which is how Alt+Backspace reaches a shell as backward-kill-word. + private static string GetBackspace(KeyModifiers modifiers) + { + var key = (modifiers & KeyModifiers.Control) != 0 ? "\b" : "\u007f"; + return (modifiers & KeyModifiers.Alt) != 0 ? "\u001b" + key : key; + } + private string GetArrowKey(char direction, KeyModifiers modifiers) { // Arrow keys respect ApplicationCursorKeys mode