My Literate .emacs.d
Want to use it? Go ahead!
git clone --depth 1 https://github.com/renzmann/.emacs.d ~/.emacs.d
All external dependency sources are explicitly included under the
directory, meaning it’s as simple as “clone-n-go”. Opening this document under
my configuration looks like so:
If you prefer a prettier reading experience, check out this same document weaved into my website. Or, if you’re already reading this on my website, check out the source code on GitHub.
If I had to sum up the theme of my configuration, it would be “vanilla extract”.
In only a few instances do I change overt behavior of Emacs, the most noticeable
departure of course being the color theme with org-modern. Even with those,
though, I want a configuration that fits my hands in such a way that I remain
emacs -Q with very little disruption to my normal muscle
memory and workflow.
Aside from these aesthetic and philosophical reasons, there are practical
concerns this configuration needs to address. I spend my time on Windows for
games, macOS or Linux with remote machines for work, and desktop Linux for
personal projects like building my website. Some of these situations enforce a
very slow internet connection and tight security measures for Tramp, which can
cause modern, “live updating” features like
consult to hang Emacs. In
other cases, I have no access to the outside internet at all (so no ELPA or
MELPA updates). Hence, keeping only a small number of external dependencies
elpa/ maximizes portability and maintainability between systems.
Altogether, I wind up using Emacs 28+ on all three of the major platforms, in both GUI and TTY mode. So this config is designed to work equally well for:
|platform||terminal||GUI||ssh + TTY||Tramp|
Once I figure out how to get colors working in Windows terminal, I’ll update this table. For now though, the 16 bit colors don’t react to my color theme, and so on Windows I rely singularly on GUI mode, rather than WSL or Emacs inside Alacritty.
Notable Features #
You may notice that despite the laudable goal of intended minimalism, this document is is still quite long, as I have found many (ever increasing) quirky behaviors of Emacs that I tweak. Most of my time is spent in Org, SQL, Python, Bash, and Markdown, so the majority of configuration lies around these sections.
I do make changes to things that I feel “should have been included.” Some examples of this are:
- Additional major modes for languages like Markdown, Go, and Rust
- Error message support for
- Reasonable indentation behavior for SQL files
- Updating buffers automatically if their contents change on disk
- Syntax highlighting for Source blocks in Markdown
- Handling ANSI color escape codes in shell output, compilation, and VC buffers
- Ability to run TUI interfaces in comint-mode (shell, eshell) on Linux and macOS
My configuration is a single literate programming document, which is tangled
into the standard
init.el and supporting files. This is so I can keep track of
all the crazy things I try, and explain them inline with the final code I decide
to include. Some platforms like GitHub can render this document in a limited
way, but to see all the final configuration values I use you will likely have to
view this document in Emacs itself.
Why use a literate document for my configuration? Basically, as I added more comments and reminders about what some line of code was doing, where I got it from, and why it might be commented out, the prose grew longer than the actual code, and so a change of medium felt prudent. In my case, that’s the venerable Org mode, which comes with Emacs and serves as a way to seamlessly weave commentary and code together.
Here’s where I put the typical quote about standing on one form of shoulders or another. I steal quite a lot from other, more qualified Emacs community contributors, such as:
- Protesilaos Stavrou
- Ramón Panadestein
- Mickey Petersen
- Daniel Mendler
- Omar Antolín Camarena
- Luca’s Literate Config
To comply with the Emacs conventions for libraries, the tangled init.el must have the following header and footer:
;;; init.el --- Robb's Emacs configuration -*- lexical-binding: t -*- ;; Copyright (C) 2022 Robert Enzmann ;; Author: Robb Enzmann <firstname.lastname@example.org> ;; Keywords: internal ;; URL: https://robbmann.io/ ;;; Commentary: ;; A mostly minimal, reproducible Emacs configuration. This file is ;; automatically tangled from README.org, with header/footer comments on each ;; code block that allow for de-tangling the source back to README.org when ;; working on this file directly. ;;; Code:
I prefer having custom modify its own file. This next snippet ensures any
custom edits go to
(setq custom-file (expand-file-name "custom.el" user-emacs-directory)) (when (file-exists-p custom-file) (load custom-file 'noerror))
The initial cornerstone of every Emacs configuration is a decision on package
management and configuration. I opt for
package.el, since both
are built-in to Emacs 29+, which helps maximize stability and portability.
To avoid loading packages twice, the manual recommends disabling
(require 'package) (setq package-enable-at-startup nil)
MELPA (Milkypostman’s Emacs Lisp Package Archive) is the largest repository for
elisp sources that aren’t a part of the official GNU ELPA. To install packages
from it, we need it on the
(add-to-list 'package-archives '("melpa" . "https://melpa.org/packages/") t)
I do not use the
:ensure t keyword in
use-package declarations to install
packages. Instead, I rely on
M-x package-install and
M-x package-delete, and
use-package to handle the configuration and loading of packages. As
mentioned in the introduction, each package’s source is explicitly included into
version control of my configuration, so I don’t worry too much about pinning
package versions in this file. When I want to update a package, I use
M-x package-update, the
package.el user interface, or delete the package’s source
folder and use
renz/package-sync (defined below). Should something go wrong, I
roll back to a previous commit. So far, this method has been reliable for
init.el (this README),
elpa/ directory all in sync with one another.
(defun renz/package-sync () "Remove unused sources and install any missing ones." (interactive) (package-autoremove) (package-install-selected-packages))
There are also a few hand-made packages I keep around in a special
(add-to-list 'load-path (expand-file-name "site-lisp/" user-emacs-directory))
OS-specific Configuration #
Microsoft Windows #
Compiling Emacs #
Compiling Emacs on Windows can be a bit of a pain, but it’s mostly the same as on *nix. First, I use MSYS2-MinGW to compile everything. From the MinGW terminal (NOT the plain MSYS2 one), I install all of these dependencies.
pacman -Su \ autoconf \ autogen \ automake \ automake-wrapper \ diffutils \ git \ guile \ libgc \ libguile \ libltdl \ libunistring \ make \ texinfo \ mingw-w64-x86_64-binutils \ mingw-w64-x86_64-bzip2 \ mingw-w64-x86_64-cairo \ mingw-w64-x86_64-crt-git \ mingw-w64-x86_64-dbus \ mingw-w64-x86_64-expat \ mingw-w64-x86_64-fontconfig \ mingw-w64-x86_64-freetype \ mingw-w64-x86_64-gcc \ mingw-w64-x86_64-gcc-libs \ mingw-w64-x86_64-gdk-pixbuf2 \ mingw-w64-x86_64-gettext \ mingw-w64-x86_64-giflib \ mingw-w64-x86_64-glib2 \ mingw-w64-x86_64-gmp \ mingw-w64-x86_64-gnutls \ mingw-w64-x86_64-harfbuzz \ mingw-w64-x86_64-headers-git \ mingw-w64-x86_64-imagemagick \ mingw-w64-x86_64-isl \ mingw-w64-x86_64-libffi \ mingw-w64-x86_64-libgccjit \ mingw-w64-x86_64-libiconv \ mingw-w64-x86_64-libjpeg-turbo \ mingw-w64-x86_64-libpng \ mingw-w64-x86_64-librsvg \ mingw-w64-x86_64-libtiff \ mingw-w64-x86_64-libwebp \ mingw-w64-x86_64-libwinpthread-git \ mingw-w64-x86_64-libxml2 \ mingw-w64-x86_64-mpc \ mingw-w64-x86_64-mpfr \ mingw-w64-x86_64-pango \ mingw-w64-x86_64-pixman \ mingw-w64-x86_64-winpthreads \ mingw-w64-x86_64-xpm-nox \ mingw-w64-x86_64-lcms2 \ mingw-w64-x86_64-xz \ mingw-w64-x86_64-zlib \ mingw-w64-x86_64-tree-sitter \ tar \ wget
Then, the configure scripts:
./autogen.sh ./configure \ --prefix=/c/emacs-29 \ --with-native-compilation \ --with-tree-sitter \ --with-gnutls \ --with-jpeg \ --with-png \ --with-rsvg \ --with-tiff \ --with-wide-int \ --with-xft \ --with-xml2 \ --with-xpm \ --without-dbus \ --without-pop
And finally the command to compile:
make --jobs=$(NPROC) sudo make install
Windows, funnily enough, has some trouble registering the Windows key as a
usable modifier for Emacs. In fact,
s-l will never be an option, since it’s
handled at the hardware level. I also add a few nice-to-haves, like setting the
default shell to
pwsh and explicitly pathing out
aspell, which I always install
(defun renz/windowsp () "Are we on Microsoft Windows?" (memq system-type '(windows-nt cygwin ms-dos))) (when (renz/windowsp) ;; Set a font that supports emoji (set-fontset-font t 'unicode (font-spec :family "Segoe UI Emoji") nil 'prepend) (set-face-attribute 'default nil :font "Hack NF-12") ;; Alternate ispell when we've got msys on Windows (setq ispell-program-name "aspell.exe"))
For a time I considered enabling the use of the winkey like this:
(setq w32-pass-lwindow-to-system nil) (setq w32-lwindow-modifier 'super) ; Left Windows key (setq w32-pass-rwindow-to-system nil) (setq w32-rwindow-modifier 'super) ; Right Windows key
Followed by enabling specific chords, such as “winkey+a”:
Since I’ve taken a more TTY-friendly approach for my config in general, where
super can be a bit tough to integrate with both the windowing application and
the terminal emulator, I’ve mostly given up on the GUI key in favor of other
chords, especially the
A note on TreeSitter #
When compiling tree-sitter dll’s with
treesit-install-language-grammar, I also
need to launch Emacs via
runemacs.exe from the MinGW terminal to ensure the C
and C++ compilers are both visible and usable.
Installing Emacs #
On macOS, I’ve had the best luck with jimeh’s nightly builds. These Emacs.app bundles have no external dependencies, signed with a developer certificate, and notarized by Apple, so it just works. Even without administrator permissions, you can drag the bundle to the “Applications” folder under your user home instead, and Emacs still works beautifully.
Launching Emacs from the typical application launcher or command-space usually
won’t capture any modifications to
$PATH, typically handled in a file like
~/.bashrc. So, the main configuration included here is from
(when (eq system-type 'darwin) ;; Uncomment this if we can't install Hack Nerd font ;; (set-face-attribute 'default nil :font "Menlo-14") (set-face-attribute 'default nil :font "Hack Nerd Font Mono-13") (setq exec-path-from-shell-arguments '("-l")) (exec-path-from-shell-initialize))
Very little to do here. Emacs on Linux seems to “just work”. When I have the Hack font installed, I sometimes turn it on by manually evaluating this block, though.
(set-face-attribute 'default nil :font "Hack Nerd Font Mono-11")
Prot’s themes have been reliably legible in nearly every situation. Now with
his new ef-themes, they’re pretty, too! The
ef-themes-headings variable creates
larger, bolder headings when in Org-mode, and
ef-themes-to-toggle allows me to
quickly switch between preset light and dark themes depending on the ambient
light of the room I’m in.
(use-package ef-themes :if (display-graphic-p) :demand t :bind ("C-c m" . ef-themes-toggle) :init (setq ef-themes-headings '((0 . (1.9)) (1 . (1.3)) (2 . (1.2)) (3 . (1.1)) (4 . (1.0)) (5 . (1.0)) ; absence of weight means `bold' (6 . (1.0)) (7 . (1.0)) (t . (1.0)))) (setq ef-themes-to-toggle '(ef-cherie ef-summer)) :config (load-theme 'ef-cherie :no-confirm))
I LOVE these themes from
I’ve mostly settled on
ef-cherie, but sometimes switch to the others above.
Gave up on Nord #
It’s worth mentioning that I’ve tried nord-theme a couple times and found that the legibility or contrast wasn’t quite good enough in some modes. Though I still employ Nord for my terminal config in Alacritty and Kitty, where it looks excellent. I also still actively use the nordfox theme in Neovim, which sports a beautiful TreeSitter integration.
Messed up colors in TTY mode #
In TTY mode, I use kitty. I have had trouble with dark blue or red themes in
Alacritty, and on Windows terminal. There is probably some hacking I could do
$TERM variable to try and sort that out, but since it just kinda works in
Kitty for me, I haven’t spent too much time looking into it.
Emacs’ Built-in Settings #
My settings for base Emacs. Assuming I ran with no plugins (ala
emacs -Q), I
would still set most of these by hand at one point or another.
Start a server for
Sometimes (especially on Windows), Emacs gets confused about what encoding to use. These setting try to prevent that confusion.
(prefer-coding-system 'utf-8) (set-default-coding-systems 'utf-8) (set-terminal-coding-system 'utf-8) (set-keyboard-coding-system 'utf-8) (setq default-buffer-file-coding-system 'utf-8) (setq x-select-request-type '(UTF8_STRING COMPOUND_TEXT TEXT STRING))
Mode line #
It’s easy for the mode line to get cluttered once things like Flymake and eglot kick in. When I was starting out, I used to have these two settings:
(setq display-battery-mode t display-time-day-and-date t) (display-time)
After a while I noticed that I’m almost never running Emacs in a full screen where I can’t see the battery or date in the corner of my window manager, so they were just wasting mode line space. Nowadays I simply opt for column mode and a dimmed mode line in non-selected windows.
(setq column-number-mode t mode-line-in-non-selected-windows t)
I find it very distracting when
eldoc suddenly pops up and consumes a large part
of the screen for docstrings in python.
(setq eldoc-echo-area-use-multiline-p nil)
Remember minibuffer history #
Found this on a System Crafters video.
(setq history-length 25) (savehist-mode 1)
Colored output in
Copy-pasted from a stack overflow question.
(add-hook 'eshell-preoutput-filter-functions 'ansi-color-apply)
Recent files menu #
This enables “File -> Open Recent” from the menu bar, and
(recentf-mode t) (defun renz/find-recent-file () "Find a file that was recently visted using `completing-read'." (interactive) (find-file (completing-read "Find recent file: " recentf-list nil t)))
Regardless of whether we’re doing visual fill or hard fill, I like the default at around 80 characters, and I’ll manually change it per buffer if I want something different
(setq-default fill-column 80)
Scroll bar #
I toggle this one on/off sometimes depending on how I feel and which OS I’m currently on.
Window margins and fringe #
This hunk adds some space around all sides of each window so that we get a clear
space between the edge of the screen and the fringe. This helps
src blocks look
clean and well delineated for org-modern.
(modify-all-frames-parameters '((right-divider-width . 40) (internal-border-width . 40))) (dolist (face '(window-divider window-divider-first-pixel window-divider-last-pixel)) (face-spec-reset-face face) (set-face-foreground face (face-attribute 'default :background))) (set-face-background 'fringe (face-attribute 'default :background))
Automatically visit symlink sources #
When navigating to a file that is a symlink, this automatically redirects us to the source file it’s pointing to.
(setq find-file-visit-truename t) (setq vc-follow-symlinks t)
Indent with spaces by default #
For the most part I edit Python, SQL, Markdown, Org, and shell scripts. All of these favor spaces over tabs, so I prefer this as the default.
(setq-default indent-tabs-mode nil)
Generally, though, indentation behavior is set by major-mode functions, which
may or may not use Emacs’ built-in indentation functions. For instance, when
trying to find the functions behind indentation in shell mode, I cam across
smie.el, whose introductory comments include this gem:
OTOH we had to kill many chickens, read many coffee grounds, and practice untold numbers of black magic spells, to come up with the indentation code. Since then, some of that code has been beaten into submission, but the `smie-indent-keyword’ function is still pretty obscure.
Even the GNU manual speaks of it in the same way:
Writing a good indentation function can be difficult and to a large extent it is still a black art. Many major mode authors will start by writing a simple indentation function that works for simple cases, for example by comparing with the indentation of the previous text line. For most programming languages that are not really line-based, this tends to scale very poorly: improving such a function to let it handle more diverse situations tends to become more and more difficult, resulting in the end with a large, complex, unmaintainable indentation function which nobody dares to touch.
(add-hook 'sh-mode-hook (lambda () (setq indent-tabs-mode t)))
Render ASCII color escape codes #
For files containing color escape codes, this provides a way to render the colors in-buffer.
(defun renz/display-ansi-colors () "Render colors in a buffer that contains ASCII color escape codes." (interactive) (require 'ansi-color) (ansi-color-apply-on-region (point-min) (point-max)))
Enable horizontal scrolling with mouse #
From a helpful stackoverflow answer.
(setq mouse-wheel-tilt-scroll t)
Window management #
From a Mickey Petersen article, this causes
switch-to-buffer to open the
selected buffer in the current window rather than switching windows, assuming
both are open in the current frame. This is more frequently the behavior I
intend when I’m trying to get a window to display a specific buffer.
(unless (version< emacs-version "27.1") (setq switch-to-buffer-obey-display-actions t))
Automatically update buffers when contents change on disk #
global-auto-revert-mode, we have to remember to issue a
C-x x g by default) in case a file
changed. Over Tramp, we still have to manually revert files when they’ve
changed on disk.
Highlight the line point is on #
Add a faint background highlight to the line we’re editing.
(add-hook 'prog-mode-hook #'hl-line-mode) (add-hook 'text-mode-hook #'hl-line-mode) (add-hook 'org-mode-hook #'hl-line-mode)
Stop stupid bell #
This snippet has a special place in my heart, because it was the first two lines of elisp I wrote when first learning Emacs.
;; Stop stupid bell (setq ring-bell-function 'ignore)
The bell is really, really annoying.
Automatically create matching parens in programming modes #
(add-hook 'prog-mode-hook (electric-pair-mode t)) (add-hook 'prog-mode-hook (show-paren-mode t))
Delete whitespace on save #
I would also like to have a good-looking display for trailing whitespace and
leading tabs like in my Neovim setup, but it has proven challenging to just
narrow down to those two faces. In the interim, I toggle
M-x whitespace-mode to
check for mixed tabs, spaces, and line endings.
(add-hook 'before-save-hook 'delete-trailing-whitespace)
Don’t wrap lines #
(setq-default truncate-lines t) (add-hook 'eshell-mode-hook (lambda () (setq-local truncate-lines nil)))
Relative line numbers #
For programming and prose/writing modes.
Unfortunately, line numbers are displayed in the text area of the buffer, but
org-modern uses the fringe to display source blocks. There’s no way to display
them to the left of the fringe, so I’m careful about only turning on line
numbers in modes that I think I’ll benefit from it. It’s been working pretty
well in org-mode without the line numbers so far, since for each of the code
blocks I can always use
C-c ' to edit in
prog-mode, where I do get line numbers.
(add-hook 'prog-mode-hook (lambda () (setq display-line-numbers 'relative))) (add-hook 'yaml-mode-hook (lambda () (setq display-line-numbers 'relative))) (unless (display-graphic-p) (add-hook 'text-mode-hook (lambda () (setq display-line-numbers 'relative))))
Delete region when we yank on top of it #
I just think that’s a funny sentence. Normally when yanking text with an active region, the region will remain and the yanked text is just inserted at point. I prefer the modern word processor behavior of replacing the selected text with the yanked content.
Enable mouse in terminal/TTY #
As new text appears, the default behavior is for it to spill off the bottom where we can’t see it. Instead, I prefer the window to scroll along with text as it appears
(setq compilation-scroll-output t)
Enable colors in the
*compilation* buffer. Provided by a helpful stackoverflow
(defun renz/colorize-compilation-buffer () "Enable colors in the *compilation* buffer." (require 'ansi-color) (let ((inhibit-read-only t)) (ansi-color-apply-on-region (point-min) (point-max)))) (add-hook 'compilation-filter-hook 'renz/colorize-compilation-buffer)
Tool bar #
I usually leave the tool bar disabled
The menu bar, on the other hand
(menu-bar-mode), is very handy, and I only
disable it on Windows, where it looks hideous if I’m running in dark mode.
(when (renz/windowsp) (menu-bar-mode -1))
For newcomers to Emacs, I would strongly discourage disabling the menu bar, as it is the most straightforward way to discover Emacs’ most useful features.
Ignore risky .dir-locals.el #
From an Emacs stackexchange answer.
(advice-add 'risky-local-variable-p :override #'ignore)
(when (executable-find "rg") (setq grep-program "rg")) (when (executable-find "fd") (setq find-program "fd"))
Confirm when exiting Emacs #
It’s very annoying when I’m working and suddenly I meant to do
C-c C-x, but
C-x C-c. This helps prevent that.
(setq confirm-kill-emacs 'yes-or-no-p)
Smooth scrolling #
Emacs 29 introduced smooth, pixel-level scrolling, which removes much of the “jumpiness” you see when scrolling past images.
(if (version< emacs-version "29.0") (pixel-scroll-mode) (pixel-scroll-precision-mode 1) (setq pixel-scroll-precision-large-scroll-height 35.0))
(when (executable-find "aspell") (setq ispell-program-name "aspell"))
Backup and auto-save files #
Keep all backup files in a temporary folder. At the moment I have some “file not found” errors popping up during auto-save on Windows. Once I debug that, I’ll uncomment the second part.
(setq backup-directory-alist '(("." . "~/.emacs.d/backups/")) ;; auto-save-file-name-transforms ;; '(("." ,temporary-file-directory t)) )
(put 'narrow-to-region 'disabled nil)
Enable up/downcase-region #
(put 'upcase-region 'disabled nil) (put 'downcase-region 'disabled nil)
Eventually, some of the custom functions that I bound to convenient keys had a logical abstraction, which I extract and put up here for them to use.
(defun renz/--jump-section (dirname prompt extension) "Jump to a section of my configuration. Asks for a file under `DIRNAME' using `PROMPT' in the user Emacs config site with matching `EXTENSION' regexp." (find-file (concat dirname (completing-read prompt (directory-files dirname nil extension)))))
Expanded/better defaults #
These convenient chords allow for fast text replacement by holding
h in succession.
(global-set-key (kbd "C-M-<backspace>") 'backward-kill-sexp) (global-set-key (kbd "C-M-h") 'backward-kill-sexp)
The next line UNBINDS the suspend-frame keybinding. Accidentally minimizing on
the GUI was frustrating as hell, so now I use
C-x C-z if I really want to
suspend the frame.
(global-set-key (kbd "C-z") #'zap-up-to-char)
Hippie-expand is purported to be a better version of
dabbrev, but I rather like
the default behavior of
dabbrev. I typically have
hippie-expand on a dedicated
key, and sometimes re-bind the default
M-/ as well, depending on my current
(global-set-key [remap dabbrev-expand] 'hippie-expand)
ibuffer is a strictly superior, built-in version of its counterpart.
(global-set-key [remap list-buffers] 'ibuffer)
The most common situation where I’m running
flymake would be for spelling in
prose, or diagnostics from a language server. In either case, I like having
next/previous on easy to reach chords.
(use-package flymake :bind (:map flymake-mode-map ("M-n" . flymake-goto-next-error) ("M-p" . flymake-goto-prev-error)))
isearch to jump to things, it’s sometimes convenient to re-position
point on the opposite side of where the search would normally put it. E.g. when
C-r, but we want point to be at the end of the word when we’re done.
Provided by a stack overflow answer.
(define-key isearch-mode-map (kbd "<C-return>") (defun isearch-done-opposite (&optional nopush edit) "End current search in the opposite side of the match." (interactive) (funcall #'isearch-done nopush edit) (when isearch-other-end (goto-char isearch-other-end))))
Overriding defaults #
Some default bindings aren’t useful for me, so I bind them to actions I take more frequently.
(global-set-key (kbd "C-x C-p") 'previous-buffer) ; Overrides `mark-page' (global-set-key (kbd "C-x C-n") 'next-buffer) ; Overrides `set-goal-column'
C-c bindings #
Emacs has some standards about where user-configured keys should go;
C-c <letter> is always free for users. It may seem like overkill how I set a header
for each possible
C-c combination, but it’s incredibly handy when I want to jump
directly to one of these headings while in another buffer. See
renz/jump-init, which allows me to narrow in on a particular key I’d like
to bind by leveraging
completing-read. If a
C-c <letter> combination is missing
as a header, then I’m probably using it in a
:bind statement with
C-c b build / compile #
(global-set-key (kbd "C-c b") #'compile) (global-set-key (kbd "C-c B") #'recompile)
C-c d jump to a tag #
(defun renz/find-tag () "Use `completing-read' to navigate to a tag." (interactive) (xref-find-definitions (completing-read "Find tag: " tags-completion-table))) (global-set-key (kbd "C-c d") #'renz/find-tag)
C-c f find file at point (ffap) #
(global-set-key (kbd "C-c f") #'ffap)
C-c i jump to a header in my configuration #
(setq renz/site-lisp-dir (expand-file-name "site-lisp/" user-emacs-directory)) (defun renz/jump-configuration () "Prompt for a .el file in my site-lisp folder, then go there." (interactive) (renz/--jump-section renz/site-lisp-dir "Elisp config files: " ".*\.el$")) (defun renz/jump-init () "Jump directly to my literate configuration document." (interactive) (find-file (expand-file-name "README.org" user-emacs-directory))) (global-set-key (kbd "C-c i i") #'renz/jump-init) (global-set-key (kbd "C-c i l") #'renz/jump-configuration)
C-c j Toggle window split #
Toggling windows from vertical to horizontal splits and vice-versa.
(defun toggle-window-split () "Switch between horizontal and vertical split window layout." (interactive) (if (= (count-windows) 2) (let* ((this-win-buffer (window-buffer)) (next-win-buffer (window-buffer (next-window))) (this-win-edges (window-edges (selected-window))) (next-win-edges (window-edges (next-window))) (this-win-2nd (not (and (<= (car this-win-edges) (car next-win-edges)) (<= (cadr this-win-edges) (cadr next-win-edges))))) (splitter (if (= (car this-win-edges) (car (window-edges (next-window)))) 'split-window-horizontally 'split-window-vertically))) (delete-other-windows) (let ((first-win (selected-window))) (funcall splitter) (if this-win-2nd (other-window 1)) (set-window-buffer (selected-window) this-win-buffer) (set-window-buffer (next-window) next-win-buffer) (select-window first-win) (if this-win-2nd (other-window 1)))))) (global-set-key (kbd "C-c j") #'toggle-window-split)
C-c k kill all but one space #
(global-set-key (kbd "C-c k") #'just-one-space)
C-c q replace regexp #
(global-set-key (kbd "C-c q") #'replace-regexp)
C-c r find recent files #
(global-set-key (kbd "C-c r") #'renz/find-recent-file)
C-c s shell #
(global-set-key (kbd "C-c s s") #'shell) (global-set-key (kbd "C-c s e") #'eshell) (global-set-key (kbd "C-c s t") #'term)
C-c v open thing at point in browser #
(global-set-key (kbd "C-c v") #'browse-url-at-point)
C-c w whitespace mode #
(global-set-key (kbd "C-c w") #'whitespace-mode)
C-c Other bindings #
(global-set-key (kbd "C-c ;") #'comment-line) ; TTY-friendly (global-set-key (kbd "C-c <DEL>") #'backward-kill-sexp) ;; TTY-frindly (global-set-key (kbd "C-c <SPC>") #'mark-sexp) ;; TTY-friendly
C-c <letter> bindings, these are reserved for users. In practice, even
though there are few of these keys, I tend to forget which is which. So I wind
up using things bound to my
C-c keymaps instead. The
C-c kyes from a more
natural, nested language in my head, so it feels more like I’m “speaking Emacs”
Text Completion #
Emacs offers incredible depth and freedom when configuring methods that automatically complete text. There are actually two things that “autocompletion” can refer to in Emacs:
Emacs on its own does not have a nice pop-up-menu like Vim for completing text
at point. For both the minibuffer and
completion-at-point it uses a special
*Completions*, from which we can see (and optionally select) a
completion from potential candidates. Before we get to tweak those settings,
though, we first need to oil the engine with an enhanced completion style
Completion style: Orderless #
For both the minibuffer and
completion-at-point, I use the same completion
style. Completion style is the method of assigning completion candidates to a
given input string.
flex is the built-in “fuzzy” completion style, familiar to
us from symbol completion in IDEs and VSCode’s command palette.
much like your default TAB-complete at a Bash shell.
(setq completion-styles '(flex basic partial-completion emacs22))
I’ve found the orderless completion style especially well-suited to Emacs. It
allows me to type short strings that can match the symbol I’m looking for in any
order. In Emacs, I may not know if I’m looking for
list-packages. In either case, I can just type “
pack lis” in the minibuffer to
find the correct one.
(use-package orderless :config (add-to-list 'completion-styles 'orderless) (setq orderless-component-separator "[ &]") :custom (completion-category-overrides '((file (styles basic partial-completion)))))
Nicer Display and Behavior of
With the completion style set, we now have to configure the interface for displaying candidates as we type. First, I want candidates displayed as a single, vertical list.
(setq completions-format 'one-column)
Also, when using the built-in completion-at-point, the
*Completions* buffer can
sometimes take up the whole screen when there are a lot of candidates.
(unless (version< emacs-version "29.0") (setq completions-max-height 15))
Some time ago, Prot wrote a package called MCT (Minibuffer and Completions in
Tandem) that enhanced the default minibuffer and
*Completions* buffer behavior
to act more like what we expect of a modern editor’s auto-complete. He
discontinued development of that project once it became clear that Emacs 29 was
going to include similar behavior as a configurable option. These are the
options in question.
(unless (version< emacs-version "29.0") (setq completion-auto-help 'visible completion-auto-select 'second-tab completion-show-help nil completions-sort nil completions-header-format nil))
Another nice addition to Emacs 29 is the option to sort completion candidates with any supplied function. Below is one example provided by Prot, which prioritzes history, followed by lexicographical order, then length.
(defun renz/sort-by-alpha-length (elems) "Sort ELEMS first alphabetically, then by length." (sort elems (lambda (c1 c2) (or (string-version-lessp c1 c2) (< (length c1) (length c2)))))) (defun renz/sort-by-history (elems) "Sort ELEMS by minibuffer history. Use `mct-sort-sort-by-alpha-length' if no history is available." (if-let ((hist (and (not (eq minibuffer-history-variable t)) (symbol-value minibuffer-history-variable)))) (minibuffer--sort-by-position hist elems) (renz/sort-by-alpha-length elems))) (defun renz/completion-category () "Return completion category." (when-let ((window (active-minibuffer-window))) (with-current-buffer (window-buffer window) (completion-metadata-get (completion-metadata (buffer-substring-no-properties (minibuffer-prompt-end) (max (minibuffer-prompt-end) (point))) minibuffer-completion-table minibuffer-completion-predicate) 'category)))) (defun renz/sort-multi-category (elems) "Sort ELEMS per completion category." (pcase (renz/completion-category) ('nil elems) ; no sorting ('kill-ring elems) ('project-file (renz/sort-by-alpha-length elems)) (_ (renz/sort-by-history elems)))) (unless (version< emacs-version "29.0") (setq completions-sort #'renz/sort-multi-category))
Ideally, I would have a function that prioritizes based on relevance, which is not always a trivial algorithm.
What all of the above form isn’t quite the live-updating version that Oantolnin,
MCT, or vertico offer, but it’s pretty close. The
*Completions* buffer updates
<SPC>, which is the natural filtering mechanism for
Completion at point #
By default, Emacs uses
M-TAB, or the equivalent
I’d much prefer to use the easier and more intuitive
(setq tab-always-indent 'complete)
Again, we set
C-p when completion-in-region is active for selecting
(unless (version< emacs-version "29.0") (define-key completion-in-region-mode-map (kbd "C-p") #'minibuffer-previous-completion) (define-key completion-in-region-mode-map (kbd "C-n") #'minibuffer-next-completion))
Tramp (Transparent Remote Access Multiple Protocol) allows us to access files on
a remote machine, and edit them locally. This is great for simple changes or
quickly testing out some Python on a VM somewhere. It isn’t as snappy as using
the TTY version or an X-forwarded Emacs from the server directly, so if I can
set up Emacs remotely, I usually do. When I don’t want to or don’t have the
time, Tramp is a godsend. There are, however, many foibles to guard against,
particularly with how interacts with version control and
Tramp manual (distributed with Emacs) recommends adjusting these for some speed
(use-package tramp :defer t :config (setq vc-handled-backends '(Git) file-name-inhibit-locks t tramp-inline-compress-start-size 1000 tramp-copy-size-limit 10000 tramp-verbose 1) (add-to-list 'tramp-remote-path 'tramp-own-remote-path))
eglot is actively working on an issue related to timers causing a “Forbidden reentrant call of Tramp” message and freezing. In the meantime, this setting was recommended.
(setq tramp-use-ssh-controlmaster-options nil)
For some time I was having a lot of trouble with prohibitive slowness over
Tramp, and after careful scrutiny of the logs on (I believe)
tramp-verbose 6, I
found out that enabling remote dir-locals was causing a huge bottleneck. On
every operation it would trace up the filesystem tree back to the root
directory, scanning for a
.dir-locals file. Since some of the drives were
network-mounted, this caused thousands of network calls per file operation,
obviously slowing things down a lot. Because of this, I’ve opted to simply
.dir-locals over Tramp entirely, since I don’t really use it much, if at
;; (setq enable-remote-dir-locals t)
Disabling VC does seem to speed things up a little, but it’s not an acceptable thing to put in, since I so frequently use VC over tramp. Fully disabling VC would include this snippet:
(remove-hook 'find-file-hook 'vc-find-file-hook) (setq vc-ignore-dir-regexp (format "\\(%s\\)\\|\\(%s\\)" vc-ignore-dir-regexp tramp-file-name-regexp))
Additionally, these came up as other potential options from the doom-emacs issues, which I do not currently include.
(setq tramp-default-method "scp") (setq projectile--mode-line "Projectile")
I often need to set these in ~/.ssh/config for TRAMP to speed up
Host * ControlMaster auto ControlPath ~/.ssh/master-%h:%p ControlPersist 10m ForwardAgent yes ServerAliveInterval 60
About TreeSitter and its Load Paths #
Emacs 29 added native TreeSitter support. TreeSitter is a new way of
incrementally parsing source code that offers superior navigation and syntax
highlighting. To fully realize this benefit, however, it requires that we
tree-sitter grammars independently from Emacs. Right now, I’m using
casouri’s modules, which I build and install under
they don’t already exist under
~/.local/lib. In case of the
latter, I just add extra paths to
(when (boundp 'treesit-extra-load-path) (add-to-list 'treesit-extra-load-path "/usr/local/lib/") (add-to-list 'treesit-extra-load-path "~/.local/lib/"))
For the full instructions, the commit history of adding the
to Emacs included a full guide, which can be read in Info under “Parsing Program
C-h i d m elisp RET g Parsing Program Source RET
Enabling TreeSitter is done on a per-language basis to override the default major mode with the corresponding TreeSitter version.
Automatically Using TreeSitter Modes #
We will have to wait until Emacs 30+ for automatic fallback. Until then, the
recommended workaround is to derive a mode that picks between them. Instead,
I’m hoping I can abuse the new
(use-package treesit-auto :demand t)
Ooo, aaah, shiny colors #
I like to program “in Skittles”:
(setq-default treesit-font-lock-level 4)
Language-specific major modes #
Shell (Bash, sh, …) #
(defun renz/sh-indentation () (setq indent-tabs-mode t) (setq tab-width 8)) (add-hook 'sh-mode-hook #'renz/sh-indentation) ;; When the interpreter is explicitly set to "bash", use the TreeSitter mode if ;; we can (add-to-list 'interpreter-mode-alist '("r?bash2?" . bash-ts-or-fallback-mode))
(setq css-indent-offset 2) (add-to-list 'auto-mode-alist '("\\.css\\'" . css-ts-or-fallback-mode))
For validation, grab css-validator.jar and execute it with java:
java -jar ~/.local/jars/css-validator.jar file:///home/me/my/site/index.html
(setq renz/org-home "~/org/")
org-babel-tangle-jump-to-org, which jumps back to an Org
source file from within the tangled code.
defined below, does the opposite - given the Org source file and point inside a
src block, it jumps to the location of the tangled code. Provided by a helpful
(defun renz/org-babel-tangle-jump-to-src () "The opposite of `org-babel-tangle-jump-to-org'. Jumps at tangled code from org src block." (interactive) (if (org-in-src-block-p) (let* ((header (car (org-babel-tangle-single-block 1 'only-this-block))) (tangle (car header)) (lang (caadr header)) (buffer (nth 2 (cadr header))) (org-id (nth 3 (cadr header))) (source-name (nth 4 (cadr header))) (search-comment (org-fill-template org-babel-tangle-comment-format-beg `(("link" . ,org-id) ("source-name" . ,source-name)))) (file (expand-file-name (org-babel-effective-tangled-filename buffer lang tangle)))) (if (not (file-exists-p file)) (message "File does not exist. 'org-babel-tangle' first to create file.") (find-file file) (beginning-of-buffer) (search-forward search-comment))) (message "Cannot jump to tangled file because point is not at org src block.")))
Now we configure
org-mode itself. For a while I was trying
(setq org-startup-indented t) t get indentation under each header, but this was
interfering with the beautification features from
org-modern. Preferring the
latter over the former, I’ve removed the
(use-package org :hook ((org-mode . (lambda () (progn (add-hook 'after-save-hook #'org-babel-tangle :append :local) (add-hook 'org-babel-after-execute-hook #'renz/display-ansi-colors))))) :init (defun renz/jump-org () "Prompt for an org file in my emacs directory, then go there." (interactive) (renz/--jump-section renz/org-home "Org files: " ".*\.org$")) :bind (("C-c o a" . org-agenda) ("C-c o b d" . org-babel-detangle) ("C-c o b o" . org-babel-tangle-jump-to-org) ("C-c o b s" . renz/org-babel-tangle-jump-to-src) ("C-c o k" . org-babel-remove-result) ("C-c o o" . renz/jump-org) ("C-c o w" . renz/org-kill-src-block) ("C-c o y" . ox-clip-image-to-clipboard)) :custom (org-image-actual-width nil "Enable resizing of images") (org-agenda-files (list (expand-file-name "work.org" renz/org-home)) "Sources for Org agenda view") (org-html-htmlize-output-type nil "See C-h f org-html-htmlize-output-type") (org-confirm-babel-evaluate nil "Don't ask for confirmation when executing src blocks") (org-edit-src-content-indentation 2 "Indent all src blocks by this much") (org-goto-interface 'outline-path-completion "Use completing-read for org-goto (C-c C-j, nicer than imenu)") (org-outline-path-complete-in-steps nil "Flatten the outline path, instead of completing hierarchically") :config (add-to-list 'org-modules 'org-tempo) (org-babel-do-load-languages 'org-babel-load-languages '((emacs-lisp . t) (python . t) (sql . t) (shell . t) ;; (fortran . t) ;; (julia . t) ;; (jupyter . t) ;; (scheme . t) ;; (haskell . t) (lisp . t) ;; (clojure . t) ;; (C . t) ;; (org . t) ;; (gnuplot . t) ;; (awk . t) ;; (latex . t) )))
A lovely look for
org-mode by minad.
(use-package org-modern :after org :config (setq org-auto-align-tags nil org-tags-column 0 org-catch-invisible-edits 'show-and-error org-special-ctrl-a/e t org-insert-heading-respect-content t ;; Org styling, hide markup etc. org-hide-emphasis-markers t org-pretty-entities t org-ellipsis "…" ;; Agenda styling org-agenda-tags-column 0 org-agenda-block-separator ?─ org-agenda-time-grid '((daily today require-timed) (800 1000 1200 1400 1600 1800 2000) " ┄┄┄┄┄ " "┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄") org-agenda-current-time-string "<─ now ────────────────────────────────────────────────") (if (display-graphic-p) (setq org-modern-table t) (setq org-modern-table nil)) (global-org-modern-mode))
Copying images out of org-babel #
Offers two functions:
(use-package ox-clip :after org :config (setq org-hugo-front-matter-format "yaml"))
Exporting to Hugo #
I also use
org-mode for writing my blog. With a little help from an article we
have exporting to Hugo-specific markdown. Without the export, Hugo can read Org
files okay-ish, but you wind up missing some nice QoL features, like header
(use-package ox-hugo :after org)
Converting JSON to Org Tables #
I use a small external dependency for this:
(use-package json-to-org-table :load-path "site-lisp/json-to-org-table/" :after org)
hive2 mode #
(use-package hive2 :load-path "site-lisp/" :after (sql) :mode ("\\.hql" . sql-mode))
I’ve modified sqlformat for use with sql-formatter, but I need to find a way to pass in a JSON of configuration values to the command line interface when we call it.
(use-package sqlformat :after (sql))
When I get to it, I think what I’ll do instead is rewrite this to simply pipe the current buffer into
sql-formatter, and use a bit of elisp to determine whether a
.sql-formatter-config.jsonexists in the VC root directory.
sql Blocks in Org-Babel #
org-babel-execute:sql in this way allows me to use
#+begin_src sql :engine bq :results raw blocks in org-babel and execute them with
C-c C-c. More
commonly, though, I set
#+PROPERTY: header-args:sql :engine bq :results raw at
the top of the document so that I can just mark a
src block as
sql and be done
(defun org-babel-execute:bq (orig-fun body params) (if (string-equal-ignore-case (cdr (assq :engine params)) "bq") (json-to-org-table-parse-json-string (org-babel-execute:shell (concat "bq query --format=json --nouse_legacy_sql '" body "'") params)) (org-babel-execute:sql body params))) (advice-add 'org-babel-execute:sql :around #'org-babel-execute:bq)
Pyright error links in ∗compilation∗ #
M-x compile feature does not recognize or parse
pyright error messages out
of the box, so I add that support myself. Here’s an example error message:
/home/robb/tmp/errors.py/ /home/robb/tmp/errors.py:1:1 - error: "foo" is not defined (reportUndefinedVariable) /home/robb/tmp/errors.py:1:1 - warning: Expression value is unused (reportUnusedExpression) /home/robb/tmp/errors.py:4:12 - error: Operator "+" not supported for types "str" and "Literal" Operator "+" not supported for types "str" and "Literal" (reportGeneralTypeIssues) 2 errors, 1 warning, 0 informations
To get the basic
M-g M-n and
M-g M-p navigation working, we just need a regex to
parse file name, line, and column number.
(with-eval-after-load 'compile (add-to-list 'compilation-error-regexp-alist-alist '(pyright "^[[:blank:]]+\\(.+\\):\\([0-9]+\\):\\([0-9]+\\).*$" 1 2 3)) (add-to-list 'compilation-error-regexp-alist 'pyright))
It would be nice if we could also capture the
\\(error\\|warning\\) part as
“KIND”, but I’m struggling to get it working.
Python check with “ruff” #
Another nice vanilla feature of
M-x python-check, which runs a
pre-specified linter. Setting that to
pyright if either of those
programs exist is a small time saver.
(use-package python :config (setq python-check-command "ruff") (add-hook 'python-mode-hook #'flymake-mode))
Fix Microsoft Windows Issues #
At one point, I ran into something similar to this elpy issue on Windows. The culprit was “App Execution Aliases” with python and python3 redirecting to the windows store. Using this fixed it:
winkey -> Manage app execution aliases -> uncheck python and python3
Also on Windows - a
pip install of
pyreadline3 is required to make
tab-completion work at all. It provides the
readline import symbol.
Make check command and virtualenv root safe for .dir-locals.el #
.dir-locals.el to have something like:
((python-mode . ((python-shell-virtualenv-root . "/path/to/my/.venv"))))
However, this only operates on `run-python’ shells. Also, for projects, we need to make sure that setting the virtualenv root is marked as safe.
(put 'python-check-command 'safe-local-variable #'stringp) (put 'python-shell-virtualenv-root 'safe-local-variable #'stringp)
Emacs Jupyter? #
Eventually, I would like to try the emacs-jupyter package to interface with Jupyter kernels from org-mode.
The most consistent way to get
eglot to properly configure the python virtual
pyright is to have a static file at the root of the project,
pyrightconfig.json. I wrote a short plugin that allows me to select a
completing-read and have Emacs write the content of
pyrightconfig.json based on what I selected, in the appropriate directory.
(use-package pyrightconfig :after (python))
Configuring pyright this way rather than “activating” an environment through
pythonic-activate or similar) means we can be running the language
server in more than one project at a time, each pointing to its respective
Formatting a buffer with
black has never been easier!
(use-package blacken :bind ("C-c p" . blacken-mode) :after (python))
Activating Virtual Environments Over Tramp #
(use-package tramp-venv :bind (("C-c t v a" . tramp-venv-activate) ("C-c t v d" . tramp-venv-deactivate)))
Tags - jump to definition the old way #
C-x p c
find . -name "*.py" -print | etags -
Some folks like to write markdown without hard line breaks. When viewing those
documents, I can use
M-x renz/md-hook to view it as if there were line breaks in
(defun renz/md-hook () "View buffer in visual fill mode with 80 character width." (interactive) (visual-fill-column-mode) (setq-local fill-column 80))
I make a lot of spelling mistakes as I type…
(add-hook 'markdown-mode-hook 'flyspell-mode) (add-hook 'markdown-mode-hook 'auto-fill-mode)
poly-markdown-mode enables syntax highlighting within code fences for markdown.
(use-package poly-markdown :after (markdown-mode) :mode ("\\.md" . poly-markdown-mode))
(use-package ahk-mode :mode "\\.ahk\\'")
Handy for viewing data quickly.
(use-package csv-mode :mode "\\.csv\\'")
Small tool configuration #
These are tweaks for both third party packages and those bundled with emacs that require little configuration and don’t warrant a top-level header.
(use-package imenu :config (setq imenu-auto-rescan t org-imenu-depth 3))
(use-package dabbrev ;; Swap M-/ and C-M-/ :bind (("M-/" . dabbrev-completion) ("C-M-/" . dabbrev-expand)) ;; Other useful Dabbrev configurations. :custom (dabbrev-ignored-buffer-regexps '("\\.\\(?:pdf\\|jpe?g\\|png\\)\\'")))
dired uses bytes instead of “K”, “Mb”, or “G” for file sizes. I
also have it hide the mode, size, and owner of each file by default.
(use-package dired :hook (dired-mode . dired-hide-details-mode) :config (setq dired-listing-switches "-alFh") (setq dired-dwim-target t))
Also enabled above is Do-What-I-Mean (DWIM) copying. This is for when two dired
windows are open, and we want to copy something from one location to the other.
dired-dwim-target, it auto-populates the minibuffer with the other
dired window’s path when issuing a copy command with
Coterm mode #
Adds the ability to use TUI programs in shell mode.
(use-package coterm :unless (renz/windowsp) :config (coterm-mode))
Visual fill column #
For visual lines, this adds line breaks at the fill-column value. Especially useful for prose that is meant to be copied to other mediums, such as email or word.
(use-package visual-fill-column :config (add-hook 'visual-line-mode-hook #'visual-fill-column-mode))
eww - search engine and browser #
(use-package eww :config (setq eww-search-prefix "https://duckduckgo.com/html/?q="))
Modeled after Vim’s
ya commands, these let us yank or kill text
within a “surrounding” delimiter, such as "" or ().
(use-package change-inner :bind (("C-c c i" . change-inner) ("C-c c o" . change-outer) ("C-c y i" . yank-inner) ("C-c y o" . yank-outer)))
Esup: startup time profiling #
esup is a tool for profiling the startup time of Emacs. This snippet is a work around of a bug where esup tries to step into the byte-compiled version of `cl-lib’, and fails horribly: https://github.com/jschaf/esup/issues/85
(use-package esup :bind ("C-c x p") :config (setq esup-depth 0))
Reloading Emacs #
Often used when changing up my
(use-package restart-emacs :bind ("C-c x r" . restart-emacs))
Language Server Protocol (LSP) with
As of version 29, eglot (Emacs polyGLOT) is bundled with Emacs. It provides Emacs with the client side configuration for the language server protocol.
(use-package eglot :bind (("C-c l c" . eglot-reconnect) ("C-c l d" . flymake-show-buffer-diagnostics) ("C-c l f f" . eglot-format) ("C-c l f b" . eglot-format-buffer) ("C-c l l" . eglot) ("C-c l r n" . eglot-rename) ("C-c l s" . eglot-shutdown)))
eglot always start up for a python buffer, we would tangle this line
init.el. However, this can cause a significant loading delay over Tramp,
and I would prefer snappy, simple access with LSP provided on an as-needed
(add-hook 'python-mode-hook 'eglot-ensure)
For a while, it looks like Emacs was trying out something called semantic-mode,
which looks a lot like a precursor to what we now know as the Language Server
Protocol. Enabling it was done through adding the
semantic-mode hook to your
language’s major mode hook:
(add-hook 'python-mode-hook 'semantic-mode)
Don’t forget about these #
There are several other interesting options that I haven’t tried out yet, including:
- emacs-eaf/emacs-application-framework <— big hassle
- notmuch for email
Thank you for reading ’till the end or for being interested on how to end an Emacs package. So that’s it, let’s gracefully finish tangling everything:
(provide 'init.el) ;;; init.el ends here