Emacs in the terminal: keybindings, client and more
Emacs in the terminal
If you've ever tried using Emacs with emacs -nw
(no window) in your terminal,
you might've faced a frustrating experience: because Emacs binds keys starting
with Ctrl and terminal input is first handled by the terminal
emulator; some keys might not work as expected. They might provoke an action in
the terminal but not in Emacs or be changed on the way. If you're also not using
a "typical" QWERTY layout or have the bad idea of having accents (thus non ASCII
characters) in your language, the problem gets worse.
A common issue is trying to use Ctrl+Backspace which should delete a word but instead inserts Ctrl+h instead when used in the terminal. There are also issues for more complex key/characters combinations.
Fix
The fix consists of using the Kitty Keyboard Protocol in both Emacs and your terminal emulators.
I've tried to set a couple variables and tried some terminal emulators; the best
experience I've had is with the kkp
package
and the ghostty terminal emulator.
With the following configuration, in your initialization file:
Quality of life
When setting Emacs as your $EDITOR
in the shell, I discovered that export EDITOR="emacs -nw"
sometimes fail depending on your shell, because it expects
an executable without any white-spaces. An alternative is to write a shell
function and refer to that as the editor:
#!/bin/sh
I have it in /home/natfu/.local/bin/emacseditor
and I've set my $EDITOR
to this path.
Note that this spins a new instance of Emacs every time with a slight delay in
starting up even with a rather optimized ~/.emacs.d/init.el
. The alternative
is to use the
emacsclient
.
In my case, I'm using Linux with systemd
so I can write a unit file that will
start an Emacs daemon in the background and open a new frame instantly by
calling it. On my system, I've found that file in those places:
/usr/share/emacs/30.2/etc/emacs.service
/usr/lib/systemd/user/emacs.service
To make it clear that I'll use it for my user, I'll copy the file under
~/.config/systemd/user/emacsd.service
. You can tweak it to your liking, for
example by adding an environment file only for that service (useful to set the
path among other things).
[Unit]
GUI)
Emacs text editor (:emacs man:1) /
info
[Service]
notify
/usr/bin/emacs --fg-daemon
/usr/bin/emacsclient --eval "(kill-emacs)"
15
-failure
on
[Install]
.target
default
Then, I can change the /home/natfu/.local/bin/emacseditor
file to use the client
instead.
#!/bin/sh
Finally, start the service with systemctl --user start --now emacsd.service
and
systemctl --user enable --now emacsd.service
to have the service launch Emacs for you
at the start of a user session.
As always, the Arch wiki on Emacs is great information, even if you're not using Arch.
Important note for Linux
On most Linux systems, you'll be able to use .desktop
files which not only do let
you add icons and interact on a GUI level with an application (like icons in
menus/desktops), it also lets you interact with the rest of the system, for
example through shortcuts.
From my understanding, it's up to the distribution and package management to
create those files, here's what Arch is doing for Emacs. Where the entry called
'Emacs (Client)' when clicked on, will try to create a frame by connecting to
the client if it exists and create it otherwise. It lives in
/usr/local/share/applications/emacsclient.desktop
. If you want to create one
that'll be picked up by system instead of that one, you should use
~/.local/share/applications/emacsclient.destkop
.
[Desktop Entry]
Emacs (Client)
Text Editor
Edit text
text/english;text/plain;text/x-makefile;text/x-c++hdr;text/x-c++src;text/x-chdr;text/x-csrc;text/x-java;text/x-moc;text/x-pascal;text/x-tcl;text/x-tex;application/x-shellscript;text/x-c;text/x-c++;x-scheme-handler/org-protocol;
-c "if [ -n \\"\\$*\\" ]; then exec /usr/local/bin/emacsclient --alternate-editor= --reuse-frame \\"\\$@\\"; else exec emacsclient --alternate-editor= --create-frame; fi" sh %F
sh emacs
Application
false
Development;TextEditor;
true
Emacs
emacsclient;
-window;new-instance;
new
[Desktop Action new-window]
New Window
/usr/local/bin/emacsclient --alternate-editor= --create-frame %F
[Desktop Action new-instance]
New Instance
%F
emacs
Right now, I'm keeping the systemd services and just let that file pick up the existing client.
I've also bound calling the client to Super+E to get a new frame
quickly wherever I am on the system and aliased e=$EDITOR
to open the editor
in the terminal.
Using multiple Emacs clients
I've had a couple annoyances since the Emacs configuration is loaded once when the client is created and changes, like themes, are reflected on the client and all frames connecting to it.
I generally change my theme based on the time of day in the GUI but I want it to
stay a dark theme in the CLI in general. I also want to load some packages only
in graphical mode, like org-mode
. For example,
This prompted me to write a systemd service file for the GUI and the other for the TUI. Which Emacs lets us do easily: you can name the socket you expose the client on.
This is getting out of hand, now there are two of them!
[Unit]
GUI)
Emacs text editor (:emacs man:1) /
info
[Service]
notify
/usr/bin/emacs --fg-daemon=gui
/usr/bin/emacsclient -s gui --eval "(kill-emacs)"
15
-failure
on
[Install]
.target
default
and
[Unit]
TUI)
Emacs text editor (:emacs man:1) /
info
[Service]
notify
/usr/bin/emacs --fg-daemon=tui
/usr/bin/emacsclient -s tui --eval "(kill-emacs)"
15
-failure
on
[Install]
.target
default
As a result, the desktop file needs to refer to the gui
client and the editor
in the terminal needs to refer to the tui
client.
[Desktop Entry]
Emacs (Client)
Text Editor
Edit text
text/english;text/plain;text/x-makefile;text/x-c++hdr;text/x-c++src;text/x-chdr;text/x-csrc;text/x-java;text/x-moc;text/x-pascal;text/x-tcl;text/x-tex;application/x-shellscript;text/x-c;text/x-c++;x-scheme-handler/org-protocol;
-c "if [ -n \\"\\$*\\" ]; then exec /usr/local/bin/emacsclient -s gui --reuse-frame \\"\\$@\\"; else exec /usr/local/bin/emacsclient -s gui --create-frame; fi" sh %F
sh emacs
Application
false
Development;TextEditor;
true
Emacs
emacsclient;
-window;new-instance;
new
[Desktop Action new-window]
New Window
/usr/local/bin/emacsclient -s gui --create-frame %F
[Desktop Action new-instance]
New Instance
--daemon=gui-temp -Q && emacsclient -s gui-temp --create-frame %F
emacs
#!/bin/sh
Finally, you can reload the systemd daemon and start then enable the services:
That's all for me, I honestly didn't think that trying to fix Ctrl+Backspace would become such an adventure.