Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

Awesome Lists | Featured Topics | Projects

https://github.com/patrickt/emacs

the greatest emacs setup of all time
https://github.com/patrickt/emacs

Last synced: 19 days ago
JSON representation

the greatest emacs setup of all time

Awesome Lists containing this project

README

        

This is my Emacs configuration, assembled over the course of more than ten years. I first started using Emacs in 2009 out of sheer necessity---I first learned to code using [[https://github.com/textmate/textmate][TextMate]], but that wasn't an option upon, after arriving at college, being required to SSH into some creaky Sun boxes running geologically-ancient versions of Solaris. As of this writing, it is 2020, some eleven years into my Emacs journey, and I have an incurable case of Emacs-induced brain worms; I've spent on the order of hundreds of hours tweaking and refining my configuration. This file is the result of that process.

If there's anything that convinced me to take on this lifelong Emacs habit, it's because both macOS and iOS come, out of the box, with support for Emacs keybindings in their text areas. Said keybindings aren't hugely exhaustive, but they're useful, especially given that the ~Ctrl-k~ and ~Ctrl-y~ inputs for copy and paste operate on a /different/ pasteboard than do ~⌘C~ and ~⌘V~. Once I discovered that, and the dorkily exhiliating feeling of juggling information through the various system pasteboards, I was more or less entirely beholden to the Emacs way, despite the merits of modal editing[fn:1]. But the forcing factor was, after I left Apple, arriving at a job where I had to test C code on macOS, Linux, FreeBSD, and SmartOS. The difficulty of remembering keyboard shortcuts between TextMate, Sublime Text, and Emacs was leagues beyond insupportable, and as such I took the Emacs plunge.

This file is an [[https://en.wikipedia.org/wiki/Org-mode][Org-mode]] document, like all the posts on my blog. And if you're reading it on my blog in HTML form, it is because my blog pulls in my Emacs configuration as a submodule, and a conveniently-placed symlink means that it is treated like any other post, and its embedded code is rendered in fancy code blocks. If you're reading it on [[https://github.com/patrickt/emacs][its GitHub repository]], you'll see it rendered inline in the repository, as its filename is ~readme.org~. And at Emacs boot time, Emacs itself runs ~org-babel~, generates elisp code out of the code snippets embedded herein, and loads it; further updates to the blog entry are a ~git submodule~ command away. I feel both delight and shame in equal quantities at this state of affairs: having a really cherried-out, custom-built[fn:2] Emacs setup is one of those things that's cool because of how uncool it is, like growing giant vegetables at a county fair, or being really good at Connect Four.

Yet I don't feel bad about publishing something this self-indulgent. Not only because maybe it'll demystify what the care and feeding of a very-intense Emacs setup looks like, and also because yak-shaving is an excellent way to occupy myself in quarantine [fn:3], but because I'm happier with my setup as an org-mode document, as Emacs configurations generally have a high propensity for entropy: it's easy to leave a few computer-specific hacks in some dusty corner of a config, and then, upon returning years later, having absolutely no idea what that hack does. Forcing a literate style guilts me into documenting every inch of it. In addition, I have been thanked by strangers for the quality of my config, and my coworker described it as "inspiring", which seems like sufficient justification to continue, at least with respect to my ego.

This configuration assumes you're running Emacs 28, the latest version (though I live off of ~HEAD~). Note that I strongly, /strongly/ recommend using an Emacs built with native compilation enabled. The difference in speed is truly profound. Getting ~libgccjit~ and friends set up can be a bit of a bear (I use [[https://github.com/jimeh/build-emacs-for-macos][this script]] on macOS), but it's absolutely essential if you want to live in Emacs to the degree that I do.

[fn:1] I've tried to reconfigure my brain to use modal editing, to little avail, but its model of a domain-specific-language for text editing is a hugely exciting one to me.

[fn:2] My configuration is not built atop one of the all-in-one Emacs distributions like [[https://www.spacemacs.org][Spacemacs]] or [[https://github.com/hlissner/doom-emacs][Doom Emacs]]. I probably would have if either had been around at the beginning of my Emacs journey, but at this point my own personal set of key bindings is burnt into my brain.

[fn:3] Hello, future generations! If you're reading this, please believe me when I say that *:2020* was a truly enervating time to be a human being.

** Canonical links to this document

The most up-to-date version of this document is located [[https://github.com/patrickt/emacs][on GitHub]]. A better-rendered version can be found [[https://blog.sumtypeofway.com/posts/emacs-config.html][on my blog]], but might not be as current as the first version.

* Configuration: start!

** Preliminaries

We have to be sure to set ~lexical-binding~ in the file header to opt into Emacs lexical scope. Emacs Lisp really isn't the worst language once you pull in the community's /de facto/ standard libraries, but you need lexical scope.

#+begin_src emacs-lisp
;; -*- coding: utf-8; lexical-binding: t -*-
#+end_src

I used to set ~use-package-always-ensure~ here, but now that is conditional, as always-ensure means that we hit the network every time on startup. Seems unnecessary. As such, we set it in the [[https://github.com/patrickt/emacs/blob/master/init.el][~init.el~]] that bootstraps this whole enterprise if ~use-package~ wasn't installed, which is only true on a fresh install or wiped ~elpa~ directory.

#+begin_src emacs-lisp
(require 'use-package)
#+end_src

Many of Emacs's defaults are ill-suited for my purposes, but the first one that needs fixing is the shockingly low garbage-collection threshold, which defaults to a paltry *:8kb*. Setting it to *:100mb* seems to strike a nice balance between GC pauses and performance. We also need to bump the number of bindings/unwind-protects (~max-specpdl-size~).

#+begin_src emacs-lisp
(setq gc-cons-threshold 100000000)
(setq max-specpdl-size 5000)
#+end_src

The most useful Emacs command is ~execute-extended-command~. It should be painless to access from the home row. (~bind-key*~ ensures that this setting is propagated through all major modes, which saves us a bunch of ~unbind-key~ calls in ~use-package~ stanzas.) Why not something even easier, like ~C-;~, you ask? Unfortunately, macOS Terminal.app swallows that keybinding and does nothing with it. I'm sure this is correct behavior by some sort of standard, but I have to work around it, since occasionally I do use Emacs in the terminal.

#+begin_src emacs-lisp
(bind-key* "C-c ;" #'execute-extended-command)
(bind-key* "C-c 4" #'execute-extended-command) ;; for a purely left-handed combo
(bind-key* "C-c C-;" #'execute-extended-command-for-buffer)
#+end_src

Since subsequent packages like ~libgit~ may depend on executables like ~cmake~, we need to ensure that Emacs has access to the PATH associated with the current environment.

#+begin_src emacs-lisp
;; exec-path-from shell was misbehaving, this hack seems to mollify it
(use-package exec-path-from-shell
:init
(exec-path-from-shell-initialize))
#+end_src

With this auxiliary package for ~use-package~, we can instruct Emacs that a given package depends on the presence of a system tool. It will even install this tool with the system's recommended package manager.

#+begin_src emacs-lisp
(use-package use-package-ensure-system-package)
#+end_src

The ~try~ package lets me try out a new Emacs package without it cluttering up my system permanently.

#+begin_src emacs-lisp
(use-package try)
#+end_src

** Fixing defaults

Fixing Emacs's defaults is a nontrivial problem. We'll start with UI concerns.

#+begin_src emacs-lisp
(setq
;; No need to see GNU agitprop.
inhibit-startup-screen t
;; No need to remind me what a scratch buffer is.
initial-scratch-message nil
;; Double-spaces after periods is morally wrong.
sentence-end-double-space nil
;; Never ding at me, ever.
ring-bell-function 'ignore
;; Save existing clipboard text into the kill ring before replacing it.
save-interprogram-paste-before-kill t
;; Prompts should go in the minibuffer, not in a GUI.
use-dialog-box nil
;; Fix undo in commands affecting the mark.
mark-even-if-inactive nil
;; Let C-k delete the whole line.
kill-whole-line t
;; search should be case-sensitive by default
case-fold-search nil
;; accept 'y' or 'n' instead of yes/no
;; the documentation advises against setting this variable
;; the documentation can get bent imo
use-short-answers t
;; my source directory
default-directory "~/src/"
;; eke out a little more scrolling performance
fast-but-imprecise-scrolling t
;; prefer newer elisp files
load-prefer-newer t
;; when I say to quit, I mean quit
confirm-kill-processes nil
;; if native-comp is having trouble, there's not very much I can do
native-comp-async-report-warnings-errors 'silent
;; unicode ellipses are better
truncate-string-ellipsis "…"
;; I want to close these fast, so switch to it so I can just hit 'q'
help-window-select t
;; this certainly can't hurt anything
delete-by-moving-to-trash t
;; keep the point in the same place while scrolling
scroll-preserve-screen-position t
;; more info in completions
completions-detailed t
;; highlight error messages more aggressively
next-error-message-highlight t
;; don't let the minibuffer muck up my window tiling
read-minibuffer-restore-windows t
;; scope save prompts to individual projects
save-some-buffers-default-predicate 'save-some-buffers-root
;; don't keep duplicate entries in kill ring
kill-do-not-save-duplicates t
)

;; Never mix tabs and spaces. Never use tabs, period.
;; We need the setq-default here because this becomes
;; a buffer-local variable when set.
(setq-default indent-tabs-mode nil)
#+end_src

It's good that Emacs supports the wide variety of file encodings it does, but UTF-8 should always, /always/ be the default.

#+begin_src emacs-lisp
(set-charset-priority 'unicode)
(prefer-coding-system 'utf-8-unix)
#+end_src

We also need to turn on a few modes to have behavior that's even remotely modern.

#+begin_src emacs-lisp
(delete-selection-mode t)
(global-display-line-numbers-mode t)
(column-number-mode)
(savehist-mode)
#+end_src

Emacs 27 comes with fast current-line highlight functionality, but it can produce some visual feedback in ~vterm~ buffers, so we only activate it in programming or text modes.

#+begin_src emacs-lisp
(require 'hl-line)
(add-hook 'prog-mode-hook #'hl-line-mode)
(add-hook 'text-mode-hook #'hl-line-mode)
#+end_src

Emacs is super fond of littering filesystems with backups and autosaves, since it was built with the assumption that multiple users could be using the same Emacs instance on the same filesystem. This was valid in 1980. It is no longer the case.

#+begin_src emacs-lisp
(setq
make-backup-files nil
auto-save-default nil
create-lockfiles nil)
#+end_src

By default, Emacs stores any configuration you make through its UI by writing ~custom-set-variables~ invocations to your init file, or to the file specified by ~custom-file~. Though this is convenient, it's also an excellent way to cause aggravation when the variable you keep trying to modify is being set in some ~custom-set-variables~ invocation. We can disable this by mapping it to a temporary file. (I used to map this to ~/dev/null~, but this started causing a bunch of inane save dialogues.)

#+begin_src emacs-lisp
(setq custom-file (make-temp-name "/tmp/"))
#+end_src

However, because Emacs stores theme-safety information in that file, we have to disable the warnings entirely. This is not particularly secure, but if someone has uploaded malicious code to MELPA inside a theme, I have bigger problems. (Besides, Emacs is not a secure system, and I see no need to try overmuch to make it one.)

#+begin_src emacs-lisp
(setq custom-safe-themes t)
#+end_src

Don't copy this to your config. This just prevents inexplicable failures from elpa.

#+begin_src emacs-lisp
(setq package-check-signature nil)
#+end_src

There are a great many keybindings that are actively hostile, in that they are bound to useless or obsolete functions that are really easy to trigger accidentally. (The lambda is because ~unbind-key~ is a macro.)

#+begin_src emacs-lisp
(defun pt/unbind-bad-keybindings ()
"Remove unhelpful keybindings."
(-map (lambda (x) (unbind-key x)) '("C-x C-f" ;; find-file-read-only
"C-x C-d" ;; list-directory
"C-z" ;; suspend-frame
"C-x C-z" ;; again
"" ;; pasting with mouse-wheel click
"" ;; text scale adjust
"" ;; ditto
"s-n" ;; make-frame
"s-t" ;; ns-popup-font-panel
"s-p" ;; ns-print-buffer
"C-x C-q" ;; read-only-mode
)))
#+end_src

These libraries are helpful to have around when writing little bits of elisp, like the above. You can't possibly force me to remember the difference between the ~mapcar~, ~mapc~, ~mapcan~, ~mapconcat~, the ~cl-~ versions of some of the aforementioned, and ~seq-map~. I refuse. ~shut-up~ is good for noisy packages.

#+begin_src emacs-lisp
(use-package s)
(use-package dash :config (pt/unbind-bad-keybindings))
(use-package shut-up)
#+end_src

Emoji don't work on Emacs versions < 27 (aside from the Mitsuharu Yamamoto emacs-mac port). However, we can just do this.

#+begin_src emacs-lisp
(set-fontset-font "fontset-default" 'unicode "Apple Color Emoji" nil 'prepend)
#+end_src

In the name of avoiding RSI, which has become a feared nemesis, I bind ~C-h~ to backwards-delete-char, as per the macOS keybindings. But this means I have to rebind the keys that I actually use for help purposes.

#+begin_src emacs-lisp
(bind-key* "C-h" #'backward-delete-char)
(bind-key* "M-h" #'backward-delete-word)
(bind-key* "C-c C-h k" #'describe-key)
(bind-key* "C-c C-h f" #'describe-function)
(bind-key* "C-c C-h m" #'describe-mode)
(bind-key* "C-c C-h v" #'describe-variable)
(bind-key* "C-c C-h l" #'view-lossage)
#+end_src

Emacs can jump between header files and implementation files, or implementations and tests, as needed.

#+begin_src emacs-lisp
(bind-key "s-" #'ff-find-related-file)
(bind-key "C-c a f" #'ff-find-related-file)
#+end_src

Searching should be done with isearch, for UI purposes.

#+begin_src emacs-lisp
(bind-key "C-s" #'isearch-forward-regexp)
(bind-key "C-c s" #'isearch-forward-symbol)
#+end_src

The out-of-the-box treatment of whitespace is unfortunate, but fixable.

#+begin_src emacs-lisp
(add-hook 'before-save-hook #'delete-trailing-whitespace)
(setq require-final-newline t)
(bind-key "C-c q" #'fill-paragraph)
(bind-key "C-c Q" #'set-fill-column)

(defun pt/indent-just-yanked ()
"Re-indent whatever you just yanked appropriately."
(interactive)
(exchange-point-and-mark)
(indent-region (region-beginning) (region-end))
(deactivate-mark))

(bind-key "C-c I" #'pt/indent-just-yanked)
#+end_src

Emacs instances started outside the terminal do not pick up ssh-agent information unless we use keychain-environment.
Note to self: if you keep having to enter your keychain password on macOS, make sure this is in .ssh/config:

#+begin_src
Host *
UseKeychain yes
#+end_src

#+begin_src emacs-lisp
(use-package keychain-environment
:config
(keychain-refresh-environment))
#+end_src

Emacs is also in love with showing you its NEWS file; it's bound to like four different keybindings. Overriding the function makes it a no-op. You might say… no news is good news. For that matter, we can elide more GNU agitprop.

#+begin_src emacs-lisp
(defalias 'view-emacs-news 'ignore)
(defalias 'describe-gnu-project 'ignore)
(defalias 'describe-copying 'ignore)
#+end_src

Undo has always been problematic for me in Emacs. I used to use ~undo-tree-mode~ but it's been unmaintained for some time. I'm giving ~vundo~ a shot for the time being.

#+begin_src emacs-lisp
(use-package vundo
:diminish
:bind* (("C-c _" . vundo))
:custom (vundo-glyph-alist vundo-unicode-symbols))
#+end_src

I define a couple of my own configuration variables with ~defvar~, and no matter how many times I mark the variable as safe, it warns me every time I set it in the ~.dir-locals~ file. Disabling these warnings is probably (?) the right thing to do.

#+begin_src emacs-lisp
(setq enable-local-variables :all)
#+end_src

By default, Emacs wraps long lines, inserting a little icon to indicate this. I find this a bit naff. What we can do to mimic more modern behavior is to allow line truncation by default, but also allow touchpad-style scrolling of the document.

#+begin_src emacs-lisp
(setq mouse-wheel-tilt-scroll t
mouse-wheel-flip-direction t)
(setq-default truncate-lines t)
#+end_src

By default, Emacs ships with a nice completion system based on buffer contents, but inexplicably cripples its functionality by setting this completion system to ignore case in inserted results. Absolutely remarkable choice of defaults.

#+begin_src emacs-lisp
(use-package dabbrev
:bind* (("C-/" . #'dabbrev-completion))
:custom
(dabbrev-check-all-buffers t)
(dabbrev-case-replace nil))

;; TODO: I want to use the fancy-dabbrev package everywhere,
;; but it uses popup.el rather than read-completion, and
;; I don't like how quickly it operates on its inline suggestions
#+end_src

I'm trying to use some abbrevs to help with tedious patterns like checking ~if err == nil~ in Go.

#+begin_src emacs-lisp
(add-hook 'go-mode-hook #'abbrev-mode)
(setq abbrev-suggest t)
#+end_src

I never want to quit if readme.org is in a bad state. This warns me should I accidentally do so.

#+begin_src emacs-lisp
(defun check-config ()
"Warn if exiting Emacs with a readme.org that doesn't load."
(or
(ignore-errors (org-babel-load-file "~/.config/emacs/readme.org"))
(y-or-n-p "Configuration file may be malformed: really exit?")))

(push #'check-config kill-emacs-query-functions)
#+end_src

** Visuals

It's a mystery why Emacs doesn't allow colors by default in its compilation buffer, but ~fancy-compilation~ addresses that (and ensures the background color is set to something dark so that programs that make assumptions about its colors don't break).

#+begin_src emacs-lisp
(use-package fancy-compilation :config (fancy-compilation-mode))
#+end_src

Emacs looks a lot better when it has a modern monospaced font and VSCode-esque icons, as well as smooth scrolling.

#+begin_src emacs-lisp
(set-face-attribute 'default nil :font "Menlo-13")
(set-face-attribute 'variable-pitch nil :font "SF Mono-12")

(let ((installed (package-installed-p 'all-the-icons)))
(use-package all-the-icons)
(unless installed (all-the-icons-install-fonts)))

(use-package all-the-icons-dired
:after all-the-icons
:hook (dired-mode . all-the-icons-dired-mode))
#+end_src

Every Emacs window should, by default occupy all the screen space it can.

#+begin_src emacs-lisp
(add-to-list 'default-frame-alist '(fullscreen . maximized))
#+end_src

Window chrome both wastes space and looks unappealing. (This is actually pasted into the first lines of my Emacs configuration so I never have to see the UI chrome, but it is reproduced here for the sake of people who might be taking this configuration for a spin themselves.)

#+begin_src emacs-lisp
(when (window-system)
(tool-bar-mode -1)
(scroll-bar-mode -1)
(tooltip-mode -1)
(pixel-scroll-mode))

(when (eq system-type 'darwin)
(setq ns-auto-hide-menu-bar t))
#+end_src

I use the [[https://github.com/hlissner/emacs-doom-themes][Doom Emacs themes]], which are gorgeous. I sometimes also use Modus Vivendi, the excellent new theme that now ships with Emacs.

#+begin_src emacs-lisp
(use-package doom-themes
:config
(let ((chosen-theme 'doom-material-dark))
(doom-themes-visual-bell-config)
(doom-themes-org-config)
(setq doom-challenger-deep-brighter-comments t
doom-challenger-deep-brighter-modeline t
doom-rouge-brighter-comments t
doom-ir-black-brighter-comments t
modus-themes-org-blocks 'gray-background
doom-dark+-blue-modeline nil)
(load-theme chosen-theme)))
#+end_src

Most major modes pollute the modeline, so we pull in diminish.el to quiesce them.

#+begin_src emacs-lisp
(use-package diminish
:config
(diminish 'visual-line-mode))
#+end_src

The default modeline is pretty uninspiring, and ~mood-line~ is very minimal and pleasing. With a bit of elbow grease, it can be convinced to show the project-relative file name.

#+begin_src emacs-lisp
(defun pt/project-relative-file-name (include-prefix)
"Return the project-relative filename, or the full path if INCLUDE-PREFIX is t."
(letrec
((fullname (if (equal major-mode 'dired-mode) default-directory (buffer-file-name)))
(root (project-root (project-current)))
(relname (if fullname (file-relative-name fullname root) fullname))
(should-strip (and root (not include-prefix))))
(if should-strip relname fullname)))

(use-package mood-line
:config
(defun pt/mood-line-segment-project-advice (oldfun)
"Advice to use project-relative file names where possible."
(let
((project-relative (ignore-errors (pt/project-relative-file-name nil))))
(if
(and (project-current) (not org-src-mode) project-relative)
(propertize (format "%s " project-relative) 'face 'mood-line-buffer-name)
(funcall oldfun))))

(advice-add 'mood-line-segment-buffer-name :around #'pt/mood-line-segment-project-advice)
(mood-line-mode))
#+end_src

Highlighting the closing/opening pair associated with a given parenthesis is essential. Furthermore, parentheses should be delimited by color. I may be colorblind, but it's good enough, usually.

#+begin_src emacs-lisp
(use-package rainbow-delimiters
:disabled
:hook ((prog-mode . rainbow-delimiters-mode)))
#+end_src

It's nice to have the option to center a window, given the considerable size of my screen.

#+begin_src emacs-lisp
(use-package centered-window
:custom
(cwm-centered-window-width 180))
#+end_src

Compilation buffers should wrap their lines.

#+begin_src emacs-lisp
(add-hook 'compilation-mode-hook 'visual-line-mode)
#+end_src

URLs should be highlighted and linkified.

#+begin_src emacs-lisp
(global-goto-address-mode)
#+end_src

*** Tree-sitter

As part of my day job, I hack on the [[https://tree-sitter.github.io][~tree-sitter~]] parsing toolkit. Pleasingly enough, the parsers generated by ~tree-sitter~ can be used to spruce up syntax highlighting within Emacs: for example, highlighting Python with ~emacs-tree-sitter~ will correctly highlight code inside format strings, which is really quite useful. Note that for this to work you have to add the tree-sitter ELPA server.

#+begin_src emacs-lisp
(shut-up
(use-package tree-sitter
:config (global-tree-sitter-mode))

(use-package tree-sitter-langs))
#+end_src

*** Tabs

For some reason ~centaur-tabs~ has stopped working. I'm keeping the config around in case I ever figure out why. But for now we're using the (fairly lackluster) builtin ~tab-line-mode.~

#+begin_src emacs-lisp
(use-package centaur-tabs
:config
(centaur-tabs-mode t)
:custom
(centaur-tabs-set-icons t)
(centaur-tabs-show-new-tab-button nil)
(centaur-tabs-set-close-button nil)
(centaur-tabs-enable-ido-completion nil)
(centaur-tabs-gray-out-icons t)

:bind
(("s-{" . #'centaur-tabs-backward)
("s-}" . #'centaur-tabs-forward)))
#+end_src

** Text manipulation

Any modern editor should include multiple-cursor support. Sure, keyboard macros would suffice, sometimes. Let me live. I haven't yet taken advantage of many of the ~multiple-cursors~ commands. Someday.

#+begin_src emacs-lisp
(use-package multiple-cursors
:bind (("C-c C-e m" . #'mc/edit-lines)
("C-c C-e d" . #'mc/mark-all-dwim)))
#+end_src

The ~fill-paragraph~ (~M-q~) command can be useful for formatting long text lines in a pleasing matter. I don't do it in every document, but when I do, I want more columns than the default *:70*.

#+begin_src emacs-lisp
(setq-default fill-column 135)
#+end_src
Textmate-style tap-to-expand-into-the-current-delimiter is very useful and curiously absent.

#+begin_src emacs-lisp
(use-package expand-region
:bind (("C-c n" . er/expand-region)))
#+end_src

Emacs's keybinding for ~comment-dwim~ is ~M-;~, which is not convenient to type or particularly mnemonic outside of an elisp context (where commenting is indeed ~;~). Better to bind it somewhere sensible. Also, it's nice to have a binding for ~capitalize-dwim~.

#+begin_src emacs-lisp
(bind-key* "C-c /" #'comment-dwim)
(bind-key* "C-c 0" #'capitalize-dwim)
#+end_src

~avy~ gives us fluent jump-to-line commands mapped to the home row.

#+begin_src emacs-lisp
(use-package avy
:bind (:map prog-mode-map ("C-'" . #'avy-goto-line))
:bind (:map org-mode-map ("C-'" . #'avy-goto-line))
:bind (("C-c l" . #'avy-goto-line)
("C-c j k" . #'avy-kill-whole-line)
("C-c j j" . #'avy-goto-line)
("C-c j h" . #'avy-kill-region)
("C-c j w" . #'avy-copy-line)
("C-z" . #'avy-goto-char)
("C-c v" . #'avy-goto-char)))

(use-package avy-zap
:bind (("C-c z" . #'avy-zap-to-char)
("C-c Z" . #'avy-zap-up-to-char)))
#+end_src

~iedit~ gives us the very popular idiom of automatically deploying multiple cursors to edit all occurrences of a particular word.

#+begin_src emacs-lisp
(shut-up (use-package iedit
:bind (:map iedit-mode-keymap ("C-h" . #'sp-backward-delete-char))
:bind ("C-;" . #'iedit-mode)))
#+end_src

I'm trying to learn how to take advantage of ~smartparens~, but it already provides a better editing experience.

#+begin_src emacs-lisp
(use-package smartparens
:bind (("C-(" . #'sp-backward-sexp)
("C-)" . #'sp-forward-sexp)
("C-c d w" . #'sp-delete-word)
("" . #'sp-backward-sexp)
("" . #'sp-forward-sexp)
("C-c C-(" . #'sp-up-sexp)
("C-c j s" . #'sp-copy-sexp)
("C-c C-)" . #'sp-down-sexp))
:config
(require 'smartparens-config)
(setq sp-show-pair-delay 0
sp-show-pair-from-inside t)
(smartparens-global-mode)
(show-smartparens-global-mode t)
;; (set-face-attribute 'sp-pair-overlay-face nil :background "#0E131D")
(defun indent-between-pair (&rest _ignored)
(newline)
(indent-according-to-mode)
(forward-line -1)
(indent-according-to-mode))

(sp-local-pair 'prog-mode "{" nil :post-handlers '((indent-between-pair "RET")))
(sp-local-pair 'prog-mode "[" nil :post-handlers '((indent-between-pair "RET")))
(sp-local-pair 'prog-mode "(" nil :post-handlers '((indent-between-pair "RET"))))
#+end_src

Emacs Lisp doesn't have namespaces, which can be ugly when hacking on libraries. Though Emacs 28 added a feature called "[[https://www.gnu.org/software/emacs/manual/html_node/elisp/Shorthands.html][shorthands]]" that mucks with the reader to desugar some specified prefixes (in ~Local Variables~ blocks) into longer equivalents, it's kind of silly specifying them per-file, when what I just want is to hide the common prefix in my function definitions and calls. ~nameless~ does that and provides a shortcut (~C-c C--~) to insert the prefix.

#+begin_src emacs-lisp
(use-package nameless
:custom
(nameless-private-prefix t))
#+end_src

I got used to a number of convenient TextMate-style commands.

#+begin_src emacs-lisp
(defun pt/eol-then-newline ()
"Go to end of line, then newline-and-indent."
(interactive)
(move-end-of-line nil)
(newline-and-indent))

(bind-key "s-" #'pt/eol-then-newline)
#+end_src

It's occasionally useful to be able to search a Unicode character by name. And it's a measure of Emacs's performance, when using native-comp and Vertico, that you can search the entire Unicode character space without any keystroke latency.

#+begin_src emacs-lisp
(bind-key "C-c U" #'insert-char)
#+end_src

** Quality-of-life improvements

We start by binding a few builtin commands to more-convenient keystrokes.

#+begin_src emacs-lisp
(defun pt/split-window-thirds ()
"Split a window into thirds."
(interactive)
(split-window-right)
(split-window-right)
(balance-windows))

(bind-key "C-c 3" #'pt/split-window-thirds)
#+end_src

Given how often I tweak my config, I bind ~C-c E~ to take me to my config file.

#+begin_src emacs-lisp
(defun open-init-file ()
"Open this very file."
(interactive)
(find-file "~/.config/emacs/readme.org"))

(bind-key "C-c E" #'open-init-file)
#+end_src

It's weird that Emacs doesn't come with a standard way to insert the current date.

#+begin_src emacs-lisp
(defun pt/insert-current-date ()
"Insert the current date (Y-m-d) at point."
(interactive)
(insert (shell-command-to-string "echo -n $(date +%Y-%m-%d)")))
#+end_src

Standard macOS conventions would have ~s-w~ close the current buffer, not the whole window.

#+begin_src emacs-lisp
(bind-key "s-w" #'kill-this-buffer)
#+end_src

One of Emacs's most broken UI decisions is to prompt for saving buffers that are marked as modified, even if their contents are the same as on disc. It's totally asinine that this doesn't work like it does everywhere else.

#+begin_src emacs-lisp
(defun pt/check-file-modification (&optional _)
"Clear modified bit on all unmodified buffers."
(interactive)
(dolist (buf (buffer-list))
(with-current-buffer buf
(when (and buffer-file-name (buffer-modified-p) (not (file-remote-p buffer-file-name)) (current-buffer-matches-file-p))
(set-buffer-modified-p nil)))))

(defun current-buffer-matches-file-p ()
"Return t if the current buffer is identical to its associated file."
(autoload 'diff-no-select "diff")
(when buffer-file-name
(diff-no-select buffer-file-name (current-buffer) nil 'noasync)
(with-current-buffer "*Diff*"
(and (search-forward-regexp "^Diff finished \(no differences\)\." (point-max) 'noerror) t))))

;; (advice-add 'save-some-buffers :before #'pt/check-file-modification)

;; (add-hook 'before-save-hook #'pt/check-file-modification)
;; (add-hook 'kill-buffer-hook #'pt/check-file-modification)
(advice-add 'magit-status :before #'pt/check-file-modification)
(advice-add 'save-buffers-kill-terminal :before #'pt/check-file-modification)
#+end_src

Emacs makes it weirdly hard to just, like, edit a file as root, probably due to supporting operating systems not built on ~sudo~. Enter the ~sudo-edit~ package.

#+begin_src emacs-lisp
(use-package sudo-edit)
#+end_src

Dired needs a couple customizations to work in a sensible manner.

#+begin_src emacs-lisp
(setq
;; I use exa, which doesn't have a --dired flag
dired-use-ls-dired nil
;; Why wouldn't you create destination directories when copying files, Emacs?
dired-create-destination-dirs 'ask
;; Before the existence of this option, you had to either hack
;; dired commands or use the dired+ library, the maintainer
;; of which refuses to use a VCS. So fuck him.
dired-kill-when-opening-new-dired-buffer t
;; Update directory listings automatically (again, why isn't this default?)
dired-do-revert-buffer t
;; Sensible mark behavior
dired-mark-region t
)

(use-package dired-recent :config (dired-recent-mode))
#+end_src

Emacs has problems with very long lines. ~so-long~ detects them and takes appropriate action. Good for minified code and whatnot.

#+begin_src emacs-lisp
(global-so-long-mode)
#+end_src

It's genuinely shocking that there's no "duplicate whatever's marked" command built-in.

#+begin_src emacs-lisp
(use-package duplicate-thing
:init
(defun pt/duplicate-thing ()
"Duplicate thing at point without changing the mark."
(interactive)
(save-mark-and-excursion (duplicate-thing 1))
(call-interactively #'next-line))
:bind (("C-c u" . pt/duplicate-thing)
("C-c C-u" . pt/duplicate-thing)))
#+end_src

#+begin_src emacs-lisp
(require 're-builder)
(setq reb-re-syntax 'string)
#+end_src

Vim comes with support for incrementing and decrementing numbers at point. Shame that Emacs doesn't. But fixable.

#+begin_src emacs-lisp
(use-package evil-numbers
:bind ("C-c a 1" . #'evil-numbers/inc-at-pt))
#+end_src

We need to support reading large blobs of data for LSP's sake.

#+begin_src emacs-lisp
(setq read-process-output-max (* 1024 1024)) ; 1mb
#+end_src

When I hit, accidentally or purposefully, a key chord that forms the prefix of some other chords, I want to see a list of possible completions and their info.

#+begin_src emacs-lisp
(use-package which-key
:diminish
:custom
(which-key-enable-extended-define-key t)
:config
(which-key-mode)
(which-key-setup-side-window-right))
#+end_src

#+begin_src emacs-lisp
(defun display-startup-echo-area-message ()
"Override the normally tedious startup message."
(message "Welcome back."))
#+end_src

Emacs has an ~executable-prefix-env~ command that adds a magic shebang line to scripts in interpreted languages. With a little cajoling, it can use ~env(1)~ instead of hardcoding the interpreter path, which is slightly more robust in certain circumstances. Furthermore, we can automatically chmod a file containing a shebang into executable mode.

#+begin_src emacs-lisp
(setq executable-prefix-env t)
(add-hook 'after-save-hook #'executable-make-buffer-file-executable-if-script-p)
#+end_src

The new ~context-menu-mode~ in Emacs 28 makes right-click a lot more useful. But for terminal emacs, it's handy to have the menubar at hand.

#+begin_src emacs-lisp
(context-menu-mode)
(bind-key "C-c C-m" #'tmm-menubar)
#+end_src

** Buffer management

I almost always want to default to a two-buffer setup.

#+begin_src emacs-lisp
(defun revert-to-two-windows ()
"Delete all other windows and split it into two."
(interactive)
(delete-other-windows)
(split-window-right))

(bind-key "C-x 1" #'revert-to-two-windows)
(bind-key "C-x !" #'delete-other-windows) ;; Access to the old keybinding.
#+end_src

~keyboard-quit~ sometimes isn't enough, especially if the minibuffer is open, so here's a beefed-up version.

#+begin_src emacs-lisp
(defun pt/abort ()
"Remove auxiliary buffers."
(interactive)
(ignore-errors (exit-recursive-edit))
(ignore-errors (ctrlf-cancel))
(popper-close-latest)
(call-interactively #'keyboard-quit))

(bind-key* "s-g" #'pt/abort)
#+end_src

Completion systems make ~kill-buffer~ give you a list of possible results, which isn't generally what I want.

#+begin_src emacs-lisp
(defun kill-this-buffer ()
"Kill the current buffer."
(interactive)
(pt/check-file-modification)
(kill-buffer nil)
)

(bind-key "C-x k" #'kill-this-buffer)
(bind-key "C-x K" #'kill-buffer)
#+end_src

Also, it's nice to be able to kill all buffers.

#+begin_src emacs-lisp
(defun kill-all-buffers ()
"Close all buffers."
(interactive)
(let ((lsp-restart 'ignore))
;; (maybe-unset-buffer-modified)
(delete-other-windows)
(save-some-buffers)
(let
((kill-buffer-query-functions '()))
(mapc 'kill-buffer (buffer-list)))))

(bind-key "C-c K" #'kill-all-buffers)
#+end_src

VS Code has a great feature where you can just copy a filename to the clipboard. We can write it in a more sophisticated manner in Emacs, which is nice.

#+begin_src emacs-lisp
(defun copy-file-name-to-clipboard (do-not-strip-prefix)
"Copy the current buffer file name to the clipboard. The path will be relative to the project's root directory, if set. Invoking with a prefix argument copies the full path."
(interactive "P")
(let
((filename (pt/project-relative-file-name do-not-strip-prefix)))
(kill-new filename)
(message "Copied buffer file name '%s' to the clipboard." filename)))

(bind-key "C-c p" #'copy-file-name-to-clipboard)
#+end_src

Normally I bind ~other-window~ to ~C-c ,~, but on my ultra-wide-screen monitor, which supports up to 8 buffers comfortably, holding that key to move around buffers is kind of a drag. Some useful commands to remember here are ~aw-ignore-current~ and ~aw-ignore-on~.

#+begin_src emacs-lisp
(use-package ace-window
:config
;; Show the window designators in the modeline.
(ace-window-display-mode)

:bind* (("C-<" . other-window) ("C-," . ace-window) ("C-c ," . ace-window))
:custom
(aw-keys '(?a ?s ?d ?f ?g ?h ?j ?k ?l) "Designate windows by home row keys, not numbers.")
(aw-background nil))
#+end_src

Emacs allows you to, while the minibuffer is active, invoke another command that uses the minibuffer, in essence making the minibuffer from a single editing action into a stack of editing actions. In this particular instance, I think it's appropriate to have it off by default, simply for the sake of beginners who don't have a mental model of the minibuffer yet. But at this point, it's too handy for me to discard. Handily enough, Emacs can report your current depth of recursive minibuffer invocations in the modeline.

#+begin_src emacs-lisp
(setq enable-recursive-minibuffers t)
(minibuffer-depth-indicate-mode)
#+end_src

It's useful to have a scratch buffer around, and more useful to have a key chord to switch to it.

#+begin_src emacs-lisp
(defun switch-to-scratch-buffer ()
"Switch to the current session's scratch buffer."
(interactive)
(switch-to-buffer "*scratch*"))

(bind-key "C-c a s" #'switch-to-scratch-buffer)
#+end_src

One of the main problems with Emacs is how many ephemeral buffers it creates. I'm giving ~popper-mode~ a try to see if it can stem the flood thereof.

#+begin_src emacs-lisp
(use-package popper
:bind* ("C-c :" . popper-toggle-latest)
:bind (("C-`" . popper-toggle-latest)
("C-\\" . popper-cycle)
("C-M-`" . popper-toggle-type))
:hook (prog-mode . popper-mode)
:config
(popper-mode +1)
(popper-echo-mode +1)
:custom
(popper-window-height 24)
(popper-reference-buffers '("\\*Messages\\*"
"Output\\*$"
"\\*Async Shell Command\\*"
"\\*rustic-compilation\\*"
help-mode
prodigy-mode
"magit:.\*"
"\\*deadgrep.\*"
"\\*eldoc.\*"
"\\*Codespaces\\*"
"\\*SCLang:PostBuffer\\*"
"\\*xref\\*"
"\\*org-roam\\*"
"\\*direnv\\*"
"\\*tidal\\*"
"\\*Checkdoc Status\\*"
"\\*Warnings\\*"
"\\*Go Test\\*"
"\\*Bookmark List\\*"
haskell-compilation-mode
compilation-mode
bqn-inferior-mode)))
#+end_src

** Org-mode

Even though my whole-ass blogging workflow is built around org-mode, I still can't say that I know it very well. I don't take advantage of ~org-agenda~, ~org-timer~, ~org-calendar~, ~org-capture~, anything interesting to do with tags, et cetera. Someday I will learn these things, but not yet.

#+begin_src emacs-lisp
(use-package org
:hook ((org-mode . visual-line-mode) (org-mode . pt/org-mode-hook))
:hook ((org-src-mode . display-line-numbers-mode)
(org-src-mode . pt/disable-elisp-checking))
:bind (("C-c o c" . org-capture)
("C-c o a" . org-agenda)
("C-c o A" . consult-org-agenda)
:map org-mode-map
("M-" . nil)
("M-" . nil)
("C-c c" . #'org-mode-insert-code)
("C-c a f" . #'org-shifttab)
("C-c a S" . #'zero-width))
:custom
(org-adapt-indentation nil)
(org-directory "~/txt")
(org-special-ctrl-a/e t)

(org-default-notes-file (concat org-directory "/notes.org"))
(org-return-follows-link t)
(org-src-ask-before-returning-to-edit-buffer nil "org-src is kinda needy out of the box")
(org-src-window-setup 'current-window)
(org-agenda-files (list (concat org-directory "/todo.org")))
(org-pretty-entities t)

:config
(defun pt/org-mode-hook ())
(defun make-inserter (c) '(lambda () (interactive) (insert-char c)))
(defun zero-width () (interactive) (insert "​"))

(defun pt/disable-elisp-checking ()
(flymake-mode nil))
(defun org-mode-insert-code ()
"Like markdown-insert-code, but for org instead."
(interactive)
(org-emphasize ?~)))

(use-package org-modern
:config (global-org-modern-mode)
:custom (org-modern-variable-pitch nil))

(use-package org-ref
:disabled ;; very slow to load
:config (defalias 'dnd-unescape-uri 'dnd--unescape-uri))

(use-package org-roam
:bind
(("C-c o r" . #'org-roam-capture)
("C-c o f" . #'org-roam-node-find)
("C-c o t" . #'org-roam-tag-add)
("C-c o i" . #'org-roam-node-insert)
("C-c o :" . #'org-roam-buffer-toggle))
:custom
(org-roam-directory (expand-file-name "~/Dropbox/txt/roam"))
(org-roam-completion-everywhere t)
(org-roam-v2-ack t)
:config
(org-roam-db-autosync-mode))

(use-package org-alert
:config (org-alert-enable)
:custom (alert-default-style 'osx-notifier))

(use-package ob-mermaid)
#+end_src

* Keymacs support

I recently acquired a [[https://keymacs.com][Keymacs A620N]], a reproduction of the [[https://deskthority.net/wiki/Symbolics_365407][Symbolics 365407]], from 1983. Though it's expensive, it's unquestionably the nicest keyboard I've ever used, given its vintage ALPS switches; of the keyboards I've used, only the keyboard.io comes close. It's big enough that it has a preposterous amount of function keys.

#+begin_src emacs-lisp
(bind-key "" #'other-window)
#+end_src

* IDE features
** Magit

Magit is one of the top three reasons anyone should use Emacs. What a brilliant piece of software it is. I never thought I'd be faster with a git GUI than with the command line, since I've been using git for thirteen years at this point, but wonders really never cease. Magit is as good as everyone says, and more.

#+begin_src emacs-lisp
(use-package magit
:diminish magit-auto-revert-mode
:diminish auto-revert-mode
:bind (("C-c g" . #'magit-status))
:custom
(magit-diff-refine-hunk t)
(magit-repository-directories '(("~/src" . 1)))
(magit-list-refs-sortby "-creatordate")
:config
(defun pt/commit-hook () (set-fill-column 80))
(add-hook 'git-commit-setup-hook #'pt/commit-hook)
(add-to-list 'magit-no-confirm 'stage-all-changes))
#+end_src

Magit also allows integration with GitHub and other such forges (though I hate that term).

#+begin_src emacs-lisp
(use-package forge
:after magit)
#+end_src

I'm trying out this git-status-in-the-fringe package, which looks fairly visually appealing.

#+begin_src emacs-lisp
(use-package diff-hl
:config
(global-diff-hl-mode)
(diff-hl-flydiff-mode)
(diff-hl-margin-mode)
(add-hook 'magit-pre-refresh-hook 'diff-hl-magit-pre-refresh)
(add-hook 'magit-post-refresh-hook 'diff-hl-magit-post-refresh)
:custom
(diff-hl-disable-on-remote t)
(diff-hl-margin-symbols-alist
'((insert . " ")
(delete . " ")
(change . " ")
(unknown . "?")
(ignored . "i"))))
#+end_src

The code-review package allows for integration with pull request comments and such.

#+begin_src emacs-lisp
(use-package emojify)

(use-package code-review
:custom
(forge-owned-accounts '(("patrickt" . nil)))
(code-review-auth-login-marker 'forge)
(code-review-fill-column 80)
(code-review-new-buffer-window-strategy #'switch-to-buffer-other-window)
:after (magit forge emojify)
:bind (:map forge-pullreq-section-map (("RET" . #'forge-browse-dwim)
("C-c r" . #'code-review-forge-pr-at-point)))
:bind (:map forge-topic-mode-map ("C-c r" . #'code-review-forge-pr-at-point))
:bind (:map code-review-mode-map (("C-c n" . #'code-review-comment-jump-next)
("N" . #'code-review-comment-jump-next)
("P" . #'code-review-comment-jump-previous)
("C-c p" . #'code-review-comment-jump-previous))))
#+end_src

** Project navigation

I prefer the built-in ~project.el~ to ~projectile~, but because ~projectile~ caches very aggressively, it's nice to use when on a TRAMP connection.

#+begin_src emacs-lisp
(use-package compile
:custom
(compilation-read-command nil "Don't prompt every time.")
(compilation-scroll-output 'first-error))

(use-package project
:pin gnu
:bind (("C-c k" . #'project-kill-buffers)
("C-c m" . #'project-compile)
("C-x f" . #'find-file)
("C-c F" . #'project-switch-project)
("C-c R" . #'pt/recentf-in-project)
("C-c f" . #'project-find-file))
:custom
;; This is one of my favorite things: you can customize
;; the options shown upon switching projects.
(project-switch-commands
'((project-find-file "Find file")
(magit-project-status "Magit" ?g)
(deadgrep "Grep" ?h)
(pt/project-run-vterm "vterm" ?t)
(project-dired "Dired" ?d)
(pt/recentf-in-project "Recently opened" ?r)))
(compilation-always-kill t)
(project-vc-merge-submodules nil)
)

(use-package projectile
:disabled
:custom (projectile-enable-caching t)
:config
(defun pt/find-file-dwim ()
(interactive)
(project-find-file))
;; (if (and buffer-file-name (file-remote-p buffer-file-name))
;; (progn
;; (projectile-mode t)
;; (projectile-find-file)
;; )
;; (project-find-file)))
:bind (("C-c f" . #'pt/find-file-dwim)))

(defun pt/recentf-in-project ()
"As `recentf', but filtering based on the current project root."
(interactive)
(let* ((proj (project-current))
(root (if proj (project-root proj) (user-error "Not in a project"))))
(cl-flet ((ok (fpath) (string-prefix-p root fpath)))
(find-file (completing-read "Find recent file:" recentf-list #'ok)))))
#+end_src

I wrote [[https://github.com/patrickt/codespaces.el][my first Emacs package]], which provides a nice ~completing-read~ interface to the ~gh~ command line tool, and that drops you into a Dired buffer over TRAMP upon selection.

#+begin_src emacs-lisp
(use-package codespaces
:ensure-system-package gh
:config
(codespaces-setup)
(setq vc-handled-backends '(Git)) ;; speeds EVERYTHING up
:bind (("C-c S" . #'codespaces-connect)))
#+end_src

** Completion and input

My journey through the various Emacs completion facilities has been long and twisty. I started with Helm, then spent several years using Ivy, and am now using Vertico, with the consult and marginalia packages to yield an interface that is nicer and faster than Ivy.

#+begin_src emacs-lisp
(use-package vertico
:ensure t
:demand
:config
(vertico-mode t)
(vertico-mouse-mode)
(set-face-attribute 'vertico-mouse nil :inherit nil)
(savehist-mode)
:custom
(vertico-count 22)
(vertico-cycle t)
:bind (:map vertico-map
("C-'" . vertico-quick-exit)
("C-c '" . vertico-quick-insert)
("" . exit-minibuffer)
("C-m" . vertico-insert)
("C-c SPC" . vertico-quick-exit)
("C-" . vertico)
("DEL" . vertico-directory-delete-char)))

(use-package consult
:bind* (("C-c r" . consult-recent-file))
:bind (("C-c i" . consult-imenu)
("C-c b" . consult-project-buffer)
("C-x b" . consult-buffer)
("C-c B" . consult-bookmark)
("C-c `" . flymake-goto-next-error)
("C-c h" . consult-ripgrep)
("C-c y" . consult-yank-pop)
("C-x C-f" . find-file)
("C-c C-h a" . describe-symbol)
)
:custom
(consult-narrow-key (kbd ";"))
(completion-in-region-function #'consult-completion-in-region)
(xref-show-xrefs-function #'consult-xref)
(xref-show-definitions-function #'consult-xref)
(consult-project-root-function #'deadgrep--project-root) ;; ensure ripgrep works
(consult-preview-key '(:debounce 0.25 any))
)

(use-package marginalia
:config (marginalia-mode))

(use-package orderless
:custom (completion-styles '(orderless basic)))

(use-package ctrlf
:config (ctrlf-mode))

(use-package prescient
:config (prescient-persist-mode))
#+end_src

Dumb-jump is pretty good at figuring out where declarations of things might be. I'm using it with C because I'm too lazy to set up true C LSP integration. It complains about being deprecated and recommends ~xref~ instead, which is all well and good except I don't want to bother with creating ~etags~ tables for projects. So we pull in the ~shut-up~ package to quiesce those warnings.

#+begin_src emacs-lisp
(use-package dumb-jump
:config
(defun pt/quietly-dumb-jump ()
(interactive)
(shut-up (call-interactively 'dumb-jump-go)))
:bind (("C-c J" . #'pt/quietly-dumb-jump)))
#+end_src

~embark~ is a cool package for discoverability. It provides ~embark-act~, which opens a contextual menu about the thing at point. In essence, it changes Emacs interactions from being verb-oriented (aka Lispy-functional) to noun-oriented (like our more OO languages). But it makes things like variable customization easy: no longer do I have to, when I want to tweak a variable name, figure out its name, copy-paste it, hit ~M-:~ and write ~(setq var whatever)~ myself. Just ~embark-act~, hit ~=~ (for assignment), and then I can type in the new value.

#+begin_src emacs-lisp
(use-package embark
:bind ("C-c e" . #'embark-act)
:bind ("C-" . #'embark-act))

(use-package embark-consult :after (embark consult))
(use-package embark-vc :after embark)
#+end_src

** Searching

deadgrep is the bee's knees for project-wide search, as it uses ~ripgrep~. I defer to the faster and live-previewing ~consult-ripgrep~, but sometimes deadgrep is more useful.

#+begin_src emacs-lisp
(use-package deadgrep
:ensure-system-package rg
:bind (("C-c H" . #'deadgrep)))
#+end_src

I remember the days before Emacs had real regular expressions. Nowadays, we have them, but the find-and-replace UI is bad. ~visual-regexp~ fixes this. I have this bound to an incredibly stupid keybinding because I simply do not want to take the time to catabolize/forget that particular muscle memory.

#+begin_src emacs-lisp
(use-package visual-regexp
:bind (("C-c 5" . #'vr/replace)))
#+end_src

** Autocomplete

After a long journey with ~company~, I've settled on just using the builtin completion-at-point facilities for autocomplete. The UI considerations afforded by Vertico make it even nicer than what Company offered, and consistently faster, too. Someday I want to look into a more aggressive inline autocompletion thing like VSCode supports, but the only thing I saw wasn't compatible with my philosophy regarding completions.

#+begin_src emacs-lisp
(bind-key* "C-." #'completion-at-point)
#+end_src

** Debugging

In Haskell, my language of choice, I rarely need a step-through debugger, as designs that minimize mutable state make it so printf debugging is usually all you need. (Haskell's unorthodox evaluation strategy, and its limited step-through debugging facilities, don't help either.) However, now that I'm writing Rust and Go at work, a step-through debugger is indicated.

#+begin_src emacs-lisp
(use-package dap-mode
:disabled
:bind
(:map dap-mode-map
("C-c b b" . dap-breakpoint-toggle)
("C-c b r" . dap-debug-restart)
("C-c b l" . dap-debug-last)
("C-c b d" . dap-debug))
:init
(require 'dap-go)
;; NB: dap-go-setup appears to be broken, so you have to download the extension from GH, rename its file extension
;; unzip it, and copy it into the config so that the following path lines up
(setq dap-go-debug-program '("node" "/Users/patrickt/.config/emacs/.extension/vscode/golang.go/extension/dist/debugAdapter.js"))
(defun pt/turn-on-debugger ()
(interactive)
(dap-mode)
(dap-auto-configure-mode)
(dap-ui-mode)
(dap-ui-controls-mode)
)
)
#+end_src

** LSP

Built-in ~xref~ and ~eldoc~ are powerful packages, though we pin them to GNU ELPA to pull in the latest versions.

#+begin_src emacs-lisp
(use-package xref
:pin gnu :ensure t
:custom (xref-auto-jump-to-first-xref t)
:bind (("s-r" . #'xref-find-references)
("C-" . #'xref-find-definitions)
("C-S-" . #'xref-find-references)
("C-" . #'xref-go-back)
("s-[" . #'xref-go-back)
("s-]" . #'xref-go-forward)))

(use-package eldoc
:pin gnu
:diminish
:bind ("s-d" . #'eldoc)
:custom
(eldoc-echo-area-prefer-doc-buffer t)
(eldoc-echo-area-use-multiline-p t))
#+end_src

Though I used ~lsp-mode~ for ages, in my old age I've grown happier with packages that try to do less, as they are in almost all cases faster and more reliable. ~eglot~ is such a mode. I add a few mouse-related keybindings in its mode map.

#+begin_src emacs-lisp
(use-package eglot
:hook ((go-mode . eglot-ensure)
(haskell-mode . pt/haskell-eglot-except-tidal)
(rust-mode . eglot-ensure)
)
:bind (:map eglot-mode-map
("C-" . #'xref-find-definitions)
("C-S-" . #'xref-find-references)
("C-c a r" . #'eglot-rename)
("C-c C-c" . #'eglot-code-actions))
:custom
(eglot-confirm-server-initiated-edits nil)
(eglot-autoshutdown t)
(eglot-send-changes-idle-time 0.1)
:config
(defvar pt/disable-haskell-here t)
(defun pt/haskell-eglot-except-tidal ()
(unless (or pt/disable-haskell-here (string-equal "tidal" (file-name-extension (buffer-file-name)))) (eglot-ensure)))
;; Eglot doesn't correctly unescape markdown: https://github.com/joaotavora/eglot/issues/333
(defun mpolden/gfm-unescape-string (string)
"Remove backslash-escape of punctuation characters in STRING."
;; https://github.github.com/gfm/#backslash-escapes
(replace-regexp-in-string "[\\\\]\\([][!\"#$%&'()*+,./:;<=>?@\\^_`{|}~-]\\)" "\\1" string))

(advice-add 'eglot--format-markup :filter-return 'mpolden/gfm-unescape-string)

(defun pt/add-eglot-to-prog-menu (old startmenu click)
"Add useful Eglot functions to the prog-mode context menu."
(let ((menu (funcall old startmenu click))
(identifier (save-excursion
(mouse-set-point click)
(xref-backend-identifier-at-point
(xref-find-backend)))))
(when identifier
(define-key-after menu [eglot-find-impl]
`(menu-item "Find Implementations" eglot-find-implementation
:help ,(format "Find implementations of `%s'" identifier))
'xref-find-ref))
menu))

(advice-add 'prog-context-menu :around #'pt/add-eglot-to-prog-menu)
)

(use-package consult-eglot
:config
(defun pt/consult-eglot ()
(interactive)
(let ((completion-styles '(emacs22)))
(call-interactively #'consult-eglot-symbols)))
:bind (:map eglot-mode-map ("s-t" . #'pt/consult-eglot)))
#+end_src

And lastly, the built-in ~flymake~ does a great job, and ~eglot~ builds upon it.

#+begin_src emacs-lisp
(use-package flymake
:config
(setq elisp-flymake-byte-compile-load-path load-path)
:hook ((emacs-lisp-mode . flymake-mode)))
#+end_src

* Haskell

Haskell is my day-to-day programming language, so I've tinkered with it a good deal. Featuring automatic ~ormolu~ or ~stylish-haskell~ invocation, as based on a per-project variable, so I can default to ~ormolu~ but choose ~stylish-haskell~ for the projects that don't.

#+begin_src emacs-lisp
(use-package haskell-mode
;; :custom
;; (haskell-compile-cabal-build-command (string-join haskell-compile-cabal-build-command " -funclutter-valid-hole-fits"))
:config
(defcustom haskell-formatter 'ormolu
"The Haskell formatter to use. One of: 'ormolu, 'stylish, nil. Set it per-project in .dir-locals."
:safe 'symbolp)

(defun haskell-smart-format ()
"Format a buffer based on the value of 'haskell-formatter'."
(interactive)
(cl-ecase haskell-formatter
('ormolu (ormolu-format-buffer))
('stylish (haskell-mode-stylish-buffer))
(nil nil)
))

(defun haskell-switch-formatters ()
"Switch from ormolu to stylish-haskell, or vice versa."
(interactive)
(setq haskell-formatter
(cl-ecase haskell-formatter
('ormolu 'stylish)
('stylish 'ormolu)
(nil nil))))

:bind (:map haskell-mode-map
("C-c a c" . haskell-cabal-visit-file)
("C-c a i" . haskell-navigate-imports)
("C-c m" . haskell-compile)
("C-c a I" . haskell-navigate-imports-return)
:map haskell-cabal-mode-map
("C-c m" . haskell-compile)))

(use-package haskell-snippets
:after (haskell-mode yasnippet)
:defer)
#+end_src

My statements about Haskell autoformatters have, in the past, attracted controversy, so I have no further comment on the below lines.

#+begin_src emacs-lisp
(use-package ormolu)
#+end_src

* vterm

The state of terminal emulation is, as a whole, a mess. Not just within Emacs, but across all of Unix. (To be fair, terminals are a fascinating study in backwards compatibility and generations upon generations of standards and conventions.) A recent bright spot has been libvterm, which, when integrated with Emacs's new dynamic module support, enables us to have a very, very fast terminal inside Emacs.

A thing I want to do someday is to write a framework for sending things like compile commands to a running vterm buffer with ~vterm-send-string~. I want a version of the ~compile~ command that sends that command to my current ~vterm~ buffer. That would be so badass.

#+begin_src emacs-lisp
(use-package vterm
:ensure-system-package cmake
:custom
(vterm-timer-delay 0.05)
:config
(defun pt/turn-off-chrome ()
(hl-line-mode -1)
;;(yascroll-bar-mode nil)
(display-line-numbers-mode -1))

(defun pt/project-run-vterm ()
"Invoke `vterm' in the project's root.

Switch to the project specific term buffer if it already exists."
(interactive)
(let* ((project (project-current))
(buffer (format "*vterm %s*" (consult--project-name (project-root project)))))
(unless (buffer-live-p (get-buffer buffer))
(unless (require 'vterm nil 'noerror)
(error "Package 'vterm' is not available"))
(vterm buffer)
(vterm-send-string (concat "cd " (project-root project)))
(vterm-send-return))
(switch-to-buffer buffer)))

:hook (vterm-mode . pt/turn-off-chrome))

(use-package vterm-toggle
:custom
(vterm-toggle-fullscreen-p nil "Open a vterm in another window.")
(vterm-toggle-scope 'project)
:bind (("C-c t" . #'vterm-toggle)
:map vterm-mode-map
("C-\\" . #'popper-cycle)
("s-t" . #'vterm) ; Open up new tabs quickly
("s-v" . #'vterm-yank)
("C-y" . #'vterm-yank)
("C-h" . #'vterm-send-backspace)
))
#+end_src

* Process management

~prodigy~ is a great and handsome frontend for managing long-running services. Since many of the services I need to run are closed-source, the calls to ~prodigy-define-service~ are located in an adjacent file. Unfortunately, ~prodigy~ doesn't really have any good support for managing Homebrew services. Maybe I'll write one, in my copious spare time.

#+begin_src emacs-lisp
(use-package prodigy
:bind (("C-c 8" . #'prodigy)
:map prodigy-view-mode-map
("$" . #'end-of-buffer))
:custom (prodigy-view-truncate-by-default t)
:config
(load "~/.config/emacs/services.el" 'noerror))
#+end_src

* Snippets

I grew up writing in TextMate, so I got extremely used to text-expansion snippets. I also think they're extremely underrated for learning a new language's idioms: one of the reasons I was able to get up to speed so fast with Rails (back in the 1.2 days) was because the TextMate snippets indicated pretty much everything you needed to know about things like ActiveRecord.

#+begin_src emacs-lisp
(use-package yasnippet
:defer 15 ;; takes a while to load, so do it async
:diminish yas-minor-mode
:config (yas-global-mode)
:custom (yas-prompt-functions '(yas-completing-prompt)))
#+end_src

* Other Languages

*** General-purpose

Rust is one of my favorite languages in the world.

#+begin_src emacs-lisp
(use-package rust-mode
:defer t
:custom
(rust-format-on-save t)
(lsp-rust-server 'rust-analyzer))

(use-package rustic
:bind (:map rustic-mode-map
("C-c a t" . rustic-cargo-current-test)
("C-c m" . rustic-compile))
:custom
(rustic-lsp-client 'eglot)
(rustic-format-on-save t))
#+end_src

I occasionally write Go, generally as a glue language to munge things together. I find certain aspects of its creators' philosophies to be repellent, but a language is more than its creators, and it's hard to argue with the success it's found in industry or the degree to which people find it easy to pick up.

Amusingly enough, the built-in ~go-remove-unused-imports~ function is useless to put in a save hook, because it requires that the file be saved first, and… yeah, you can imagine how that goes. Cheers to Rob Figueiredo, who wrote a function that uses the ~goimports~ tool like a normal tool would instead of the monstrosity that is ~go-remove-unused-imports~, which searches the current flymake buffer with a regex. 🙄

#+begin_src emacs-lisp
(use-package go-mode
:defer t
:config
(defun robfig/goimports ()
"Formats the current buffer according to the goimports tool."

(interactive)
(let ((tmpfile (make-temp-file "gofmt" nil ".go"))
(patchbuf (get-buffer-create "*Gofmt patch*"))
(errbuf (get-buffer-create "*Gofmt Errors*"))
(coding-system-for-read 'utf-8)
(coding-system-for-write 'utf-8))

(with-current-buffer errbuf
(setq buffer-read-only nil)
(erase-buffer))
(with-current-buffer patchbuf
(erase-buffer))

(write-region nil nil tmpfile)

;; We're using errbuf for the mixed stdout and stderr output. This
;; is not an issue because gofmt -w does not produce any stdout
;; output in case of success.
(if (zerop (call-process "goimports" nil errbuf nil "-w" tmpfile))
(if (zerop (call-process-region (point-min) (point-max) "diff" nil patchbuf nil "-n" "-" tmpfile))
(progn
(kill-buffer errbuf)
(message "Buffer is already gofmted"))
(go--apply-rcs-patch patchbuf)
(kill-buffer errbuf)
(message "Applied gofmt"))
(message "Could not apply gofmt. Check errors for details")
(gofmt--process-errors (buffer-file-name) tmpfile errbuf))

(kill-buffer patchbuf)
(delete-file tmpfile)))

(defun pt/go-specific-save-hook ()
(when (eq major-mode 'go-mode)
(gofmt-before-save)
(robfig/goimports)))
(add-hook 'before-save-hook #'pt/go-specific-save-hook))

(use-package go-snippets :defer t)

(defun fix-messed-up-gofmt-path ()
(interactive)
(setq gofmt-command (string-trim (shell-command-to-string "which gofmt"))))

;; Note to self: there's a really helpful set of movement commands
;; under C-c C-f in go mode.
(use-package gotest
:after go-mode
:bind (:map go-mode-map
("C-c a t" . #'go-test-current-test)
("C-c a T" . #'go-test-current-file)
("C-c a i" . #'go-import-add)))
#+end_src

Elm is a good language.

#+begin_src emacs-lisp
(use-package elm-mode
:hook ((elm-mode . elm-format-on-save-mode)
(elm-mode . elm-indent-mode)))
#+end_src

I don't write a lot of Python, but when I do I like to use the extremely opinionated ~black~ formatter.

#+begin_src emacs-lisp
(use-package blacken
:hook ((python-mode . blacken-mode)))
#+end_src

Some other miscellaneous languages that I don't write often but for which I need syntax highlighting, at least.

#+begin_src emacs-lisp
(use-package typescript-mode
:custom (typescript-indent-level 2))
(use-package csharp-mode :defer t)
(setq-default js-indent-level 2)
#+end_src

I'm trying to learn APL, because I've lost control of my life.

#+begin_src emacs-lisp
(use-package dyalog-mode :defer t)
#+end_src

I think enough people have taken potshots at JavaScript that I hardly need to add mine to the barrage. Let's just say that it's not an ideal language but we do our best.

#+begin_src emacs-lisp
(use-package js2-mode
:hook (js2-mode . js2-imenu-extras-mode)
:mode ("\\.js$" . js2-mode)
:ensure t
:custom
(js2-mode-assume-strict t)
(js2-warn-about-unused-function-arguments t)
)

(use-package xref-js2
:ensure t
:hook (js2-mode . pt/js-hook)
:custom
(xref-js2-search-program 'rg)
:config
(defun pt/js-hook ()
(add-hook 'xref-backend-functions #'xref-js2-xref-backend nil t)))
#+end_src

*** Music

#+begin_src emacs-lisp
(use-package tidal
:ensure t
:demand
:config
(defun pt/replace-char (chr)
(save-excursion (delete-char 1) (insert chr)))
(defun pt/toggle-hash-or-dollar ()
(interactive)
(cl-case (char-after)
(?# (pt/replace-char ?$))
(?$ (pt/replace-char ?#))))
(defun pt/tidal-see-output-no-select ()
"Show haskell output."
(interactive)
(save-current-buffer
(with-current-buffer tidal-buffer
(goto-char (point-max)))))
(defun pt/tidal-dwim ()
(interactive)
(popper-display-popup-at-bottom (get-buffer "*tidal*"))
(tidal-run-multiple-lines)
(pt/tidal-see-output-no-select))
(defun pt/hush ()
(interactive)
(tidal-send-string "quiet"))
(defun pt/new-tidal-file ()
"Create a new track."
(interactive)
(let* ((title (s-upcase (read-string "Enter song attribute: ")))
(date (shell-command-to-string "echo -n $(date +%Y%m%d)")))
(find-file (format "~/beats/%s%s.tidal" title date))))
:bind (:map tidal-mode-map
(("C-c c" . pt/tidal-dwim)
("C-c d" . pt/hush)
("C-c C-d" . pt/hush)
("C-c a v" . pt/see-output-no-select)
("s-3" . pt/toggle-hash-or-dollar)
)))

(add-to-list 'load-path "/Users/patrickt/Library/Application Support/SuperCollider/downloaded-quarks/scel/el")

(use-package sclang
:ensure nil
:demand
:load-path "/Users/patrickt/Library/Application Support/SuperCollider/downloaded-quarks/scel/el"
:mode ("\\.scd\\'" . sclang-mode)
:bind (:map sclang-mode-map
("C-c c" . pt/sclang-dwim)
("C-c d" . pt/hush)
("C-c C-d" . pt/hush))
:config
(setq sclang-show-workspace-on-startup nil)
(defun pt/sclang-hook ()
(setq-local imenu-generic-expression (list (list nil "^SynthDef..\\([A-z0-9]+\\)" 1))))
(defun pt/sclang-dwim ()
(interactive)
(popper-display-popup-at-bottom (get-buffer "*SCLang:PostBuffer*"))
(sclang-eval-defun))

(defun pt/sclang-var-to-namedcontrol ()
"Turn the selected variable into a NamedControl."
(interactive)
(backward-word)
(insert-char ?\\)
(forward-word)
(insert ".ar()")
(backward-char))
(defun pt/sclang-region-to-namedcontrol ()
"Turn the selected region into a NamedControl."
(interactive)
(unless mark-active (user-error "Mark the desired region first"))
(let ((name (read-string "Control name: ")))
(call-interactively #'kill-region)
(insert (format "\\%s.ar()" name))
(backward-char)
(call-interactively #'yank))))

(add-to-list 'load-path "/Users/patrickt/.config/emacs/pith")

;; an in-progress UI for Tidal + SuperCollider
(use-package pith
:load-path "/Users/patrickt/.config/emacs/pith"
:bind (("C-c x" . pith-dispatch)))

(use-package evil-numbers
:after tidal
:config
(defun pt/tidal-inc ()
(interactive)
(call-interactively #'evil-numbers/inc-at-pt)
(tidal-run-multiple-lines))
(defun pt/tidal-dec ()
(interactive)
(call-interactively #'evil-numbers/dec-at-pt)
(tidal-run-multiple-lines))
:bind (:map global-map
(("s-k" . pt/tidal-inc)
("s-j" . pt/tidal-dec))))

(use-package emms
:bind (:map dired-mode-map
("z" . pt/emms-play-at-dired-point)
("Z" . emms-stop)
("J" . pt/emms-next-then-play)
("K" . pt/emms-prev-then-play)
("s-k" . dired-up-directory)
("s-j" . dired-find-file))
:custom
(emms-info-asynchronously nil)
:config
(emms-all)
(define-emms-simple-player afplay '(file)
(regexp-opt '(".mp3" ".m4a" ".aac" ".wav"))
"afplay")
(setq emms-player-list `(,emms-player-afplay))
(defun pt/emms-play-at-dired-point ()
(interactive)
(shut-up (emms-play-file (dired-file-name-at-point))))
(defun pt/emms-next-then-play ()
(interactive)
(call-interactively #'dired-next-line)
(pt/emms-play-at-dired-point))
(defun pt/emms-prev-then-play ()
(interactive)
(call-interactively #'dired-previous-line)
(pt/emms-play-at-dired-point)))
#+end_src

*** Configuration

#+begin_src emacs-lisp
(use-package yaml-mode :defer t)
(use-package dockerfile-mode :defer t)
(use-package toml-mode :defer t)
(use-package dhall-mode)
(use-package terraform-mode :defer t)
#+end_src

I use Bazel for some Haskell projects.

#+begin_src emacs-lisp
(use-package bazel
:defer t
:config
(add-hook 'bazel-mode-hook (lambda () (add-hook 'before-save-hook #'bazel-mode-buildifier nil t)))
)
#+end_src

*** Interchange

#+begin_src emacs-lisp
(use-package protobuf-mode :defer t)
#+end_src

*** Markup

I generally use GitHub-flavored Markdown, so we default to that.

#+begin_src emacs-lisp
(use-package markdown-mode
:hook (gfm-mode . visual-line-mode)
:bind (:map markdown-mode-map ("C-c C-s a" . markdown-table-align))
:mode ("\\.md$" . gfm-mode))
#+end_src

Occasionally I need to edit Rails .erb templates, God help me.

#+begin_src emacs-lisp
(use-package web-mode
:custom (web-mode-markup-indent-offset 2)
:mode ("\\.html.erb$" . web-mode)
:mode ("\\.art$" . web-mode))
#+end_src

I usually use curly quotes when writing in markup languages, which ~typo-mode~ makes easy.

#+begin_src emacs-lisp
(use-package typo :defer t)
#+end_src

*** Shell

~fish~ is the only shell that doesn't make me want to defenestrate. The only time I use anything else is when I have to use TRAMP to connect to a codespace, in which case I need to use zsh, as ~fish~ is not POSIX-compliant.

#+begin_src emacs-lisp
(use-package fish-mode :defer t)
#+end_src

Emacs can be a really great editor for shell scripts, but it needs a little love first.

#+begin_src emacs-lisp
(setq sh-basic-offset 2
sh-basic-indentation 2)
#+end_src

* Miscellany

Being able to Google something I'm looking at is really nice.

#+begin_src emacs-lisp
(use-package google-this
:bind ("C-c G" . #'google-this))
#+end_src

Emacs can provide a nice interface for selecting ~make~ tasks.

#+begin_src emacs-lisp
(use-package makefile-executor
:bind ("C-c M" . makefile-executor-execute-project-target))
#+end_src

~just~ is a nice general-purpose ~make(1)~ replacement.

#+begin_src emacs-lisp
(use-package just-mode)
#+end_src

~restclient~ is a terrific interface for running HTTP requests against local or remote services.

#+begin_src emacs-lisp
(use-package restclient
:mode ("\\.restclient$" . restclient-mode))
#+end_src

~Dash~ is the foremost documentation browser for macOS.

#+begin_src emacs-lisp
(use-package dash-at-point
:bind ("C-c D" . dash-at-point))
#+end_src

TRAMP mode is excellent for editing files on a remote machine or Docker container, but it needs some TLC.

#+begin_src emacs-lisp
(require 'tramp)
(setq remote-file-name-inhibit-locks t)

;; Needs to be called from recentf's :init
;; todo: make this into a use-package invocation
(defun pt/customize-tramp ()

(setq tramp-default-method "ssh"
tramp-verbose 1
remote-file-name-inhibit-cache nil
tramp-use-ssh-controlmaster-options nil
tramp-default-remote-shell "/bin/bash"
tramp-connection-local-default-shell-variables
'((shell-file-name . "/bin/bash")
(shell-command-switch . "-c")))

(connection-local-set-profile-variables 'tramp-connection-local-default-shell-profile
'((shell-file-name . "/bin/bash")
(shell-command-switch . "-c")))
;;(add-to-list 'tramp-remote-path 'tramp-own-remote-path)

)

;; (lsp-register-client
;; (make-lsp-client :new-connection (lsp-stdio-connection "gopls")
;; :major-modes '(go-mode go-dot-mod-mode)
;; :language-id "go"
;; :remote? t
;; :priority 0
;; :server-id 'gopls-remote
;; :completion-in-comments? t
;; :library-folders-fn #'lsp-go--library-default-directories
;; :after-open-fn (lambda ()
;; ;; https://github.com/golang/tools/commit/b2d8b0336
;; (setq-local lsp-completion-filter-on-incomplete nil))))

#+end_src

By default, the list of recent files gets cluttered up with the contents of downloaded packages.

#+begin_src emacs-lisp
(use-package recentf
:pin gnu
:after dash
:init (pt/customize-tramp) ;; so that tramp urls work ok in recentf
:custom
;; (recentf-exclude (-concat recentf-exclude '("\\elpa"
;; "private/tmp" ; to avoid custom files
;; "txt/roam"
;; "type-break"
;; )))
(recentf-max-saved-items 50)
(recentf-max-menu-items 30)
:config (recentf-mode))
#+end_src

I use ~direnv~ to manage per-project environment variables. The Emacs direnv mode is quite sophisticated, automatically setting all relevant variables for you when you go in and out of a particular project.

#+begin_src emacs-lisp
(use-package direnv
:config (direnv-mode)
:custom (direnv-always-show-summary nil))
#+end_src

* Initial screen setup

#+begin_src emacs-lisp
(defun my-default-window-setup ()
"Called by emacs-startup-hook to set up my initial window configuration."

(split-window-right)
(other-window 1)
(find-file "~/txt/todo.org")
(other-window 1))

(add-hook 'emacs-startup-hook #'my-default-window-setup)
#+end_src

* Adios

If you made it this far, well, may your deity of choice bless you. If you don't use Emacs already, I hope I tempted you a little. If you do, I hope you learned a couple new tricks, just as I have learned so many tricks from reading dozens of other people's configs.

Au revoir.