The Developer’s Cry

a blog about computer programming

The Year Of Emacs On The Desktop

My custom keyboard has a green escape key. Why green? Well, hitting escape is usually a good thing when you’re a vim user. There are plenty of nice programmer’s editors out there, but when you’re frequently logged through a remote UNIX terminal, options become kind of limited quickly, and so we use vim, or its more modern sibling, neovim. Despite having like what’s a lifetime of experience with vim, I never truly felt comfortable with writing large codes in it. Emacs is that other editor that tends to raise eyebrows in my work environment, just because all we know is vim. And yet, emacs pops up in the most unexpected of places; book authors, script writers, poetic producers of poetry, and even game programmers on Windows (!) use emacs.

The first time I gave emacs a shot was quite some time ago, and honestly, I was quite turned off by it. Emacs scrolls pages in its own particular way to optimize for slow internet connections, and it may feel jarring, disorienting even. Later, on the second time around, it didn’t bother me.

Emacs had quite a reputation for being a slow hog. That reputation stems from the 1980-1990s however, a time when computers (and networks) were magnitudes slower than what we have today.

The other reputation it has is for being an operating system in itself, and jokingly, “a great operating system, lacking a good editor” (harr harr). The source of truth behind that joke is that emacs is actually a LISP interpreter that doubles as text editor. An unusual design decision that gives the editor super powers, as it allows programming in any extension you may think of.

Learning a new editor can be challenging, but emacs (and may I say, vim as well) take things to a new level. Anyway, we got this!

Basic setup

The config file of emacs is unlike any editor you’ve seen before; it is written in the LISP programming language.

Let’s first do some basic configuration. Give me a non-blinking cursor, line numbers, and please disable the bell, as well as the visual (screen flash!) bell.

;; ~/.emacs.d/init.el

(setq inhibit-splash-screen t)
(setq initial-scratch-message nil)

(setq debug-on-error t)

