In the GNU/Linux world there are two major text editing programs: the minimalist vi […] and the maximalist emacs. I use emacs, which might be thought of as a thermonuclear word processor.

— Neal Stephenson, In the Beginning was the Command Line

The closest thing you can get to a tiny Lisp machine running on your Unix that’s still adequately maintained. It never ceases to amaze me what Emacs can do, even though it gets less attention now than before since lots of programmers have switched to trendier editors.

Setting up Emacs on Mac OS

Emacs, historically, has not really been a good Mac citizen. You could get it for Mac OS going back even to System 6 or something, I believe — but it never really behaved like a Mac application. This led to various semi-forks like Aquamacs which were meant to provide more Mac-like behaviour by default. Unfortunately, Aquamacs wasn’t maintained for a while, and though it now has someone working on it again, it’s still stuck with an Emacs 24 backend and moving forward to Emacs 27, the current version, seems like it’s going to take a while.

Fortunately I have found that rmsmacs straight from the GNU Project has got a lot better in its Macintosh incarnation. The default build is no longer Carbon Emacs (which was a direct descendent of that old version for classic Mac OS) but Cocoa Emacs (a descendent of Emacs for the NeXT machine), and this has been improved to the point where, with a few tweaks, it can be made to behave roughly like a real Mac app. (Or close enough, in an age where WebKit-based monstrosities like Atom and VSCode apparently pass for ‘real Mac apps’, you kids why I oughta.)

Disabling the tool bar

What are all those buttons anyway? They’re ugly and I’m pretty sure I don’t need them. The menu bar is more useful.

