If you’re someone who writes text a lot everyday you’re probably using a word processor. If you develop software it may be an IDE or text editor. Some software in these categories that I can think of off the top of my head is:
- Visual Studio (🤮)
- Microsoft Word (🤮)
- Visual Studio Code (🙄)
- Some JetBrains IDE
- Some esoteric text editor (😏)
I recommend you stop using whatever you are and start using Emacs. If you use Vim you should start using Emacs. If you use a distribution of Emacs, start configuring your own. Emacs is the best text editor ever and to not, at the very least, try it out is a massive disservice to yourself.
There’s the TL;DR for anyone who can’t concentrate for more than 5 seconds without a subway surfers video on the side. For the rest of us, here are some arguments in case you aren’t magically convinced by the previous paragraph.
Other editors are like cities, Emacs is like your garden
Any word processing software is designed around some common denominator. The keybindings, layout and overall aesthetic are engineered around some philosophy of the developers, which is enforced on the user. How much you get to change of that is based on the whims of the people who make that environment for you.
In that sense, IDEs are like cities. You can choose your way around it, whether that be arbitrarily long or as fast as possible, but at the end of the day you are limited by the people who designed the city. You can’t just walk through a house to get to another street; you have to walk around the block.
On the one hand that means everyone has the same experience as you so it’s relatively easy to get advice when something doesn’t seem right. On the other hand if something really annoys you, there may not be any way to fix that; you’ve just got to get used to it.
For the average use case that may be tolerable. However, if you’re spending hundreds of hours in the same environment you’ll want to introduce changes to better fit the way your mind works. Depending on the editor and the change in question, this can be really trivial or a major refactor of the source code with hundred of man hours required. So what do you do? Either learn to accept it or move on to greener pastures…
Emacs, in comparison, is designed from the ground up to be edited and mutated for your wants and needs. There is a single design decision which makes Emacs the most configurable experience imaginable: it is a Lisp interpreter first. Beyond some internal and core functions, written in the implementation language C, all of Emacs’ features and options come from Emacs Lisp code.
This erodes the distinction between the user and the developer that other software has. Core packages from the developer are indistinguishable from stuff written by the user: it’s all just code. Since core developer code is usually loaded into the runtime before user code, any user configuration will shadow and take precedence over the developers. So any functions you overwrite in your configuration will be the version that Emacs uses.
Using a metaphor, Emacs is closer to a personal garden. When you’re starting out it has the absolute essentials: fertile soil and some space. It may also have a few things that the people who owned it before you planted, some shrubs and flowers. But, because it’s your garden, you can uproot, plant and modify anything you want. By doing this over and over again this patch of land becomes your own personal space. You effectively gain an understanding of every component as you come to use it, throwing away that which you dislike and don’t need, cultivating the stuff that you like and planting new things you want.
Screw your metaphorical bullshit, give me some examples
Alright, that’s understandable. What follows is a tour over some stuff I like in Emacs, specifically the things that allow you to extend it to do what you want. Later on I give some snippets of code for more complex behaviour you may want from Emacs.
What’s this key do?
Every input in Emacs is a thinly veiled function call. Emacs, as a
Lisp machine, is capable of inspection on any internals and code
evaluated. The (describe-key)
function asks you to input some key
combination, then presents you with the documentation for the
corresponding function. It’s bound to Ctrl+h k
by default and you
can even ask (describe-key)
about itself (by doing Ctrl+h k Ctrl+h k
).
The documentation includes stuff like:
- what other keys are bound to this?
- function signature
- text documentation by the developer
- link to the source code
With this you can quickly find documentation for what any input does.
Writing Lisp
With (describe-key)
you can find out the function that a key is
bound to. There’s actually a whole host of (describe-)
functions,
including (describe-function)
(which (describe-key)
uses
internally to display the documentation of the function bound to a
key). It’s bound to Ctrl+h f
by default.
But how do you write Emacs Lisp? You can use the info pages (bound to
Ctrl+h i
), a set of manuals for most things in Emacs. There’s a
manual on Lisp for non-programmers that should get you up to speed.
Here’s a little primer for you.
Lists are Lisp’s main poison of choice, the thing it uses to express everything. All expressions in Lisp are either lists or atomic values. An atomic value is something that isn’t a list, like an integer or a string. Let’s see this in action:
(+ 2 3)
This expression adds the numbers 2 and 3 together. Note how +
is at
the start of the list, and the arguments (2 and 3) are after it, which
is prefix notation. This is true for all function calls in Lisp: a
list with the first member being the function name, followed by any
arguments.
A benefit of this is that we can trivially write variadic functions.
In fact, the mathematical operators are all variadic functions, so
instead of writing (+ (+ 3 4) (+ 5 6))
we can just write (+ 3 4 5 6)
.
To define a function, we use the defun
function. defun
takes a
name, an argument list then the body of the function. Let’s make a
function which adds one to the argument given:
(defun add-one (x)
(+ x 1))
To call this function, we use the same notation as for all other
functions, (add-one 2)
.
Rebind anything
Now you know what’s bound to a key, how do you change it? Just call a Lisp function of course!
Say I want to bind Ctrl+r
such that when I press it Emacs tells me
the time1. Keys are bound to commands, functions that have
(interactive)
at the start of the body. The function has to tell us
the current time when we call so we can’t just hard code the value.
Something like this should work:
(defun tell-me-the-time ()
(interactive)
(message "%s" (format-time-string "%H:%M:%S")))
I used the documentation of format-time-string
to write the format
string in this example, and if you’re actually using Emacs at this
point you can really easily look up documentation for the functions
I’ve called.
How do I bind this to a key? There’s a few ways to do so in default
Emacs, but the way I recommend is bind-key
. Looking at the
documentation, the signature for bind-key
is:
(bind-key KEY-NAME COMMAND &optional KEYMAP PREDICATE)
&optional
just indicates that the arguments after it are not
required, so we can ignore KEYMAP
and PREDICATE
. From the
documentation, KEY-NAME
can be a “key vector” or a string
representing the keys you want to bind. COMMAND
is a reference to
the command we want to bind to that key. So for our use case, the
bind-key
call would look like:
(bind-key "C-r" #'tell-me-the-time)
Emacs has its own notation for key bindings. It’s terse and allows one to easily communicate a key combination over text, certainly easier than writing out the spoken form. A quick summary of the syntax:
C-(something)
means “Control+(something)”M-(something)
means “Alt+(something)” (pronounced Meta (something) for historic reasons from the era of dinosaurs)C-M-(something)
means “Control+Alt+(something)” (pronounced control meta something)(a) (b)
means perform (a) then (b). For exampleC-h k
means inputControl+h
thenk
That last rule means you can make arbitrarily long combinations. To
encode, say, the keyboard combination of Ctrl+Alt+m
then Alt+x
then Control+y
then finally Control+z
, you’d write C-M-m M-x C-y C-z
. The latter is terser than the natural language equivalent and
is a valid string for use in bind-key
.
Any key can be bound to a function. Anything you don’t like can be bound to something that works for you, using this simple format. It’s difficult to really understand how incredible this is till you try it yourself.
Install external packages
To install external packages you declare them via package-install
.
For example, if I wanted to get the 2048 game in Emacs, I could
install it from Melpa like so:
(package-install '2048-game)
Oh yeah, you can play 2048 in Emacs. By default Emacs comes with Tetris and snake as well. All written in Lisp. These are relatively simple applications in the grander scheme of things: RSS readers, mail clients, fully featured git clients and an HTTP request package for JSON APIs are just some of the things you can install and use. Though Emacs isn’t super popular it’s had a lot of very smart and innovative people use it and make packages for it.
They all follow the same conventions you’d expect from Emacs as well:
you can figure out what any function does via describe-function
, any
key bindings that these packages setup can be describe-key
’d, and
you can rebind any key in these applications through bind-key
. You
can run these systems in parallel i.e. have your RSS client up,
reading a new article, while also playing tetris
.
From this point we’re going to be looking at some interesting problems and Lisp code that allows you to solve them.
Automatically format code for C/C++ files
Whenever I’m in a C/C++ file I want to format it automatically using
clang-format
. Most editors would either support it through a custom
extension or an in built option, but we’re going to write it
ourselves. clang-format
is an Emacs package that comes with clang
by default, so as long as you have clang
you can use it.
How do we figure out when Emacs is saving a C/C++ file? In event
driven programming there is the notion of “hooks”, something which can
have functions registered to it which run when an event occurs.
Emacs’ hooks are very simple: each event usually has a variable named
event-hook
which is a list of functions when that event occurs. You
can add a hook via add-hook
. Looking through the documentation,
there exists a before-save-hook
variable. Perfect!
Here’s a function which checks the major-mode for C or C++ then does
clang-format-buffer
.
(defun clang-format-on-save ()
(when (or (eq major-mode 'c-mode)
(eq major-mode 'c++-mode))
(clang-format-buffer)))
before-save-hook
triggers on saving any file. We want to affect
only C/C++ files, which is why we do the conditional check first.
When Emacs is visiting a file, that file has a major mode activated in
it. A major mode is how Emacs considers that file, such as a text
file or a source code file. There are major-modes for essentially
every type of file, including C and C++ files. major-mode
is a
variable which we can test against to ensure we’re only working on
C/C++ files.
To register this to the before-save-hook
, we just do the following:
(add-hook
'before-save-hook
#'clang-format-on-save)
Compile code when I save
Say I’m working on something that has a build step. I want to build
whatever I’m working on when I make a change to a file. This can
apply for writing some LaTeX or developing software. For the sake of
an example, let’s say I’m working on my
fork of DWM. It has a
Makefile, so I can call make
to build the project.
Like before, we want to register a function to some hook. We’d only
want to compile after saving a file, hence we need to use
after-save-hook
. What are the conditions in which Emacs should
build the project?:
- Be in a C file
- Be in the DWM source code directory
We already know how to check (1): (eq major-mode 'c-mode)
. The
second one requires a bit more work. To get the current directory of
the file we’re visiting we can use (file-name-directory buffer-file-name)
which returns the directory as a string, where
buffer-file-name
is the current file name. We just need to check if
this string is equivalent to the DWM source code directory, which we
can use string=
for.
Here’s the function:
(defun compile-dwm-on-save ()
(when (and (eq major-mode 'c-mode)
(string= "~/.local/src/dwm"
(file-name-directory buffer-file-name)))
(compile "make")))
compile
is a function for running some shell command in Emacs
related to building some application. To register this function onto
the hook, we do the same as before:
(add-hook
'after-save-hook
#'compile-dwm-on-save)
Message “Going to the next line” whenever I go to the next line
Whenever I go to the next-line, I want to use the message
function
to provide a log of the fact the editor is going to the next line.
There are a couple of ways to do this:
- Define a function which logs then goes the next line, then replace
any binding and function call to
next-line
with this new function - “advise” the
next-line
to run some code after execution
Let’s do the latter. First let’s make a function that does the log I want.
(defun log-next-line ()
(interactive)
(message "Going to the next line..."))
How do I fit this into next-line
? Through the bespoke “advice
system” of Emacs:
(advice-add 'next-line :after #'log-next-line)
You can read this expression as “after the next line function has run,
run the log-next-line
function”. The :after
is a positional,
which I can replace with :before
and :around
to get
log-next-line
to run at different points of next-line
’s execution.
Cool right? You can advise any function you want, which adds a whole layer of customisation. Can you think of another system, let alone a text editor, that can do something like that?
Customise for different machines
You may use Emacs on different machines. Sometimes you need configuration that is specific to a certain machine. How do you do that?
A quick look through documentation gives the (system-name)
function,
which returns a string representing the hostname of the machine. You
can run conditionals on it to write system specific code!
Here’s an example where I set the colour of keywords for different machines.
(cond
((string= (system-name) "my-laptop")
(set-face-attribute 'font-lock-keyword-face nil
'(:foreground "blue")))
((string= (system-name) "my-desktop")
(set-face-attribute 'font-lock-keyword-face nil
:foreground "orange")))
Cond, standing for conditional, is just a Lisp form. Each element is
a list, the first member being a test. If the test is successful, the
code is executed. If not, it goes to the next test. So if (string= (system-name) "my-laptop")
is true, then run the corresponding code.
The set-face-attribute
function just sets certain attributes in how
Emacs displays stuff. In this case we want to change the foreground
attribute of the font-lock-keyword-face
which you can consider a
variable holding a value that Emacs uses to colour certain things.
Because everything is an expression in Lisp, we can put that cond inside of the set-face-attribute for a simpler looking expression:
(set-face-attribute
'font-lock-keyword-face nil
(list :foreground
(cond
((string= (system-name) "my-laptop")
"blue")
((string= (system-name) "my-desktop")
"orange"))))
Vim in Emacs, or why Vim is irrelevant
I hate Emacs’ default keybindings. They’re awful and can literally cause RSI. I like Vim’s modal layout. But I don’t want to use Vim because it’s not Emacs. So what do I do?
There’s this external package called evil
or the Emacs Vi Layer. It
brings Vim’s modal system in all its glory to Emacs. I’m not kidding,
the entire editor can just be ported over to Emacs.
There are other packages which port over stuff like evil-surround
and evil-commentary
using just Emacs alone.
With this, Emacs is an operating system with a good text editor.
Conclusion
I’ve provided some interesting arguments to try and use Emacs. Though it’s kinda oriented around programmers, Emacs is actually heavily used in academia and the writing industry; there are people with non technical backgrounds who use Emacs for writing documents. Can you think of another editor that has such a wide set of use cases?
I hope you try out Emacs. The default experience can be quite horrible, Emacs does look weird out of the box, and the learning curve is quite steep. But once you get past that, you’ll see how truly unique it is while being incredibly powerful.
-
Bit arbitrary, I know, but it demonstrates the things I want from this example. ↩︎