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.

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:

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:

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

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.