Text Manipulation and Modifier Keys
About 3 years ago I switched my primary OS from MacOS back to Linux. I hadn't run it as a desktop OS since college and have memories of updates breaking my system, battling with Xorg configs, and slow, bloated DEs. However, this time around it has been a very positive and painless experience. It's been more stable than MacOS (despite using a rolling release distribution). And now that I'm running a Wayland compositor (Sway), I don't have any screen tearing or need to deal with Xorg configuration anymore. But the feature I've missed most from MacOS is having readline style bindings in all text inputs uniformly across the system.
Readline bindings
GNU Readline is a library that enables a program to read a line of text from the user, and allow basic in-line editing. It is commonly used by shells and REPLs for input handling. A big advantage for using a single library for this is it enables consistent keyboard shortcuts for text manipulation across all programs that use it. Some of the more useful shortcuts are below:
Ctrl-A
: move cursor to beginning of the lineCtrl-E
: move cursor to end of the lineCtrl-W
: delete back wordCtrl-U
: delete to beginning of lineCtrl-K
: delete to end of lineCtrl-N
: next line or itemCtrl-P
: previous line or itemCtrl-F
: move cursor forward one characterCtrl-B
: move cursor back one characterAlt-F
: move cursor forward one wordAlt-B
: move cursor back one wordCtrl-D
: delete forward one characterAlt-D
: delete forward one wordCtrl-T
: swap (transpose) the last two lettersAlt-T
: swap (transpose) the last two words
A good way to help remember the last 8 is that the Ctrl
modifier operates on
letters while the Alt
modifier operates on words. (Ctrl-W
probably should
have been Alt-H
for consistency since Ctrl-H
deletes backward one
character).
MacOS Modifiers
On MacOS, many of these bindings simply work out of the box on Cocoa text
inputs. The rest are trivially enabled by creating the file
~/Library/KeyBindings/DefaultKeyBinding.dict
and specifying the missing
shortcuts.
I created a repo with the missing readline bindings defined that enables them after a simple git clone and a logout. Back when I ran MacOS, cloning this was always one of the first things I would do on a clean install.
This made text manipulation a breeze. For example, say I want to navigate to
github.com/swaywm/sway
. So I open my browser, press Cmd+L
to select the
URL, and start typing the URL. But suppose as I start typing the first few
characters, the browser suggests github.com/alexdavid/sigma
. I jump the
cursor to the end of the URL with Ctrl-E
, press Ctrl-W
twice to delete the
project & username, and start typing sw
to let the browser auto-complete the
rest and press enter.
The entire process takes about 9 keystrokes and is such a habit I am able to do it in under a second. There's no need to move my hand over to the mouse and take the time to precisely click and drag over the text I want to change. There's no need for me to move my hand off the home row to the arrow keys to try to manipulate the text that way. But most importantly, there's no need to remember a completely different set of bindings for manipulating text whether I'm entering a line of text in Firefox, or entering a command into a shell, python REPL, sqlite, etc.
Why Readline Over Vim Bindings?
Vim is my primary editor. I'm a big proponent of modal editing when writing software, or long blocks of text such as email. But when inputting a command into a shell, or a URL into a browser window I found that I want a much smaller set of commands, and for that a quick delete‑back‑word or beginning‑of‑line binding is more useful. In fact I even use readline bindings in vim's command line with vim-husk.
I've found it's much easier to train myself to differentiate shortcuts based on if I'm editing a body of text vs a one off line rather than if the text happens to be in a GUI or a terminal.
Incomplete Solution: GTK Key Themes
GTK 2 and 3 support a feature called "key themes" that tries to solve this
problem. By setting the GTK option gtk-key-theme
to
Emacs
,
all GTK inputs get the readline bindings. This is a big improvement, but there
are some problems.
First, it's GTK only, which for me isn't a huge issue since most of the graphical programs I run are GTK, but that just makes it that much more frustrating when I am running a QT application.
Another big issue stems from the fact that GUI toolkits on Linux use control as
the standard modifier. This means pressing Ctrl-W
to delete back word will
instead close the window. Luckily in Firefox this is solvable by setting
ui.key.accelKey
to 18
in about:config
. This sets the default GUI modifier
to alt in firefox, which solves the issue there, but now I have modifier
inconsistency between various programs.
Worst of all, though, is GTK 4 has dropped support for key themes altogether. It sounds like they plan on replacing it with a better solution - and I agree the way it is implemented is an abuse of CSS. But I hope there is something to replace it before too many programs start switching to GTK 4.
Sort-of-Complete Solution: QMK Firmware
I use QMK Firmware to set a custom keyboard mapping and enable macros. After my frustration with the drawbacks of GTK key themes I decided to try to add a custom text-modification layer to my keyboard.
This will require re-training my readline muscle memory, but I prefer to do that and have a consistent set of editing shortcuts that allow me to keep my fingers on the home row.
My Preonic keyboard that runs QMK firmware
The basic idea is to add a new modifier key that can add all text manipulation shortcuts needed:
Mod+A
:Home
- move cursor to beginning of the lineMod+E
:End
- move cursor to end of the lineMod+F
:Ctrl-Right
- move cursor forward one wordMod+B
:Ctrl-Left
- move cursor back one wordMod+W
:Ctrl-Backspace
- delete back wordMod+D
:Ctrl-Delete
- delete forward wordMod+U
:Shift-Home
thenBackspace
- Delete to beginning of lineMod+K
:Shift-End
thenDelete
- Delete to end of line
This works well since it's consistent across most GUIs, but it has problems as well.
First, using Ctrl-Backspace
is a bit of a hack for delete-back-word. It seems
to be standard for most GUIs, but not in the terminal. But what's worse are my
bindings for Delete-to-beginning/end-of-line are massive hacks. They work by
selecting the text first with Shift
+Home
/End
, and then deleting it, but
this is obviously completely broken in a terminal.
However, with some pretty janky bindings in my Alacritty config, I was able to just barely get it working, albeit just barely:
key_bindings: - { key: Back, mods: Control, chars: "\x17" } - { key: Home, mods: Shift, chars: "\x15_" } - { key: End, mods: Shift, chars: "\x0b" }
This binds Ctrl-Backspace
to send a Ctrl-W
, Shift-Home
to send a Ctrl-U
followed by a dummy underscore to be deleted by the next backspace, and
Shift-End
to send a Ctrl-K
.
So far this has been working well for me with the exception of feeling crippled when I'm typing directly on my laptop instead of using my Preonic keyboard, but I have yet to come up with a better solution.