blog/source/_posts/ctrl-h-backspace.md

140 lines
5.9 KiB
Markdown
Raw Permalink Normal View History

---
title: Mapping Ctrl+H to Backspace in terminal emulator
excerpt: Also fix Ctrl+Backspace in PowerShell
date: 2023-07-17
tags:
- linux
2023-08-06 06:05:32 +00:00
- zsh
- powershell
---
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'
```