diff --git a/source/_posts/ctrl-h-backspace.md b/source/_posts/ctrl-h-backspace.md new file mode 100644 index 0000000..602f3ee --- /dev/null +++ b/source/_posts/ctrl-h-backspace.md @@ -0,0 +1,137 @@ +--- +title: Mapping Ctrl+H to Backspace in terminal emulator +excerpt: Also fix Ctrl+Backspace in PowerShell +date: 2023-07-17 +tags: + - linux +--- + +A few months ago, there was [an article](https://www.masteringemacs.org/article/keyboard-shortcuts-every-command-line-hacker-should-know-about-gnu-readline) which encouraged Linux users to use more readline keyboard shortcuts. readline keyboard shortcuts are based on Emacs keybindings, while also support switching to vi keybindings. At that time, I was only familiar with `Ctrl+a` (line start) and `Ctrl+e` (line end). Interested to learn more tricks, I went on search for a cheatsheet and [found this](https://clementc.github.io/blog/2018/01/25/moving_cli/). I then added two missing shortcuts (`Ctrl+h` & `Ctrl+d`), printed it out and stick it to my desk. + +![readline keyboard shortcuts](20230717/readline-shortcuts.png) + +However there were two shortcuts which did not work as intended: `Ctrl+h` and `Ctrl+Backspace`. The first one is [supposed to](https://en.wikipedia.org/wiki/GNU_Readline#Emacs_keyboard_shortcuts) be equivalent to backspace, but it was deleting previous word just like `Ctrl+Backspace` or `Ctrl+w`. The second one did not work on PowerShell's Emacs mode. + +While looking for a workaround for other terminal and shell, I find it helpful to remember these two facts so that you can stay on the right track. + +- $TERM does not refer to the terminal emulator +- Shell does not recognise Ctrl+Backspace + +## $TERM is not the terminal emulator + +In Kitty, `$TERM` is "xterm-kitty"; most other Linux terminals output it as "xterm-256color". The value actually refers to the "[terminfo](https://en.wikipedia.org/wiki/Terminfo)" being used and not the [terminal emulator](https://en.wikipedia.org/wiki/Xterm). + +## Shell does not recognise Ctrl+Backspace + +When Ctrl+Backspace is pressed, a terminal emulator either sends "^?" or "^H" [control character](https://en.wikipedia.org/wiki/C0_and_C1_control_codes#C0_controls) to the shell, which then initiate an action (e.g. "backward-kill-word"). + +"^\[character\]" is first and foremost a [caret notation](https://en.wikipedia.org/wiki/Caret_notation) of a control character, a friendlier representation of hexadecimal, much like hexadecimal is a nicer representation of binary. "^H" actually means control-code-8 (H is the eighth letter), instead of representing `Ctrl+h`. "^H" can be entered using `Ctrl+h` simply because it is more practical than having a dedicated key for each control character on a keyboard. + +## Remap Ctrl+h to ^? + +Most terminal emulators map `Backspace` to "^?" and `Ctrl+Backspace` to "^H". Since `Ctrl+h` is also mapped to "^H", thus sharing a similar action ("backward-kill-word") with `Ctrl+Backspace`. The easiest fix is to remap `Ctrl+h` to "^?". This approach only needs to configure the terminal emulator. + +To check which control character is mapped to: + +``` +$ showkey -a + +# backspace +^? 127 0177 0x7f + +# ctrl+ backspace +^H 8 0010 0x08 +``` + +### kitty + +`map ctrl+h send_text normal \x7f` + +Add the above line to the end of "$HOME/.config/kitty/kitty.conf". "7f" is the hex of "^?". + +Press `Ctrl+Shirt+F5` to reload the config and run `showkey -a` to verify `Ctrl+h` has been remapped. + +``` +$ showkey -a + +# ctrl+h +^? 127 0177 0x7f +``` + +### Windows Terminal + +Go Settings -> Open JSON file which will open "$home\AppData\Local\Packages\Microsoft.WindowsTerminal_xxx\LocalState\settings.json". Under `"actions"` list, append the following object. + +```json +{ + "command": { + "action": "sendInput", + "input": "\u007F" + }, + "keys": "ctrl+h" +} +``` + +## Map Ctrl+Backspace to backward-kill-word + +`Ctrl+Backspace` does not work as expected when I switch the PowerShell's edit mode to Emacs `Set-PSReadLineOption -EditMode Emacs`, even though it works in the default `Cmd` mode. This is because PowerShell binds it to [`BackwardDeleteChar`](https://learn.microsoft.com/en-us/powershell/module/psreadline/about/about_psreadline_functions#backwarddeletechar) in Emacs mode. Somehow I could not remap it to "^H" (`\b`). + +Some xterm users also have this issue and a workaround is by [mapping it](https://www.vinc17.net/unix/ctrl-backspace.en.html) to an unused escape sequence, then bind it to backward-kill-word in the shell. While Windows Terminal [supports](https://learn.microsoft.com/en-us/windows/terminal/customize-settings/actions#send-input) sending an escape sequence, the corresponding binding is [not supported](https://github.com/PowerShell/PSReadLine/issues/3430) in PowerShell. Instead of using escape sequence, let's use a unicode character, specifically a character within the range of [private use area](https://en.wikipedia.org/wiki/Private_Use_Areas) (`U+E888-U+F8FF`) to avoid conflict with existing characters. I choose `U+E888` for this example. + +Anyhow, it is only a tiny issue for me since I can always use `Ctrl+w`. + +### Windows Terminal + +Go Settings -> Open JSON file which will open "$home\AppData\Local\Packages\Microsoft.WindowsTerminal_xxx\LocalState\settings.json". Under `"actions"` list, append the following object. + +```json +{ + "command": { + "action": "sendInput", + "input": "\uE888" + }, + "keys": "ctrl+backspace" +} +``` + +#### PowerShell + +```ps $PROFILE +Set-PSReadLineKeyHandler -Chord "`u{E888}" -Function BackwardKillWord +``` + +The following Windows Terminal + PowerShell configs did not work for me. Windows Terminal did yield the correct control character, but somehow PowerShell could not recognise it. + +```json +{ + "command": { + "action": "sendInput", + "input": "\u007F" + }, + "keys": "backspace" +}, +{ + "command": { + "action": "sendInput", + "input": "\b" + }, + "keys": "ctrl+backspace" +} +``` + +```ps $PROFILE +Set-PSReadLineKeyHandler -Chord "`u{007F}" -Function BackwardDeleteChar +Set-PSReadLineKeyHandler -Chord "`b" -Function BackwardKillWord +``` + +#### zsh + +```sh $HOME/.zshrc +bindkey '\uE888' backward-kill-word +``` + +#### bash + +```sh $HOME/.bashrc +bind '"\uE888":backward-kill-word' +```