Emacs notes
eight megabytes and constantly swapping
Why Emacs?
Currently, I’m using VS Code as my daily driver, however I’m trying Emacs to find out if it can replace VS Code. I’ve seen a constant buzz about it, which has recently grown louder with the upcoming release of Emacs 29; so I decided to give it a shot as well. Some of the things that have drawn me to Emacs is the alleged integration and customization that it provides, as well as the keyboard centric philosophy for getting things done.
I also briefly considered Vim, but I wanted a tighter coupling between my tooling and editor (to mimic an IDE as closely
as possible). I’ll definitely concede that Vim’s modal editing is superior to Emacs’ complex Meta
and Command
sequences, but I’ve seen various ports of Vim’s keybindings to Emacs (such as Evil
mode) so if I really need to scratch that itch, it’s possible to bring that into my
workflows as well.
Installation
At the time of writing, Emacs 29 isn’t in Ubuntu’s repositories so I decided to compile it from source. I picked Emacs 29 because there’s a bunch of exciting features such as native Tree-sitter support and a native LSP client. I’m not going to go too in depth into the installation steps because it varies significantly from platform to platform, but will give a rough outline. Naturally, filling in the blanks will be left as an exercise for the reader.
Linux systems (with apt
)
To use native comp we will want to install libgccjit
. sudo apt install libgccjit0 libgccjit-{your gcc version}-dev
should be sufficient. After cloning Emacs somewhere, run:
$ ./autogen.sh
$ ./configure --with-json \
--with-cairo \
--with-xwidgets \
--prefix=/opt/emacs/ \
--with-x-toolkit=gtk3 \
--with-tree-sitter \
--with-native-compilation \
--with-modules
$ make -j $(nproc)
$ sudo make install
Darwin systems
We also need to install libgccjit
. This comes with some complications since on Mac gcc
is effectively just clang
so there’s some path mangling that needs to happen to
make this work.
$ ./autogen.sh
$ ./configure --with-json \
--with-cairo \
--with-tree-sitter \
--with-native-compilation \
--with-modules \
"CFLAGS=-DFD_SETSIZE=32768 -DDARWIN_UNLIMITED_SELECT"
$ make -j $(nproc)
$ make install
Note that there’s no install prefix, instead Emacs gets bundled into an Emacs.app
folder which you can then move to
the Applications
folder. Also see the extra CFLAGS
string that we pass in. Those are for ensuring that we do not run
out of file descriptors on MacOS, since the limit seems to be hardcoded at compile time (see this blog
post for more info on it). I’ve run into this issue while
using eglot
on MacOS with large Python codebases, so we definitely do not want to run into issues caused by running
out of FDs.
Running emacs
as a daemon
Because Emacs
has a long startup time, we want to run it as a daemon in startup, and then use emacsclient
to
interact with files etc. On Ubuntu I used systemd
and launchd
on Macos.
Cheat sheet
See this cheat sheet for a quick lesson on Emacs keybindings and navigation. I’ll assume you read the cheat sheet from now on.
Workflows
Ultimately, the workflows I create in Emacs are going to decide whether I use it long term. My rough goal is to get workflow parity with VS Code, and then use it as a daily driver.
The meta workflow
You must have a workflow to master workflows
Emacs shines at allowing users to explore the editor in ways that other platforms do not. Let me draw your attention to
the “Getting Help” section in the cheat sheet. The helpful combination C-h-k
allows you to read the documentation for
keybindings. What does C-x C-f
do? Type C-h-k C-x C-f
to find out.
Generally, keybindings call elisp functions. Most functions have documentation which is present in the manual, and is
shown in the *Help*
buffer when you open the documentation using the keybindings above. In the *Help*
buffer, it’s
possible to navigate to the function definition and see how it’s implemented. To access documentation for functions that
you don’t know the keybinding to, use C-h-f
. Similar facilities are provided for getting help related to values
(C-h-v
).
Another useful feature is discovering functions that might be useful to you. With the help of
vertico, orderless, and
marginalia, we can get a beautiful completion mechanism when running M-x
. For
instance if we want to see some commands related to eglot
, we can do M-x eglot
and in the minibuffer we will see
many functions that have “eglot” in the name, and have a short description of what it does in the margins.
Window management
Emacs has powerful facilities for managing windows and buffers, see the cheat sheet for more info. C-x-b
is key for
switching between buffers quickly. Think of it as VS Code’s C-p
.
File management
I tend to use dired
for visualizing directories, and consult
for finding files (bound to M-s-d
). dired
doesn’t
have the tree based directory view that one may be expecting. While it’s possible to get packages to change that, I
don’t miss the tree view much, so I haven’t bothered to set it up.
Full text search
consult
is again instrumental here. Some highlights are M-s-r
for ripgrepping. Use it like so M-s-r search phrase -- ripgrep args
. Examples:
- Finding the string
if (query) {
as a literal:M-s-r if (query) { -- -F
(-F
means this string exactly). - Finding “processing” case sensitively:
M-s-r processing -- -s
- Finding “Tree” case insensitively, but only in python files:
M-s-r Tree -- -g *.py
- Excluding file extensions:
M-s-r thing -- -g !*.rs
See the ripgrep guide for more usage details.
For searching in a buffer you can use isearch
(C-s
and C-r
- forwards and reverse) or consult
lines
(M-s-l
). The former is useful for navigating in a buffer, the latter useful for collecting all search results in a
buffer.
Also, using the embark
package, one can “collect” all of the search results, and apply modifications to them across
files, which is useful. After you have search results, hit M-x
and pick embark-collect
. This will create a new
buffer with the search results which you can modify with the wgrep
package, or use for navigation. This is applicable
to all of the consult
search operations (lines, ripgrep, files) which makes it extremely powerful.
Git
magit
is a very ergonomic git integration for Emacs. Open magit
by doing C-x-g
and type ?
to see the help for
the controls. Usually I only need to do stuff like:
s
for staging files, or parts of filesc
for entering commit mode (to create new commits, amend old ones)p
as well asp p
for pushing to remotesf
for pullingb
interacting with branches
There’s also extensive integration for interactive rebasing, amending commits, managing remotes, and most other things
that can be accomplished through the git command line interface. I haven’t needed to pop open a terminal to interact
with git because magit
has got everything covered.
Merge conflicts
smerge
is great for taking care of merge conflicts. Usually smerge
commands are accessible with C-c ^
followed by
another key (when you’re in smerge
mode). magit
integrates with smerge
to enable the appropriate mode when a merge
conflict occurs.
Language servers
I use eglot
as my language server client. lsp-mode
is also popular, and maybe better (?) but I haven’t tried it
because eglot
is built in and seems to work fine so far.
I use the language server for C++, Rust, and Python. Using clangd
, rust-analyzer
, and pyright
for the language
servers respectively. For Pyright I had issues on MacOS because of an insufficient number of file descriptors (but
that’s solved above). For C++ you must have a compile_commands.json
somewhere, which is pretty easy to generate, but
needs to be regenerated if you stomp on the build directory. rust-analyzer
just works (tm).
Pain points
There’s definitely some pain points when using Emacs. Some of the big ones are
- Performance. Sometimes it’s a little slow (the start up time of
magit-magic-revert-mode
can be measured in seconds) - Obscure failures. Sometimes you run into strange issues (such as the one mentioned above: not enough file descriptors). Usually though, someone beat you to it and has also found a solution
- Ubiquity. There’s no guarantees it’s installed on the system you’re SSHing into, but if it is installed, it’s
guaranteed to not be set up just how you like it. Allegedly
TRAMP
mode can work around this (SSHing using Emacs and editing remote files using your local Emacs) but this increases friction, so I haven’t tried it yet. I’ll get around to trying it which might invalidate this point
Verdict
I’ve found Emacs to be a fun editor which I’m going to continue using because of its flexibility and other positive traits mentioned above. I’m not going to advocate that other people start using it since their current setup works just fine for them, but I won’t shy away from explaining why I enjoy using Emacs. Ultimately, you don’t have to use Emacs, but your editor should at least be as good as Emacs.