(set-frame-font "Source Code Pro Medium 11" nil t)
(setq-default cursor-type 'box)
(blink-cursor-mode -1)
(set-cursor-color "green")
(add-to-list 'default-frame-alist '(cursor-color . "green"))
(global-display-line-numbers-mode t)
(column-number-mode)
(setq column-number-indicator-zero-based nil)
(setq-default tab-width 4)
(setq visible-bell nil)
(setq ring-bell-function 'ignore)

(when (display-graphic-p)
  (tool-bar-mode -1)
)
(electric-pair-mode 1)

(setq tab-width 4)
(setq c-default-style "linux")
(setq-default c-basic-offset 4
              tab-width 4)
(setq backward-delete-char-untabify-method 'hungry)

;; enable cut/copy/paste/undo with C-x, C-c, C-v, C-z
(cua-mode t)

;; move backup and autosave files
(setq backup-directory-alist `((".*" . ,temporary-file-directory)))
(setq auto-save-file-name-transforms `((".*" ,temporary-file-directory t)))

You may have noticed I have disabled the toolbar. I like the screen to be more clutter-free, that’s all.

I actually prefer text mode (no GUI), so make a shell alias:

alias emacs='emacs -nw'

Redefining keys

The default keybindings of emacs feel alien in this day and age. While it’s best to actually learn the default keybindings (your emacs editor will ‘just work’ wherever you go), I still use my own keybinds.

;; ~/.emacs.d/init.el

;; load custom keybindings
(load (expand-file-name (concat user-emacs-directory "keybindings")))

and the actual keybindings:

;; ~/.emacs.d/keybindings.el

;; Open
(global-set-key (kbd "C-o") 'find-file)

;; Save (All)
(global-set-key (kbd "C-s") 'save-some-buffers)

There! Now we can work. Redefining C-s actually clobbers the Search function, so that’s not convenient. Let’s try fix that;

;; Find
(global-set-key (kbd "C-f") 'isearch-forward-regexp)
(define-key isearch-mode-map (kbd "C-f") 'isearch-repeat-forward)

In theory it’s possible redefine Ctrl-C to Cancel, but it’s kind of a nightmare. Take my advice and don’t touch Ctrl-C, and learn to use Ctrl-G as Cancel.

Besides straight-up redefining keys, you can also define combos, or you can even create a new prefix key that unlocks a whole subset of commands, which is called a ‘keymap’. For example, I’m quite used to WordStar commands prefixed with Ctrl-K;

(defvar-keymap my-wordstar-prefix-map
  :doc "WordStar-like map. It's a custom map, so it's not exact."
  "b" #'set-mark-command
  "c" #'kill-ring-save
  "y" #'kill-region
  "DEL" #'kill-region
  "v" #'yank
  "k" #'keyboard-quit
  "w" #'write-region
  "r" #'read-file
  "@" #'mark-defun
  "a" #'mark-whole-buffer
  "f" #'isearch-forward
  "g" #'replace-string
  "s" #'save-some-buffers
  "C-s" #'save-some-buffers
  "d" #'write-file
  "x" #'save-buffers-kill-emacs
  "q" #'kill-emacs
  )

(global-unset-key (kbd "C-k"))
(keymap-set global-map "C-k" my-wordstar-prefix-map)

If you think this bastardizes emacs, check out DOOM emacs evil mode, that turns emacs into vi, really.

Multiple buffers and windows

Switching buffers with Ctrl-B + arrows, but also via an interactive menu:

(global-set-key (kbd "C-x C-b") 'ibuffer)

(defvar-keymap my-buffer-prefix-map
  :doc "buffer commands."
  "b" #'ibuffer
  "C-b" #'ibuffer
  "<left>" #'previous-buffer
  "<right>" #'next-buffer
  "n" #'rename-buffer
  "s" #'save-buffer
  "C-s" #'save-buffer
  "w" #'write-file
  "d" #'kill-buffer
  "q" #'kill-buffer
  )

(global-unset-key (kbd "C-b"))
(keymap-set global-map "C-b" my-buffer-prefix-map)

I like having a split-window, where you can edit files side-by-side, or even edit the same file in two places at the same time.

(setq-default truncate-lines t)
(split-window-right)

Change windows with prefix key Ctrl-W;

(defvar-keymap my-window-prefix-map
  :doc "window commands."
  "w" #'other-window
  "C-w" #'other-window
  "<left>" #'other-window
  "<right>" #'other-window
  "d" #'delete-window
  "q" #'delete-window
  "|" #'split-window-right
  "-" #'split-window-below
  )

(global-unset-key (kbd "C-w"))
(keymap-set global-map "C-w" my-window-prefix-map)

Beware that this clobbers the original C-w command (kill-region).

Syntax highlighting and custom themes

Syntax highlighting works via an LSP server (treesitter) and a client (eglot). Long story short is that you will want to put in init.el:

(use-package eglot :ensure t)

Eglot comes with emacs by default. It knows many languages, but should you encounter a language it does not support that well (eg. Rust) then you can install the respective “rust-mode” package from the online repository.

(add-to-list 'package-archives
             '("melpa-stable" . "https://stable.melpa.org/packages/") t)

Now that we have syntax highlighting, let’s choose a theme.

However, I much prefer my own custom themes. Custom themes are (again) written in LISP; the color codes are called ‘font lock faces’.

A basic example of a dark theme:

;; ~/.emacs.d/themes/my-dark-theme.el

(deftheme my-dark "My It's Dark" :background-mode "dark" :kind "color-scheme" :family "dark")
  (custom-theme-set-faces 'my-dark
   '(default ((t (:foreground "#cccccc" :background "#000000" ))))
   '(cursor ((t (:foreground "#000000" :background "#00aa00" ))))
   '(fringe ((t (:background "#24292e" ))))
   '(line-number ((t (:foreground "#838994" :background "#000000" ))))
   '(line-number-current-line ((t (:foreground "#e1e4e8" :background "#000000" ))))
   '(mode-line ((t (:foreground "#282c33" :background "#c8ccd4" ))))
   '(region ((t (:background "#555555" ))))
   '(secondary-selection ((t (:background "#be5046" ))))
   '(font-lock-builtin-face ((t (:foreground "#ffffff" ))))
   '(font-lock-comment-face ((t (:foreground "#00cc00" ))))
   '(font-lock-constant-face ((t (:foreground "#eeee00"))))
   '(font-lock-doc-face ((t (:foreground "#00cc00" ))))
   '(font-lock-doc-markup-face ((t (:foreground "#00cc00" ))))
   '(font-lock-function-name-face ((t (:foreground "#cccccc"))))
   '(font-lock-keyword-face ((t (:foreground "#ffffff" ))))
   '(font-lock-number-face ((t (:foreground "#ffffff"))))
   '(font-lock-operator-face ((t (:foreground "#ffffff"))))
   '(font-lock-punctuation-face ((t (:foreground "#cccccc"))))
   '(font-lock-misc-punctuation-face ((t (:foreground "#cccccc"))))
   '(font-lock-preprocessor-face ((t (:foreground "#ffffff"))))
   '(font-lock-string-face ((t (:foreground "#00cccc" ))))
   '(font-lock-type-face ((t (:foreground "#ffffff" ))))
   '(font-lock-variable-name-face ((t (:foreground "#cccccc"))))
   '(minibuffer-prompt ((t (:foreground "#b8bb26" :bold t ))))
   '(font-lock-warning-face ((t (:foreground "red" :bold t ))))
   )

;; autoload
(and load-file-name
    (boundp 'custom-theme-load-path)
    (add-to-list 'custom-theme-load-path
                 (file-name-as-directory
                  (file-name-directory load-file-name))))

(provide-theme 'my-dark)

and put in init.el:

(add-to-list 'custom-theme-load-path (concat user-emacs-directory "themes"))
(load-theme 'my-dark)

By the way, M-x list-faces-display shows all possible faces.

Enjoy editing

Finally, after some hours of chewing on strands of LISP code we have tamed this beast of an editor, phew!

I feel we barely scratched the surface, however. Besides whacky things like using emacs as a mail reader, you can, for example, run emacs as a daemon to evade startup times. This may be nice for slow computers, like e.g. a Raspberry Pi. You can edit remote files over ssh using “TRAMP” (look it up). I just found out that function eglot-rename renames symbols in source code. Oh, eglot crashes with a LISP stack trace. Ah well, minor detail.

It’s funny how ancient emacs survives in spite of LISP, and at the same time, because of LISP. Maybe someone should give emacs the neovim treatment and replace LISP with something more digestible, you know, why not lua. After DOOM emacs evil mode, nothing shocks me anymore.