Note: to navigate this file on GitHub use the navigator on the top left.
This document is written in [[https://orgmode.org/][org-mode]], a very powerful package for Emacs that many regard as its 'killer feature'. At its core, =org-mode= is a markup language like markdown. However it has deep integration into Emacs that makes it much more powerful. One very cool features of =org-mode= is that it lets you embed source code blocks in documents and then evaluate them. This lets us write a document with Emacs lisp in it and then load it as our configuration file. This is called a [[https://harryrschwartz.com/2016/02/15/switching-to-a-literate-emacs-configuration][literate Emacs config]].
You need to download this file, =configuration.org=, the =init.el= file and put them both into =~/.emacs.d/=. If you already have an =init.el= then just make sure that this line is in there somewhere:
(org-babel-load-file (concat user-emacs-directory "configuration.org"))
This will make sure this config is loaded at init time.
The first thing we're going to add is a command to reload the config file to save some typing:
(defun reload-config () (interactive) (org-babel-load-file (concat user-emacs-directory "configuration.org")))
Reloading the config can now be done with =M-x reload-config=.
Now for editing the config. I always get tired of having to =C-x C-f ~/.emacs.d/configuration.org~= so let's create a little function to solve that.
(defun edit-config () (interactive) (find-file (concat user-emacs-directory "configuration.org")))
I thought I'd write a few words in case anyone reading isn't living the Emacs dream and wonders why in the age of Vim, VSCode, IntelliJ, etc, we would even consider using Emacs.
In a word: trust. Trust that Emacs is unlikely to contain any user-hostile functionality and trust that Emacs will be actively maintained for the foreseeable future, so time invested in learning Emacs is unlikely to be wasted.
The first point is very important. Emacs is developed by volunteers and sponsored by the GNU project. It is Free Software and the source code is available. There is nothing up the developer's sleeve. Compare this to VSCode:
Not great!
The second point is about software survivability. Yes Emacs is a little bit of a learning curve but it will very likely last you your career. The initial release of GNU Emacs was 1976, almost 50 years ago. The idea of learning a tool once and being able to depend on it indefinitely is very appealing.
So trust is a big factor. But trust alone is not the only reason to use Emacs. Emacs has a massive and thriving ecosystem of packages that not only add support for new languages, but can also add completely new features to the editor itself. Many of them are best in class, like =org-mode= and Magit. It's also very easy to write a small bit of code and support some bespoke bit of your workflow. There is no other editor as powerful as Emacs.
** Package Repositories
The default package repository is a little bit bare, and most interesting packages are hosted in [[https://melpa.org/][MELPA]]. We add MELPA as an available package repository:
(require 'package) (add-to-list 'package-archives '("melpa" . "https://melpa.org/packages/") t) (package-initialize)
** Installing use-package
=use-package= is a nice little package to help install and configure Emacs packages. If it's not installed, we refresh our package cache and install it:
(unless (require 'use-package nil 'noerror) (package-refresh-contents) (package-install 'use-package))
** Updating Packages
Emacs doesn't automatically upgrade packages, but luckily there is a package that will do that for us.
(use-package auto-package-update :ensure t)
~chomp~ deletes whitespace at the start and end of a string.
(defun chomp (str) "Chomp leading and tailing whitespace from STR." (while (string-match "\`\n+\|^\s-+\|\s-+$\|\n+\'" str) (setq str (replace-match "" t t str))) str)
Generating a randomish string is useful occasionally. Here is a function that generates a random alpha-numeric character and another that chains that together to create a string.
(defun random-alnum () "generate a 'random' alpha-numeric character" (let* ((alnum "abcdefghijklmnopqrstuvwxyz0123456789") (i (% (abs (random)) (length alnum)))) (substring alnum i (1+ i))))
(defun random-alnum-string (length) "generate a 'random' alpha-numeric string of length `length'" (let ((output "")) (dotimes (_num length output) (setq output (concat output (random-alnum)))) output))
** Setting the Font
My currently preferred font is Fira Code, and size 14 is a decent size for me. Adding this to the ~default-frame-alist~ ensures that this font is the default for all Emacs frames.
(setq default-frame-alist '((font . "Fira Code-14")))
** Setting the Theme
(use-package spacemacs-theme :ensure t :defer t :init (load-theme 'spacemacs-light 't))
** Remove the Toolbar and Scrollbar
I don't like the toolbar or the scrollbar so I disable them:
(when (display-graphic-p) (tool-bar-mode -1) (scroll-bar-mode -1))
I keep the menu bar around because it does come in handy every now and then. However, it just takes up space in a non-interactive CLI session so I disable it when not running graphically.
(unless (display-graphic-p) (menu-bar-mode -1))
** System Bell
No thank you. Disable!
(setq ring-bell-function 'ignore)
** Blinking Cursor
I don't like blinking cursors. Get it gone!
(blink-cursor-mode 0)
** Using 'y' or 'n' rather than 'yes' or 'no'
Sometimes emacs will ask you a question and expect you to type 'yes' or 'no' which is a little tedious. This change will get emacs to prompt for 'y' or 'n' instead.
(defalias 'yes-or-no-p 'y-or-n-p)
** Smooth Scrolling (Emacs 29+)
If we have the new pixel scrolling mode, then we should enable it.
(when (boundp 'pixel-scroll-precision-mode) (pixel-scroll-precision-mode 1))
Using C-o to move around windows can be a pain. Using windmove lets you move around with arrow keys instead.
(windmove-default-keybindings 'meta)
** Tabs and Spaces
I prefer to use spaces instead of tabs:
(setq-default indent-tabs-mode nil)
Emacs also doesn't indent on the 'Enter' key by default. That's easily changed with a key binding:
(global-set-key (kbd "RET") 'newline-and-indent)
** Automatically Updating Files When They Change on Disk
(global-auto-revert-mode)
** Line Numbers
Line numbers are great for programming but not so great for other modes, like =org-mode= and terminals. Therefore we only turn on line number mode when we're in the fundamental programming mode:
(add-hook 'prog-mode-hook #'display-line-numbers-mode)
** Automatically Adding Parenthesis
I find it useful to have Emacs automatically add parenthesis, especially when messing around with Emacs lisp. Instead of using the built-in packages, I use one called [[https://github.com/Fuco1/smartparens][smartparens]] since it's a little bit smarter:
(use-package smartparens :ensure t :init (require 'smartparens-config) (smartparens-global-mode))
** Custom File
Emacs stores some configuration in a file, that by default is the =init.el= file. This makes vendoring the =init.el= in Git tricky, so we can change it to its own file:
(setq custom-file (concat user-emacs-directory "custom.el")) (when (file-exists-p custom-file) (load-file custom-file))
** Backups
Emacs defaults to storing backup files in the same directory as the file being edited. This can create a jumble of files and confuse git. This little bit of configuration ensures that backup files are stored in Emacs' working directory instead:
(setq backup-directory (concat user-emacs-directory "backups"))
(unless (file-directory-p backup-directory) (mkdir backup-directory))
(setq backup-directory-alist `((".*" . ,backup-directory)))
** Hash Key
Sometimes Mac keyboards make it difficult to enter in a hash symbol. To (sort of) get around this, I add a new keybinding it enter a hash key.
(global-set-key (kbd "C-c 3") (lambda () (interactive) (insert "#")))
Emacs has a built in, live, spellchecker called Flyspell. We use flyspell for org mode configuration later on, so we need to make sure it's configured to use an English (GB) dictionary:
(setq ispell-dictionary "british")
I find it useful to quickly spin up new temporary files. I've created a quick elisp function to do this.
(defun random-temporary-file-name () "creates a random temporary file name" (concat "emacs-tmp-" (random-alnum-string 10)))
(defun temporary-file (file-name) "spawns a quick temporary file" (interactive "sFile name (can be empty): ") (let* ((full-file-name (if (string= "" file-name) (random-temporary-file-name) file-name)) (full-path (concat (temporary-file-directory) full-file-name ))) (find-file full-path)))
; make this a quick keybinding (global-set-key (kbd "C-`") #'temporary-file)
The default Emacs minibuffer is fine, but the Ivy package really improves it. With Ivy, you get completions, previews, and many other packages integrate with it.
(use-package ivy :ensure t :init (ivy-mode 1) (setq ivy-use-virtual-buffers t) (setq enable-recursive-minibuffers t))
Ivy also powers a replacement for the default =C-s= search that's really powerful and a great way to navigate around code called Swiper. We download that as well.
(use-package swiper :ensure t :bind (("C-s" . swiper)))
Finally, we can use another Ivy powered tool called 'Counsel' to add some extra functionality to Emacs. I like =counsel-rg= because it lets me search a directory with [[https://github.com/BurntSushi/ripgrep][ripgrep]], perhaps the fastest search tool around, with a Swiper-like interface.
(use-package counsel :ensure t :bind (("C-c k" . counsel-rg)))
org-mode is the package that this very config is written in. It's pretty good out of the box, but I feel that it benefits from a bit of configuration. Ideally, I'd like org-mode to:
(use-package org :init (setq org-directory "~/org") :config (org-babel-do-load-languages 'org-babel-load-languages (append org-babel-load-languages '((python . t) (shell . t)))) :hook ((org-mode . flyspell-mode) (org-mode . org-indent-mode) (org-mode . visual-line-mode)) )
Company is a completion UI framework. LSP mode will use Company to do inline completions.
(use-package company :ensure t :config (setq company-dabbrev-downcase 0) (setq company-idle-delay 0) )
eglot is an LSP package for Emacs. We use it for autocomplete and other fancy features. eglot was chosen because it will be baked into Emacs 29.
(use-package eglot :ensure t)
Magit is a helper for managing Git repositories. It is another Emacs 'killer feature' and makes frequent Git commands really fast to execute.
(use-package magit :ensure t :bind (("C-x g" . magit-status)) :hook ((magit-mode . magit-auto-revert-mode)))
Projectile is a project management system that helps keep multiple projects tidy when running in one emacs instance. I have chosen to override ==C-x p= as the Projectile leader sequence; I tried using =C-c p= but I kept hitting =C-x= instead.
I've also added an extra little Projectile helper function to spawn a new instance of vterm in the project. By default, Projectile only spawns one vterm and will drop you into it whenever you run the vterm sequence. I tend to like a few terminals in one project at once so I wrote that quick function.
(use-package projectile :ensure t :init (projectile-mode +1) :bind (:map projectile-mode-map ("C-x p" . projectile-command-map) ("C-x p x V" . (lambda () (interactive) (projectile-run-vterm 't)))) )
Dired is Emacs' built-in file management tool. It is basically the output from ~ls~ but actionable. Basic file operations are really quick in Dired. Another benefit of Dired is that it integrates with [[https://www.emacswiki.org/emacs/TrampMode][TRAMP mode]], so you can list and modify directories on remote hosts over SSH (and all the other protocols that TRAMP supports).
One of the nice, non-default, behaviours of Dired is that you can open two Dired buffers side by side and copy between them with the 'C' key. We enable this here:
(setq dired-dwim-target t)
I also find myself needing to create files in the dired UI which isn't supported by default. I've chosen 't' for 'touch'.
(eval-after-load "dired" '(progn (define-key dired-mode-map (kbd "t") (lambda (file-name) (interactive "sTouch file: ") (shell-command (concat "touch " (dired-current-directory) "/" file-name)) (revert-buffer)))))
Dired, by default, opens a new buffer when a new directory is opened. This can create a lot of dired buffers. There is another command, ~a~, which opens a new directory buffer and deletes the old one. It needs to be enabled.
(put 'dired-find-alternate-file 'disabled nil)
** Dired Sidebar
I'm going through a sidebar phase. Dired sidebar seems like the vogue one to use. Config taken from the [[https://github.com/jojojames/dired-sidebar][Dired Sidebar README file.]]
(use-package vscode-icon :ensure t)
(use-package dired-sidebar :bind (("C-x C-n" . dired-sidebar-toggle-sidebar)) :ensure t :commands (dired-sidebar-toggle-sidebar) :init (add-hook 'dired-sidebar-mode-hook (lambda () (unless (file-remote-p default-directory) (auto-revert-mode)))) :config (push 'toggle-window-split dired-sidebar-toggle-hidden-commands) (push 'rotate-windows dired-sidebar-toggle-hidden-commands)
(setq dired-sidebar-subtree-line-prefix "__")
(setq dired-sidebar-theme 'vscode)
(setq dired-sidebar-use-term-integration t))
Emacs has a built in terminal emulator, but it's not a /true/ VTY emulator and struggles with some of the more involved commands. There's a package called =vterm= which provides a much more robust terminal emulator.
(use-package vterm :ensure t)
I have also written a quick function to make a new vterm with a specific buffer name. This is really useful when you need to start multiple terminal emulators:
(defun vterm-named (name) (interactive "sTerminal name: ") (let ((term-name (concat "vterm-" name))) (vterm term-name)))
(global-set-key (kbd "C-x v") #'vterm-named)
vterm also [[https://github.com/akermu/emacs-libvterm/blob/master/README.md][recommends]] using helpers in ~.zshrc~ to extend it's functionality. I'll record the ones I use here for reference.
vterm_printf() { if [ -n "$TMUX" ] && ([ "${TERM%%-}" = "tmux" ] || [ "${TERM%%-}" = "screen" ] ); then
printf "\ePtmux;\e\e]%s\007\e\\" "$1"
elif [ "${TERM%%-*}" = "screen" ]; then
# GNU screen (screen, screen-256color, screen-256color-bce)
printf "\eP\e]%s\007\e\\" "$1"
else
printf "\e]%s\e\\" "$1"
fi
}
Emacs doesn't yet have a Dockerfile mode, so we need to fetch one:
(use-package dockerfile-mode :ensure t)
We leave most of the Python heavy lifting to LSP mode (documented later on). However, there's a helpful package for managing virtual environments that's very handy. I'm a user of [[https://python-poetry.org/][Poetry]], so I point the ~venv-location~ variable at the directory that Poetry stores its virtual environments.
(use-package virtualenvwrapper :ensure t :config (venv-initialize-interactive-shells) (setq venv-location (concat (getenv "HOME") "/.cache/pypoetry/virtualenvs")))
(defun find-rust-analyzer-directory () "Run rustup to discover rust-analyzer path" (let* ((output (chomp (shell-command-to-string "rustup which --toolchain stable rust-analyzer")))) (if (string-match-p "not found" output) nil (file-name-directory output))))
(defun add-rust-analyzer-to-path () "Add the parent folder of rust-analyzer to the PATH variable and Emacs' exec-path" (let* ((ra-dir (find-rust-analyzer-directory)))
; first update the path
(when (and ra-dir (not (string-match-p ra-dir (getenv "PATH")))) ; we found the ra dir + ra dir not in path
(setenv "PATH"
(concat ra-dir ":" (getenv "PATH"))))
; now update the exec-path variable
(when (and ra-dir (not (member ra-dir exec-path)))
(add-to-list 'exec-path ra-dir))))
(use-package rustic :ensure t :hook ((rustic-mode . company-mode)) :init (add-rust-analyzer-to-path) :config (setq ;; eglot seems to be the best option right now. rustic-lsp-client 'eglot rustic-format-on-save nil ;; Prevent automatic syntax checking, which was causing lags and stutters. eglot-send-changes-idle-time (* 60 60) ) ;; Disable the annoying doc popups in the minibuffer. (add-hook 'eglot-managed-mode-hook (lambda () (eldoc-mode -1))) )
Emacs doesn't yet have an in-built YAML mode so we need to install one:
(use-package yaml-mode :ensure t)
There's quite a nice mode for Markdown support, so let's install it:
(use-package markdown-mode :ensure t)
Flycheck is an error checking framework for Emacs. It's useful to get feedback on errors with code.
(use-package flycheck :ensure t)
=wgrep= lets you edit recursive grep buffers and commit your changes back to those files. Really useful for bulk renaming things.
(use-package wgrep :ensure t)
Ensure we hae a mode for editing Clojure and CIDER for interactive development.
(use-package clojure-mode :ensure t)
(use-package cider :ensure t)
The modern Linux firewall is nftables, and it has its own syntax. We should support this (because emacs doesn't do a good job of managing it on its own.)
(use-package nftables-mode :ensure t)
(add-hook 'conf-toml-mode-hook (lambda () (setq tab-width 4)))