My Literate .emacs.d
Table of Contents
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:
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:
platform | terminal | GUI | ssh + TTY | Tramp |
---|---|---|---|---|
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:
- Additional major modes for common filetypes like Markdown, CSV, and YAML
- Error message support for
pyright
in a*Compilation*
buffer - 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
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:
- Through my system package manager, such as
sudo apt-get install emacs
orpacman -S emacs
- Through the official FTP
- Through the pretest FTP
- Through condax
- 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:
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
Created: 2023-10-26 Thu 16:48