Skip to main content
robbmann

My Literate .emacs.d

·38 mins
My Literate =.emacs.d=

Want to use it? Go ahead!

git clone https://github.com/renzmann/.emacs.d ~/.emacs.d

All external dependency sources are explicitly included under the elpa/ directory, meaning it's as simple as "clone-n-go". Opening this document under my configuration looks like so:

emacs-screen.png

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.

Goals

If I had to sum up the theme of my configuration, it would be "vanilla extract," because in only a few instances do I change the overt behavior of Emacs. Even with those, though, I want a configuration that fits my hands in such a way that I remain comfortable using 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 corfu and 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 under elpa/ maximizes portability and maintainability between systems.

Altogether, I wind up using Emacs 29+ on all three of the major platforms, in both GUI and TTY mode. So this config is designed to work equally well for:

platformterminalGUIssh + TTYTramp
Linux
macOS
Windows

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, YAML, TOML, 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:

  1. Additional major modes for common filetypes like Markdown, CSV, and YAML
  2. Error message support for pyright in a *Compilation* buffer
  3. Reasonable indentation behavior for SQL files
  4. Updating buffers automatically if their contents change on disk
  5. Syntax highlighting for Source blocks in Markdown
  6. Handling ANSI color escape codes in shell output, compilation, and VC buffers

Tangling

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.

Inspirations

I steal quite a lot from other, more qualified Emacs community contributors, such as:

Getting Emacs

For a while I would try to compile Emacs myself, but installing the whole compilation toolchain hasn't been worth it lately, especially on Windows. Instead, I've started simply downloading emacs from these sources on each of the platforms:

Windows

I go to the pretest FTP to get the latest version of Emacs. Usually not quite up-to-date with the master branch, but still one version number ahead of the most recent official release.

Mac

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.

In particular, this feature has saved me a lot of headaches that I ran into compiling Emacs on my own:

Emacs.app is signed with a developer certificate and notarized by Apple.

Very nice!

Linux

Depending on the machine, I get Emacs one of several ways in a GNU/Linux setup. These rank from highest to lowest priority:

  1. Through my system package manager, such as sudo apt-get install emacs or pacman -S emacs
  2. Through the official FTP
  3. Through the pretest FTP
  4. Through condax
  5. Compiling it myself

Compiling

If I do ever want to compile it myself, these are the options I use, making sure to export the correct CC and GCC variables:

git clone git://git.savannah.gnu.org/emacs.git --branch emacs-29 --depth 1
export CC=/usr/bin/gcc-10 CXX=/usr/bin/gcc-10
./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
make --jobs=$(nproc)
sudo make install

Header

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 <robbenzmann@gmail.com> ;; 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:

Custom

I prefer having custom modify its own file. This next snippet ensures any package-install or custom edits go to custom.el.