(if (fboundp 'tool-bar-mode) (tool-bar-mode -1))
(custom-set-variables '(tool-bar-mode nil))

Configuring key bindings

Emacs comes with the Command key bound to Emacs’s Super key (s-) and helpful Mac-like key bindings for some common operations bound to s-c, s-v, s-q, etc. We want to maintain those bindings, and add some more of them.

If you’re on a Mac laptop or have a compact keyboard with no number pad, the Fn key is well placed to serve as a Meta (M-) key while still allowing you to use Control for Emacs C-, Option for alternate characters according to the standard Mac OS keyboard layout (a must for those of us programming on British Mac keyboards, where the Option key is needed to type the hash character!), and Command to access Mac OS keyboard shortcuts like ⌘C, ⌘V, etc. Also, the Fn key is almost exactly where the Meta key was on the Lisp Machine keyboard, for which Emacs was originally designed! (But who knows what I’ll do for a Meta key if I end up switching back to a full-size Mac keyboard, where the Fn key is way over in the block between the main keyboard area and the number pad.)

The Option key is also used with the arrow keys in Mac OS to move the cursor by word or by page. Fortunately, Emacs lets us bind the Mac modifier keys differently depending on what kind of key we use them in: we can maintain the default behaviour for typing alternate characters when Option is used together with letter, number, or symbol keys, but still let it be bound to other things when used together with ‘function’ keys (those which don’t type a character but do something else). We bind it to Emacs’s Alt key (A-). Unfortunately, this means that if we want to bind combinations of Option plus something else (like ⌘⌥W), we have to enter those somewhat counter-intuitively as e.g. ⌘∑, where ∑ is the character you get by typing ⌥W. But it’s a solution.

I also bind ⌘⌥-arrows to forward-sexp and backward-sexp, moving the cursor by expresson (or other unit, sometimes depending on mode).

;; use fn as meta
(setq ns-function-modifier 'meta)

;; ... and allow us to bind alt for function keys, but don't affect its default meaning for letters etc.
(setq ns-alternate-modifier '(:ordinary none :function alt :mouse alt))

;; set Cmd-Shift-brackets to cycle buffers
(global-set-key (kbd "s-{") 'previous-buffer)
(global-set-key (kbd "s-}") 'next-buffer)

;; set Cmd-W to kill buffer and Cmd-Opt-W to close frame
(defun kill-current-buffer ()
  "Kill the current buffer"
  (kill-buffer (current-buffer)))
(global-set-key (kbd "s-w") 'kill-current-buffer)
(global-set-key (kbd "s-∑") 'delete-frame)

(global-set-key (kbd "s-<up>") 'beginning-of-buffer)
(global-set-key (kbd "s-<down>") 'end-of-buffer)
(global-set-key (kbd "s-<left>") 'move-beginning-of-line)
(global-set-key (kbd "s-<right>") 'move-end-of-line)
(global-set-key (kbd "s-<backspace>") (lambda ()
                                        (kill-line 0)))

(global-set-key (kbd "A-<up>") 'scroll-down-command)
(global-set-key (kbd "A-<down>") 'scroll-up-command)
(global-set-key (kbd "A-<left>") 'backward-word)
(global-set-key (kbd "A-<right>") 'forward-word)
(global-set-key (kbd "A-<backspace>") 'backward-kill-word)

(global-set-key (kbd "A-s-<left>") 'backward-sexp)
(global-set-key (kbd "A-s-<right>") 'forward-sexp)

;; Cmd+Shift combinations
(global-set-key (kbd "s-G") 'isearch-repeat-backward)
(global-set-key (kbd "s-Z") 'undo-redo)

The standard Emacs autocomplete shortcut, M-<tab>, doesn’t work with this setup because Fn doesn’t register in combination with Tab for some reason — so we make an exception and bind that one to A-<tab> instead/as well.

;; fn+tab doesn't trigger M-<tab> for some reason, so use A-<tab>
(global-set-key (kbd "A-<tab>") 'completion-at-point)

As Mac users we expect that selecting text and then typing will replace the text we select with what we typed. Emacs supports this with the global minor mode delete-selection-mode. We also want right clicking to bring up a contextual menu. Ideally this menu would contain the typical contextual cut/copy/paste options etc, but for now it’ll do to have a buffer list, as when you ⌃click, which is supposed to be an alias for right clicking systemwide in Mac OS.

;; right click to bring up a contextual menu
;; todo: make this menu bigger/better
(global-set-key (kbd "<down-mouse-3>") 'mouse-buffer-menu)

;; delete-selection-mode
(delete-selection-mode 1)

Meanwhile, most Mac editors support shortcuts with the Command key for selecting a whole line at once and for jumping to a particular line number. I used BBEdit for many years, so I’m used to ⌘L and ⌘J.

;; select line
(defun select-current-line ()
  "Select the current line, including the newline character at the end"
  (let ((lbgp (line-beginning-position)))
    (set-mark lbgp)))
(global-set-key (kbd "s-l") 'select-current-line)
(global-set-key (kbd "s-j") 'goto-line)

Disabling the bell

Emacs is a noisy text editor and likes to make the Mac OS system alert sound a lot.

(setq ring-bell-function 'ignore)

Miscellaneous useful configuration

Editing Makefiles

Recall that Make, even in its GNU incarnation, cannot handle build commands being prefixed by spaces instead of tabs. To make sure I can see when I accidentally used spaces, I turn whitespace-mode on in makefile=mode, and also set it not to highlight lines it thinks are too long (which happen often in Makefiles with moderately complex build commands):

;; don't highlight long lines in whitespace-mode
(require 'whitespace)
(setq whitespace-style (remove 'lines whitespace-style))

;; configure whitespace-mode to automatically start with makefile-mode
(add-hook 'makefile-mode-hook 'whitespace-mode)

Editing HTML

When I write prose in HTML, I like to use the ‘simple markup’ philosophy, leaving out essentially all tags in the source code which are inferred by the parser, including <body>, </p>, </li>, etc. Unfortunately none of the common modes for editing HTML understand that some of these close tags can be omitted, and try to indent everything as if you’ve got nested <p> elements (which are impossible in HTML syntax). So I use the ancient PSGML, which the kind people from the Debian project are patiently keeping alive on modern Emacs. Grab the latest patched version (the patchlevel is the number after the hyphen in the version number) and it should work fine on Emacs 27. Then I use Christopher Madsen’s psgml-html-mode to provide HTML editing. (Set sgml-set-face to nil or you’ll get font conflicts.)

Although PSGML knows enough about the structure of HTML to provide sensible indentation behaviour, its actual default indentation is pretty poor. I’m currently experimenting with my own psgml-indent to try to improve it — when I’ve more extensively tested it, I’ll consider releasing it.

Unfortunately, PSGML is incompatible with the built-in sgml-mode (this is even mentioned in the Emacs PROBLEMS file) and needs to be loaded before anything which will load it. This includes nxml-mode, which is better than PSGML for editing XML, but depends on a few utility functions etc. which are defined by sgml-mode. So after you load PSGML, but before you load nxml-mode, load sgml-mode-fix which provides those utilities, and only those utilities, from sgml-mode.

Disabling automatic hard wrap

Emacs likes to hard wrap text in most prose modes by default. Sometimes I want this, but usually not: hard wrapping messes up your default Git diffs if you changed a few words within a paragraph and that caused the remainder of the paragraph to reflow. (There’s git diff --word-diff, but GitHub and GitLab don’t support viewing diffs like that.)

;; auto-fill-mode is kill
(remove-hook 'text-mode-hook 'turn-on-auto-fill)
(add-hook 'nxml-mode-hook 'turn-off-auto-fill)
(add-hook 'markdown-mode-hook 'turn-off-auto-fill)
;; ... add more here

One space after a full stop

When manually hard wrapping (‘filling’, in Emacs jargon), Emacs expects and places two spaces after a sentence full stop by default. In order to not look like idiots (or worse, Americans), set:

;; when filling, don't convert one space after full stop to two
(setq sentence-end-double-space nil)

Et cetera.

The remainder of my Emacs config is just setting up various packages in exactly the way that you’d find documented in those packages’ installation/configuration instructions.