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!
- yank and kill have different meanings than in vim
C-cmeans Ctrl-C- press
C-x C-cconsecutively to exit the editor M-xmeans Meta/Super X. If you don’t have a meta key, you may also press Escape- press
M-xto execute a command - press
C-gto cancel prompts or actions - press
C-spaceto select visual block mode - cut/copy/paste/undo using
C-x,C-c,C-v,C-zis named CUA mode, and must be explicitly enabled - plugins are called packages
- ELPA and MELPA refer to online package repositories
- what are major and minor modes?
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)
M-x package-refresh-contentsM-x package-list-packagesto interactively browse and install packages.
Now that we have syntax highlighting, let’s choose a theme.
M-x customize-themeM-x load-theme modus-vivendi
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.