(setq custom-file (expand-file-name "custom.el" user-emacs-directory))
(when (file-exists-p custom-file)
  (load custom-file 'noerror))

Proxy settings

When behind a corporate proxy, we might have to authenticate before we can pull packages off ELPA. Emacs only uses the HOST and PORT portions of the http_proxy and https_proxy environment variables, so we need to set LOGIN (user id) and PASSWORD ourselves.

I store the login, port, and host variables in a proxy.el file (obviously outside version control) when I'm on a machine that's behind an http proxy. We grab the password interactively when such a file exists.

(defun renz/enable-proxy ()
  (interactive)
  "Turn on HTTP proxy."
  (let ((proxy-file (expand-file-name "proxy.el" user-emacs-directory)))
    (when (file-exists-p proxy-file)
      (load-file proxy-file)
      (setq url-proxy-services
            `(("no_proxy" . "^\\(localhost\\|10.*\\)")
              ("http" . ,(concat renz/proxy-host ":" renz/proxy-port))
              ("https" . ,(concat renz/proxy-host ":" renz/proxy-port))))
      (setq url-http-proxy-basic-auth-storage
            (list
             (list
              (concat renz/proxy-host ":" renz/proxy-port)
              (cons renz/proxy-login
                    (base64-encode-string
                     (concat renz/proxy-login ":" (password-read "Proxy password: "))))))))))

Packages

The initial cornerstone of every Emacs configuration is a decision on package management and configuration. I opt for use-package and 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 package-enable-at-startup in init.el.

(require 'package)
(setq package-enable-at-startup nil)
(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, because I cannot always ensure that I have a stable connection to GNU ELPA (in the case of package-install-selected-packages) or the public github.com (for package-vc-install-selected-packages). Instead, I rely on M-x package-install and M-x package-delete, and only permit 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 keeping my init.el (this README), custom.el, the package-selected-packages variable, and 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)
  (package-vc-install-selected-packages))

(when (and (cl-notevery ‘package-installed-p package-selected-packages) (yes-or-no-p “Install VC packages?”)) (package-vc-install-selected-packages))

There are also a few hand-made packages I keep around in a special .emacs.d/site-lisp directory.

(add-to-list 'load-path (expand-file-name "site-lisp/" user-emacs-directory))

OS-specific Configuration

Microsoft Windows

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.

(defun renz/windowsp ()
  "Are we on Microsoft Windows?"
  (memq system-type '(windows-nt cygwin ms-dos)))
(when (and (renz/windowsp) (executable-find "pwsh"))
  (setq shell-file-name "pwsh"))

There are a few things I set up independent of Emacs. Namely, find, xargs, and rg. These days, I can usually install these things with winget:

winget install BurntSushi.ripgrep.GNU
winget install GnuWin32.FindUtils
winget install GnuWin32.Grep
winget install RubyInstallerTeam.RubyWithDevKit.3.2  # For building my website with Jekyll
winget install Python.Python.3.11  # I work a lot in python

You can use Emacs without these, but some commands like M-x grep or M-x project-find-regexp will not work without making sure the GNU version of find and grep (or a suitable replacement) are on your PATH. I tend not to muck with PATH inside Emacs if I can help it, and instead launch Emacs from powershell where things are properly set. Usually I'll have some things like this in my $PROFILE:

$ENV:Path = "${ENV:ProgramFiles}\Hunspell\bin\;" + $ENV:Path
$ENV:Path = "${ENV:ProgramFiles(x86)}\GnuWin32\bin\;" + $ENV:Path
$ENV:Path = "${ENV:ProgramFiles}\Emacs\emacs-29.1\bin\;" + $ENV:Path
$ENV:PROFILE = $PROFILE
$ENV:LANG = "en_US"
$ENV:DICPATH = "$ENV:ProgramFiles\Hunspell\"

The duplicate $PROFILE thing is so we can access that file through C-x C-f $PROFILE within Emacs. Check out the Spellchecking section on the Hunspell stuff.

On the winkey

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":

(w32-register-hot-key [s-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 C-c ones.

macOS

Launching Emacs from the typical application launcher or command-space usually won't capture any modifications to $PATH, typically handled in a file like ~/.profile or ~/.bashrc. So, the main configuration included here is from exec-path-from-shell.

(when (eq system-type 'darwin)
  (setq exec-path-from-shell-arguments '("-l"))
  (exec-path-from-shell-initialize))

Font

Fonts are a tricky business. See Emacs/Fonts in the manual (C-h i) for relevant information on how checking and setting default fonts works:

(cond ((x-list-fonts "Hack Nerd Font")
       (add-to-list 'default-frame-alist '(font . "Hack Nerd Font-12")))
      ;; ((x-list-fonts "Segoe UI Emoji")
      ;;  (add-to-list 'default-frame-alist '(font . "Segoe UI Emoji-12")))
      )

(defun renz/change-font-size (new-size) “Change the default font size to the given size.” (interactive “nNew font size: “) (set-face-attribute ‘default nil :height (* 10 new-size)))

Theme

With the introduction of modus-vivendi-tinted in Emacs 29, I really have no need for any external themes now. It is accessible, well optimized for org-mode and prog-mode, and distributed with vanilla Emacs. Hats off to Prot for these wonderful themes.

(use-package emacs
  :custom
  (modus-themes-inhibit-reload nil)
  (modus-themes-subtle-line-numbers t)
  (modus-themes-syntax '(alt-syntax faint green-strings yellow-comments))
  (modus-themes-diffs 'desaturated)
  ;; (modus-themes-hl-line 'intense)
  (modus-themes-deuteranopia nil)
  (modus-themes-bold-constructs t)
  (modus-themes-italic-constructs t)
  ;; (modus-themes-mode-line 'borderless)
  (modus-themes-fringes 'subtle)
  (modus-themes-org-blocks 'gray-background)
  (modus-themes-vivendi-color-overrides '((bg-main . "#010101")))
  :bind   ("<f5>" . modus-themes-toggle)
  :config
  (if (version< emacs-version "29.0")
      (load-theme 'leuven-dark)
    (load-theme 'modus-vivendi-tinted t)))

Emacs' Built-in Settings

My settings for base Emacs behavior. Assuming I ran with no plugins (ala emacs -Q), I would still set most of these by hand at one point or another. This section is designed for variables that modify Emacs and its editing behavior directly. Configuation for built-in tools, such as Dired, Tramp, and Tree-sitter are located under Tool configuration.

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. It is the central kernel around which my ~/.emacs and later ~/.emacs.d/init.el grew.

;; Stop stupid bell
(setq ring-bell-function 'ignore)

The bell is really, really annoying.

Start a server for emacsclient

(server-start)

Don't hang when visiting files with extremely long lines

(global-so-long-mode t)

Unicode

Sometimes (especially on Windows), Emacs gets confused about what encoding to use. These settings 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)

Remember minibuffer history

Found this on a System Crafters video.

(setq history-length 25)
(savehist-mode 1)

Render ASCII color escape codes

For files containing color escape codes, this provides a way to render the colors in-buffer. Provided by a helpful stackoverflow answer.

(defun renz/display-ansi-colors ()
  "Render colors in a buffer that contains ASCII color escape codes."
  (interactive)
  (require 'ansi-color)
  (let ((inhibit-read-only t))
    (ansi-color-apply-on-region (point-min) (point-max))))

Colored output in eshell and *compilation*

In *compilation* mode, we just use the "display colors" function from above. Enable colors in the *compilation* buffer.

(add-hook 'compilation-filter-hook #'renz/display-ansi-colors)

For eshell, this is copy-pasted from a stack overflow question.

(add-hook 'eshell-preoutput-filter-functions  #'ansi-color-apply)

xterm-color

Soon, I'd like to swap out my hacks above for this more robust package: https://github.com/atomontage/xterm-color/tree/master

Recent files menu

This enables "File -> Open Recent" from the menu bar and using completing-read over the recentf-list.

(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)))

Fill-column

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.

(scroll-bar-mode -1)

By default, though, I prefer it to be off when I start Emacs.

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.

(defun renz/modify-margins ()
  "Add some space around each window."
  (interactive)
  (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)))

(renz/modify-margins)

We also need to make sure this runs each time we change the ef-theme, otherwise the old background color will linger in the margins.

(add-hook 'ef-themes-post-load-hook 'renz/modify-margins)

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 came 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.

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

Without setting global-auto-revert-mode, we have to remember to issue a revert-buffer or revert-buffer-quick (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.

(global-auto-revert-mode)

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)

Always turn on flymake in prog mode

(add-hook 'prog-mode-hook #'flymake-mode)

Another, related mode is flyspell-prog-mode, which is just checks spelling in comments and strings.

(add-hook 'prog-mode-hook #'flyspell-prog-mode)

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))

Shorten yes/no prompts to y/n

(setq use-short-answers 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)

Killing buffers with a running process

Typically, Emacs will ask you to confirm before killing a buffer that has a running process, such as with run-python, a *shell* buffer, or a *compilation* buffer.

(setq kill-buffer-query-functions
  (remq 'process-kill-buffer-query-function
         kill-buffer-query-functions))

Don't wrap lines

I much prefer having long lines simply spill off to the right of the screen than having them wrap around onto the next line, except in the case where I'd like to see wrapped line content, like in one of the shell modes.

(setq-default truncate-lines t)
(add-hook 'eshell-mode-hook (lambda () (setq-local truncate-lines nil)))
(add-hook 'shell-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.

(defun renz/display-relative-lines ()
  (setq display-line-numbers 'relative))

(add-hook ‘prog-mode-hook #‘renz/display-relative-lines) (add-hook ‘yaml-mode-hook #‘renz/display-relative-lines)

(unless (display-graphic-p) (add-hook ’text-mode-hook #‘renz/display-relative-lines))

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.

(delete-selection-mode t)

Enable mouse in terminal/TTY

(xterm-mouse-mode 1)

Compilation

As new text appears, the default behavior is for it to spill off the bottom, unless we manually scroll to the end of the buffer. Instead, I prefer the window to automatically scroll along with text as it appears, stopping at the first error that appears.

(setq compilation-scroll-output 'first-error)

Tool bar

I usually leave the tool bar disabled

(tool-bar-mode -1)

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)

Prefer rg over grep

(use-package grep
  :config
  (when (executable-find "rg")
    (setq grep-program "rg")
    (grep-apply-setting
     'grep-find-command
     '("rg -n -H --color always --no-heading -e '' $(git rev-parse --show-toplevel || pwd)" . 27))))

If you're on Windows, this command assumes you're running pwsh version 7 or higher.

Shorter file paths in grep/compilation buffers

(use-package scf-mode
  :load-path "site-lisp"
  :hook (grep-mode . (lambda () (scf-mode 1))))

Confirm when exiting Emacs

It's very annoying when I'm working and suddenly I meant to do C-c C-x, but instead hit 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))

Spellchecking

On macOS and linux I typically use aspell, given how easy it is to install. For Windows, I'll set up hunspell, which I install from the hunspell-binary repo. After installing the hunspell binary, it requires installing a dictionary and affix file to the installation directory:

curl -o en_US.dic https://cgit.freedesktop.org/libreoffice/dictionaries/plain/en/en_US.dic?id=a4473e06b56bfe35187e302754f6baaa8d75e54f
curl -o en_US.aff https://cgit.freedesktop.org/libreoffice/dictionaries/plain/en/en_US.aff?id=a4473e06b56bfe35187e302754f6baaa8d75e54f

Then move these files to wherever hunspell is. For instance, C:\Program Files\Hunspell.

(cond ((executable-find "aspell")
       (setq ispell-program-name "aspell"
             ispell-really-aspell t))
      ((executable-find "hunspell")
       (setq ispell-program-name "hunspell"
             ispell-really-hunspell t)))

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))
      )

Enable narrow-to-region

narrow-to-region restricts editing in this buffer to the current region. The rest of the text becomes temporarily invisible and untouchable but is not deleted; if you save the buffer in a file, the invisible text is included in the file. C-x n w makes all visible again.

(put 'narrow-to-region 'disabled nil)

Enable up/downcase-region

Allows us to convert entire regions to upper or lower case.

(put 'upcase-region 'disabled nil)
(put 'downcase-region 'disabled nil)

Mark rings and registers: bigger, faster, stronger

16 is the default number of marks stored on the global and local mark rings is 16. I hop around much more than 16 times as I'm editing, so I expand this a bit.

(setq-default mark-ring-max 32)
(setq global-mark-ring-max 32)

Another handy shortcut is continually popping marks by repeated C-<SPC> after the first C-u C-<SPC> through the set-mark-command-repeat-pop setting.

(setq set-mark-command-repeat-pop t)

And, because I always forget it, to pop a global mark you use C-x C-<SPC>. The local version, C-u C-<SPC> will only pop marks from the current buffer. So the C-x C-<SPC> version is much closer to how Vim's jump stack works.

A handy "bookmark" system (aside from actual bookmarks) is to set common buffers and files to registers pre-emptively.

(set-register ?S '(buffer . "*scratch*"))
(set-register ?I `(file . ,(expand-file-name "README.org" user-emacs-directory)))
(set-register ?B `(file . "~/.bashrc"))

The default keybinding for jump-to-register is C-x r j R, where R is the name of the register. My own personal convention here is to use lower-case letter for interactive session bookmarks that will be lost between sessions, and upper-case letters for ones I've set permanently here.

Before I was aware of this feature I had created my own jump-to-X style functions, but this is much better! You even get a handy pop-up if you wait a second after typing C-x r j to see all the available registers.

Keybindings

Expanded/better defaults

These convenient chords allow for fast text replacement by holding C-M- and rapidly typing k and 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.

(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
         ("C-c n" . flymake-goto-next-error)
         ("C-c p" . flymake-goto-prev-error)))

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 e.g. org-goto, 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 use-package somewhere else.

C-c b build / compile

(global-set-key (kbd "C-c b") #'compile)
(global-set-key (kbd "C-c B") #'recompile)

C-c c Calendar

(global-set-key (kbd "C-c c") #'calendar)

C-c d Navigating to symbols using old-school TAGS

Before the whole language server revolution, we had TAGS files for caching the location of symbol definitions. etags comes with Emacs, and combining some clever use of find with it can render a pretty good symbol search experience. To generate the TAGS file, I usually have a TAGS recipe that looks something similar to this in each project's Makefile:

find . -type d -name ".venv" -prune \
    -o -type d -name ".ipynb_checkpoints" -prune \
    -o -type d -name ".node_modules" -prune \
    -o -type d -name "elpa" -prune \
    -o -type f -name "*.py" -print \
    -o -type f -name "*.sql" -print \
    -o -type f -name "*.el" -print \
    | etags -

Then, M-x project-compile RET make TAGS builds a tags table. At which point, I can use tags-completion-table to build a list of symbols I can navigate to with completion, with just a little help from xref-find-definitions.

(defun renz/find-tag ()
  "Use `completing-read' to navigate to a tag."
  (interactive)
  (require 'etags)
  (tags-completion-table)
  (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 browse url of buffer

(global-set-key (kbd "C-c i") #'browse-url-of-buffer)

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 u open URL at point in browser

(global-set-key (kbd "C-c u") #'browse-url-at-point)

C-c v faster git-commit

(defun renz/git-commit ()
  (interactive)
  (vc-next-action nil)
  (log-edit-show-diff)
  (other-window 1))

(global-set-key (kbd “C-c v”) #‘renz/git-commit)

C-c w whitespace mode

(global-set-key (kbd "C-c w") #'whitespace-mode)

C-c Other bindings

(global-set-key (kbd "C-c <DEL>") #'backward-kill-sexp)  ;; TTY-frindly
(global-set-key (kbd "C-c <SPC>") #'mark-sexp)  ;; TTY-friendly

F5-F9

Like the 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" that way.

Super bindings

(global-set-key (kbd "s-p") #'project-switch-project)

Text Completion

Emacs offers incredible depth and freedom when configuring methods to automatically complete text. There are actually two things that "autocompletion" can refer to in Emacs:

  1. Minibuffer completion
  2. Completion at point

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 buffer called *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

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. basic functions much like your default TAB-complete at a Bash shell.

(setq completion-styles '(flex basic partial-completion emacs22))

Nicer Display and Behavior of *Completions*

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 'lazy
        completion-auto-select 'second-tab
        completion-show-help nil
        completions-sort nil
        completions-header-format nil))

Completion in the minibuffer and at point

By default, Emacs uses M-TAB, or the equivalent C-M-i for completion-at-point. I'd much prefer to use the easier and more intuitive TAB.

(setq tab-always-indent 'complete)

Something I might try is to use icomplete along with icomplete-in-buffer to get something like a little window that updates as I type. It seems a little wonky, since TAB-completion will still cause the ∗Completions∗ buffer to pop up, even while Icomplete is active, unless we set completion-auto-help to lazy; and even then it will still come up on the second TAB press.

(setq icomplete-in-buffer t)
(setq icomplete-prospects-height 10)
(icomplete-vertical-mode t)

In the case that we need to enter a new file name, but fido is still showing a completion candidate, you have to use C-d to refuse completion and take whatever is currently in the prompt. For instance, if we are editing a file hello.py, and then use C-x C-f hell.py, the minibuffer will complete hell.py into hello.py if we use RET, and will open a new buffer for hell.py if we use C-d.

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) (add-hook ‘bash-ts-mode-hook #‘renz/sh-indentation)

HTML

This changes the behavior of a few commonly-used tags in web pages that I write.

(use-package sgml-mode
  :defer t
  :config
  (let* ((p-tag-old (assoc "p" html-tag-alist))
         ;; Close the <p> tag and open on a new line.
         (p-tag-new `("p" \n ,(cdr (cdr p-tag-old)))))
    (add-to-list 'html-tag-alist p-tag-new)
    ;; Close the <code> tag and stay inline.
    (add-to-list 'html-tag-alist '("code"))))

CSS

(setq css-indent-offset 2)

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

Org-mode

(setq renz/org-home "~/.emacs.d/org/")

org-mode provides org-babel-tangle-jump-to-org, which jumps back to an Org source file from within the tangled code. renz/org-babel-tangle-jump-to-src, 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 stackoverflow answer.

(defun renz/org-babel-tangle-jump-to-src ()
  "The opposite of `org-babel-tangle-jump-to-org'.
Jumps to an Org src block from tangled code."
  (interactive)
  (if (org-in-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) to 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 org-startup-indented call.

(defun renz/list-files-with-absolute-path (directory)
  "Return a list of files in DIRECTORY with their absolute paths."
  (cl-remove-if-not #'file-regular-p (directory-files directory t ".*\.org$")))

(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) (setq indent-tabs-mode nil)))))

: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 y” . ox-clip-image-to-clipboard))

:custom (org-image-actual-width nil “Enable resizing of images”) (org-agenda-files (renz/list-files-with-absolute-path 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-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) (R . 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) )))

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)

SQL

DDL is SQL

(add-to-list 'auto-mode-alist '("\\.ddl\\'" . sql-mode))
(add-to-list 'auto-mode-alist '("\\.bql\\'" . sql-mode))

Indentation

Vanilla Emacs doesn't offer a lot (read: nothing) in terms of making SQL code pretty. I tend to format SQL like this:

SELECT
    whatever,
    thing
FROM
    wherever AS w
    JOIN the_other AS t ON w.id = t.id
GROUP BY
    whatever

The configuration of sql-indent below achieves that nicely when using RET and TAB for formatting.

(defun renz/sql-mode-hook ()
  (setq tab-width 4))

(defvar renz/sql-indentation-offsets-alist ‘((syntax-error sqlind-report-sytax-error) (in-string sqlind-report-runaway-string) (comment-continuation sqlind-indent-comment-continuation) (comment-start sqlind-indent-comment-start) (toplevel 0) (in-block +) (in-begin-block +) (block-start 0) (block-end 0) (declare-statement +) (package ++) (package-body 0) (create-statement +) (defun-start +) (labeled-statement-start 0) (statement-continuation +) (nested-statement-open sqlind-use-anchor-indentation +) (nested-statement-continuation sqlind-use-previous-line-indentation) (nested-statement-close sqlind-use-anchor-indentation) (with-clause sqlind-use-anchor-indentation) (with-clause-cte +) (with-clause-cte-cont ++) (case-clause 0) (case-clause-item sqlind-use-anchor-indentation +) (case-clause-item-cont sqlind-right-justify-clause) (select-clause 0) (select-column sqlind-indent-select-column) (select-column-continuation sqlind-indent-select-column +) (select-join-condition ++) (select-table sqlind-indent-select-table) (select-table-continuation sqlind-indent-select-table +) (in-select-clause sqlind-lineup-to-clause-end sqlind-right-justify-logical-operator) (insert-clause 0) (in-insert-clause sqlind-lineup-to-clause-end sqlind-right-justify-logical-operator) (delete-clause 0) (in-delete-clause sqlind-lineup-to-clause-end sqlind-right-justify-logical-operator) (update-clause 0) (in-update-clause sqlind-lineup-to-clause-end sqlind-right-justify-logical-operator)))

(defun renz/sql-indentation-offsets () (setq sqlind-indentation-offsets-alist renz/sql-indentation-offsets-alist) (setq sqlind-basic-offset 4))

(use-package sql-indent :hook (sqlind-minor-mode . renz/sql-indentation-offsets))

(use-package sql-mode :hook ((sql-mode . renz/sql-mode-hook) (sql-mode . sqlup-mode) (sql-mode . sqlind-minor-mode)))

Interactive hive2 mode

This "hive2" package came from the days where I was working on an on-prem system that used hive2 as the main command-line interface to Hive. I don't use this much now, but it's a good reference for implementing a plug-in to a new interactive SQL CLI.

(use-package hive2
  :load-path "site-lisp/"
  :demand t
  :mode ("\\.hql" . sql-mode))

Interactive bq shell

The SQL interactive commands are looking for a single executable file, so let's set that up somewhere common, like ~/.local/bin/bq-shell.

#!/usr/bin/env sh
bq shell "$@"

Also, we don't want to use "legacy SQL" in our queries, which requires us to configure the bq query statically in a ~/.bigqueryrc file, according to the Google issue tracker.

[query]
--use_legacy_sql=false

Then enable the BQ product.

(use-package bq
  :load-path "site-lisp"
  :demand t)

BigQuery sql Blocks in Org-Babel

Advising 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 with it.

(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)

This also typically requires #+OPTIONS: ^:nil at the top of the Org document to stop underscores from messing up how column names are displayed.

TODO BigQuery exception markers

When running BigQuery from a *compilation* buffer, it would be nice if I could get error markers to jump directly to the issue.

Python

(add-to-list 'auto-mode-alist '("Pipfile" . toml-ts-mode))

Pyright error links in *compilation*

The 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[1]"
    Operator "+" not supported for types "str" and "Literal[1]" (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 python-mode is M-x python-check, which runs a pre-specified linter. Setting that to mypy or pyright if either of those programs exist is a small time saver.

(use-package python
  :config
  (require 'eglot)
  (setq python-check-command "ruff")
  (add-hook 'python-mode-hook #'flymake-mode)
  (add-hook 'python-ts-mode-hook #'flymake-mode)
  ;; (add-to-list 'eglot-server-programs '((python-mode python-ts-mode) "ruff-lsp"))
  )

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

Virtualenvs require .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)
(put 'pyvenv-default-virtual-env-name 'safe-local-variable #'stringp)

Emacs Jupyter?

Eventually, I would like to try the emacs-jupyter package to interface with Jupyter kernels from org-mode.

pyrightconfig.json

The most consistent way to get eglot to properly configure the python virtual environment with pyright is to have a static file at the root of the project, called pyrightconfig.json. I wrote a short plugin that allows me to select a directory using 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 Emacs (ala 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 virtual environment.

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)))

Pyvenv for virtual environments

(use-package pyvenv
  :init
  (if (eq system-type 'darwin)
      (setenv "WORKON_HOME" "~/micromamba/envs/")
    (setenv "WORKON_HOME" "~/.conda/envs/"))
  :bind
  (("C-c p w" . pyvenv-workon)
   ("C-c p d" . pyvenv-deactivate)
   ("C-c p a" . pyvenv-activate))
  :config
  (pyvenv-mode))

Markdown

When installing markdown through Anaconda, the executable is actually called markdown_py. In case markdown isn't found, use that instead.

(when (and (not (executable-find "markdown")) (executable-find "markdown_py"))
  (setq markdown-command "markdown_py"))

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 it.

(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)

And I like to see language syntax highlighting within code fences.

(setq markdown-fontify-code-blocks-natively t)

Missing auto-modes

These really should already be in auto-mode-alist, but aren't for some reason.

(add-to-list 'auto-mode-alist '("\\.rs\\'" . rust-ts-mode))
(add-to-list 'auto-mode-alist '("\\.go\\'" . go-ts-mode))
(add-to-list 'auto-mode-alist '("\\.ts\\'" . typescript-ts-mode))
(add-to-list 'auto-mode-alist '("\\.dockerfile\\'" . dockerfile-ts-mode))

csv-mode

Handy for viewing data quickly.

(use-package csv-mode
  :mode "\\.csv\\'")

Tool configuration

These are tweaks for self-contained tooling, such as third party packages or built-in packages that have a well-defined scope and namespace.

eldoc

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)

imenu

(use-package imenu
  :config
  (setq imenu-auto-rescan t
        org-imenu-depth 3))

dabbrev

(use-package dabbrev
  :custom
  (dabbrev-ignored-buffer-regexps '("\\.\\(?:pdf\\|jpe?g\\|png\\)\\'")))

dired

By default, 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. By enabling dired-dwim-target, it auto-populates the minibuffer with the other dired window's path when issuing a copy command with C.

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

Ecosia requires JavaScript, unfortunately.

(use-package eww
  :config (setq eww-search-prefix "https://duckduckgo.com/html/?q="))

Reloading Emacs

Often used when changing up my init.el.

(use-package restart-emacs
  :bind ("C-c x r" . restart-emacs))

Language Server Protocol (LSP) with eglot

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)))

To have eglot always start up for a python buffer, we would tangle this line into 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 basis.

(add-hook 'python-mode-hook 'eglot-ensure)

Side show: semantic-mode

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)

TreeSitter

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 install tree-sitter grammars independently from Emacs. Right now, I'm using casouri's modules, which I build and install under ~/.emacs.d/tree-sitter, if they don't already exist under /usr/local/lib/ or ~/.local/lib. In case of the latter, I just add extra paths to treesit-extra-load-path explicitly.

(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 tree-sitter modules to Emacs included a full guide, which can be read in Info under "Parsing Program Source".

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

I've posted this to GitHub and MELPA as treesit-auto.

(use-package treesit-auto
  :custom
  (treesit-auto-install 'prompt)
  :config
  (global-treesit-auto-mode))

Before it was published to MELPA, I used a git subtree to manage the plugin. This is a pretty useful technique, so I keep these two one-liners around in case I need to reference or copy them. To get a copy of something as a subtree, I use this:

git subtree add -P site-lisp/treesit-auto git@github.com:renzmann/treesit-auto main --squash

Fetching updates is a similar command.

git subtree pull -P site-lisp/treesit-auto git@github.com:renzmann/treesit-auto main --squash

Ooo, aaah, shiny colors

I like to program "in Skittles":

(setq-default treesit-font-lock-level 3)

Tramp

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 .dir-locals. The Tramp manual (distributed with Emacs) recommends adjusting these for some speed improvements:

(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 disable .dir-locals over Tramp entirely, since I don't really use it much, if at all.

;; (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

Shell commands

The Async command buffer's default behavior is to print ^M characters (the carriage return) instead of actually clearing text. This is problematic for spinners and progress bars, so I have a little hack to work around that.

(defun renz/async-shell-command-filter-hook ()
  "Filter async shell command output via `comint-output-filter'."
  (when (equal (buffer-name (current-buffer)) "*Async Shell Command*")
    ;; When `comint-output-filter' is non-nil, the carriage return characters ^M
    ;; are displayed
    (setq-local comint-inhibit-carriage-motion nil)
    (when-let ((proc (get-buffer-process (current-buffer))))
      ;; Attempting a solution found here:
      ;; https://gnu.emacs.help.narkive.com/2PEYGWfM/m-chars-in-async-command-output
      (set-process-filter proc 'comint-output-filter))))

(add-hook ‘shell-mode-hook #‘renz/async-shell-command-filter-hook)

There might be a better way, but this mostly works for now.

Footer

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

Author: Robb Enzmann

Created: 2023-10-26 Thu 16:48

Validate

Author
Author
Robert Enzmann
I handle makes and snakes