https://github.com/pniedzielski/emacs.d
My Emacs Configuration, finally git-ified
https://github.com/pniedzielski/emacs.d
Last synced: 3 months ago
JSON representation
My Emacs Configuration, finally git-ified
- Host: GitHub
- URL: https://github.com/pniedzielski/emacs.d
- Owner: pniedzielski
- Created: 2021-08-18T06:18:44.000Z (almost 4 years ago)
- Default Branch: master
- Last Pushed: 2025-02-12T06:42:16.000Z (4 months ago)
- Last Synced: 2025-02-12T07:49:22.744Z (4 months ago)
- Language: Emacs Lisp
- Size: 308 KB
- Stars: 5
- Watchers: 3
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.org
Awesome Lists containing this project
README
#+TITLE: Emacs Configuration File
#+AUTHOR: Patrick Michael Niedzielski
#+EMAIL: [email protected]
#+DESCRIPTION: Literate Emacs configuration via Org-Mode* Org-mode Emacs Configuration
This org-mode file contains [[https://pniedzielski.net/][Patrick M. Niedzielski]]’s Emacs
configuration, written in a [[https://en.wikipedia.org/wiki/Literate_programming][Literate Programming]] style. The
configuration targets GNU Emacs git-master and, although
cross-platform, does not support any other version of GNU Emacs or any
other Emacs editor (such as XEmacs).This document is the result of declaring [[https://www.emacswiki.org/emacs/DotEmacsBankruptcy][=.emacs= bankruptcy]]
during 2016. At the time, my Emacs configuration was too large and
unwieldy to maintain. It had become difficult to do anything but add
more configuration to the end of the file; any modifications to
existing configuration ran a very high risk of breaking the entire
configuration file. The result was a long time lost to debugging my
text editor, which could have been more productively used on my own
projects. When this became too unbearable, I started anew.To make sure that I wouldn’t have to do this again, I decided on two
major pillars of my configuration file. First, and in fact primarily,
I would write it in a Literate Programming style. A big part of the
problem in my prior configuration was not knowing the thought process
that went into old configurations. Towards the end of my old
configuration file’s life, some of this was solved by good git commit
messages, but there was enough cruft that make this not a good
solution. Literate Programming allows me to write prose to describe
my intention with a particular piece of configuration, and to link
together different parts of configuration that need to be separate in
Emacs Lisp, but which make sense together in my prose. Second, I
would use John Wiegley’s [[https://github.com/jwiegley/use-package][use-package]] macro, which would make each
piece of the configuration code itself declarative. This would free
me from the minutiae of [[info:elisp#Autoload][Emacs’s autoload functionality]] and such.Through the years, my primary use of Emacs has shifted. In 2016, I
was primarily writing C++ and Perl code. Now, though, I use Emacs for
reading and writing academic works, and the little code I do write is
in Haskell. I have also needed to use this configuration file
different Debian machines (usually my personal machines) and Windows
boxes (usually work machines). Furthermore, as I’ve learned more
Emacs, the way I have used it has changed dramatically. I think it’s
a testament to the above two decisions that my configuration is still
easy to use and maintain, despite the quite major shifts in what it’s
been intended to do.Until 2021, my configuration was private, so I did not need to worry
about whether to keep secrets like passwords in the file or not. Now,
I am migrating the configuration to a public git repository, so others
can see what sorts of things I have in my configuration. I have
needed to do this carefully, so in the meantime this repository may
not have everything that my active configuration has. The ultimate
goal is to migrate entirely over to this configuration.** License
Copyright © 2016-2022, 2025, Patrick M. Niedzielski.The following license applies both to this Org file and to the
corresponding Emacs configuration file that can be generated from this
file (as well as any other derivative works).#+name: license
#+begin_src emacs-lisp :tangle no
;; This program is free software: you can redistribute it and/or
;; modify it under the terms of the GNU General Public License as
;; published by the Free Software Foundation, either version 3 of the
;; License, or (at your option) any later version.
;;
;; This program is distributed in the hope that it will be useful, but
;; WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
;; General Public License for more details.
;;
;; You should have received a copy of the GNU General Public License
;; along with this program. If not, see
;; .
#+end_src** How This Works
This Emacs configuration is based on [[http://orgmode.org/worg/org-contrib/babel/intro.][Org Babel]], which allows source
code blocks to live inside an org-mode document; the org-mode document
can then be *woven* into an output document format (such as HTML or
LaTeX) that describes the source code and *tangled* into an output
source file (in this case, Emacs Lisp) that can be run. Our actual
=init.el= file is a simple stub that tangles this org-mode file and
then executes it.The tangled file should have the license text and a note reminding us
to modify the org-mode file instead of the tangled source file.#+begin_src emacs-lisp :noweb yes :comments no
;;; configuration.el --- Emacs configuration file;; This source file has been generated from an org-mode file.
;; Modifications should be performed on the org-mode file!<>
#+end_src
** Tangle this file
Although load times don’t matter too much for us, since we use an
Emacs daemon, we can still save a bit of time on startup by compiling
the elisp we produce. One way to do this is to tangle and compile
this org file every time we save it. We’ll have to be careful,
because if ever the =.elc= file and the =.org= file get out of sync,
we’ll have some hard-to-track-down bugs.In order to tangle and compile the org file every time we save it, we
define a hook that runs every time a file is saved. If the file is
our configuration org file, we tangle it and compile it. In our
=init.el= file, we load the =.elc= file if it exists, and otherwise we
tangle and load the =.org= file.#+begin_src emacs-lisp
(defun pmn:tangle-dotfiles ()
"If the current file is this file, the code blocks are tangled."
(interactive)
(letrec ((org-file (expand-file-name "configuration.org"
user-emacs-directory))
(el-file (concat (file-name-sans-extension org-file)
".el")))
(when (equal (buffer-file-name) org-file)
(org-babel-tangle nil el-file)
(byte-compile-file el-file))))(add-hook 'after-save-hook #'pmn:tangle-dotfiles)
#+end_src* Package Installation and Dependencies
In this section, we set up the backbone of our configuration: Emacs’s
built-in =package.el=, and the =use-package= macro.** =package.el= Configuration
Most of our packages we install using =package.el=, which is bundled
with Emacs 24 and later. There are four major package repositories
for Emacs packages; we use the first three listed here.* ELPA :: The repository for packages with FSF-copyright
assignment.
* Non-GNU ELPA :: The repository for GNU-sanctioned packages without
copyright assignment.
* MELPA :: Contains the largest selection of pcakages and is
built directly from source in the package’s VCS.
* Marmalade :: Packages are uploaded by users and so tend to be
at stable (albeit old versions)We do not use Marmalade because it often has the same packages as
MELPA, only with older versions. This can sometimes lead to conflicts
that we want to avoid. So, we can set up the three package
repositories we plan to use.#+begin_src emacs-lisp
(require 'package)
(setq package-archives '(("gnu" . "http://elpa.gnu.org/packages/")
("nongnu" . "http://elpa.gnu.org/nongnu/")
("melpa" . "https://melpa.org/packages/")))
#+end_srcThen, we tell Emacs to prefer ELPA over Non-GNU ELPA over MELPA. I
have a lot more trust in ELPA than I do in MELPA, since the former
requires explicit versioned uploads. If it is ever an issue, I can
pin a specific package from MELPA. So far, though, that has never
come up.#+begin_src emacs-lisp
(setq package-archive-priorities '(("gnu" . 30)
("nongnu" . 20)
("melpa" . 10)))
#+end_srcAs of [2021-09-23 ĵaŭ], I’m trying out the Non-GNU ELPA repository.
I’m not sure if this will cause conflicts with MELPA or not, but for
now I’m prefering to use the official non-GNU package repository where
possible.It’s very useful to keep all our installed packages in a single place,
separate from any local Lisp code we have. This gives us the option
of deleting the installed packages whenever we want to reset our Emacs
state, and keeps our Emacs configuration directory tidy. Let’s put
them in the =elpa= directory under our Emacs configuration directory.#+begin_src emacs-lisp
(setq package-user-dir (concat user-emacs-directory "elpa"))
#+end_srcIt’s important for our configuration that packages are not initialized
until we have set up all the =use-package= invocations that declare
what packages we want to use. To do this, we tell =package.el= not to
activate any packages early on.#+begin_src emacs-lisp
(setq package-enable-at-startup nil)
#+end_srcFinally, we start up =package.el=.
#+begin_src emacs-lisp
(package-initialize)
#+end_src** =use-package= Configuration
Now that =package.el= is set up and ready to use, we’re ready to
configure =use-package=, which we use to automatically install the
packages we want and to track dependencies between them.
=use-package= provides us with a macro that centralizes all the
configuration for each package, and lets us state the conditions under
which we can load the package.Before using =use-package=, though, we need to make sure it’s
installed, or install it if it’s not already! We need to bootstrap by
using =package.el= to fetch and install =use-package= if it’s not
locally installed. While we’re at it, we pick up the package
=diminish=, which lets us control what packages are shown in the
modeline, and =bind-key=, which lets us bind keys more easily. Both
of these are integrated nicely into =use-package=.#+begin_src emacs-lisp :noweb yes :comments no
(unless (package-installed-p 'use-package)
(package-refresh-contents)
(package-install 'use-package)
(package-install 'diminish))<>
(require 'use-package)
(require 'use-package-ensure)
(require 'diminish)
(require 'bind-key)
#+end_srcBecause we can’t use =use-package= itself to configure =use-package=,
we’ll have a handful of loose configured options set up here at the
start. We chose to be a bit more verbose in what we output, so that
we can keep track of what is going on in the systemd journal. We also
make sure download packages by default if they aren’t already
installed (known in =use-package=-speak as /ensuring/). Finally, we
turn off the default of adding a ~-hook~ suffix to symbols that appear
in the ~:hook~ section of a ~use-package~ declaration; the default
means we can’t configure [[info:emacs#Hooks][abnormal hooks]] in the same way as normal
hooks.#+name: use-package-config
#+begin_src emacs-lisp :tangle no
(setq use-package-verbose t
use-package-expand-minimally nil
use-package-always-ensure t
use-package-hook-name-suffix nil)
#+end_srcFinally, to avoid unnecessary garbage collection during start up,
we’ll raise the (rather low) garbage collection and process buffering
thresholds. ([2021-09-07 mar]: This seems out of place here, and I
didn’t document why I chose this location in the configuration to put
these two statements. It might make sense to break it out later, or
at least move it somewhere else.)#+begin_src emacs-lisp
(setq gc-cons-threshold (* 50 1000 1000))
(setq read-process-output-max (* 1024 1024)) ; 1mb
#+end_src** Local Lisp Packages
There are still a few packages that are not on MELPA, which I have
installed locally (as it used to be, before =package.el=). I put
these packages in a subdirectory of my Emacs directory,
~.config/emacs/lisp/~. For each local package we load outside of
MELPA, we will need to add a directory to the ~load-path~ variable.
To make this easy, I add a variable called =pmn:local-lisp-directory=
that points to the right place.#+begin_src emacs-lisp
(setq pmn:local-lisp-directory (concat user-emacs-directory "lisp/"))
#+end_src** Native Compilation
Starting with version 28, Emacs includes functionality to compile Lisp
down to native code. While there has been byte-code compilation for a
long time, this still goes through the Lisp interpreter, which
introduces some delay—and since Emacs is mostly single-threaded, we
want to limit this delay as much as possible, so we can interact with
Emacs with less frustration. Native compilation (formerly GccEmacs),
lets you precompile Lisp code so it can run natively, and includes all
the optimizations of the GCC backend. This has had a noticeable
impact on the snappiness of my Emacs, especially when using larger
packages.Luckily, we don’t need to do much to make Emacs use native
compilation. However, there is one quite unfortunate default: when
packages are being asynchronously natively compiled, every warning
during compilation pops up a buffer that takes away input focus from
what I was doing, and sometimes screws up a carefully crafted window
layout. I don’t usually care about compilation warnings in code I
didn’t write, and there seems to be quite a lot of that in this
transition phase to native-comp Nirvana, so I ignore warnings during
these async compilations:#+begin_src emacs-lisp
(setq warning-minimum-level :error)
#+end_srcThis has made asynchronous native compilation so much smoother, and
this has the benefit of showing me only actual errors that I should be
concerned with.** Emacs Server
I usually run Emacs as a server on my systems, with emacsclients
connecting to the server. Let’s make sure to enable this
functionality.#+begin_src emacs-lisp
(require 'server)
#+end_src** XDG Directories
Emacs knows about the XDG Basedirs Specification, but unfortunately
the library is not loaded by default.#+begin_src emacs-lisp
(use-package xdg)
#+end_src** exec-path-from-shell
Finally, I have [[https://github.com/pniedzielski/dotfiles-ng][a lot of modifications to my =PATH= variable and
others]] that I want to import into Emacs, regardless of whether it was
started with systemd or not. To do this, we can use the
=exec-path-from-shell= package, and only initialize it when Emacs is
started as a daemon (non-interactively, so not from a shell).#+begin_src emacs-lisp
(use-package exec-path-from-shell
:config (when (daemonp)
(exec-path-from-shell-initialize)))
#+end_src* Global Configuration
This section describes some configuration options that are globally
important, but don’t really fit anywhere else.** User Configuration
Set my name and (public, personal) email address for whenever Emacs
needs it.#+begin_src emacs-lisp
(setq user-full-name "Patrick M. Niedzielski"
user-mail-address "[email protected]")
#+end_src** Consistent Configuration Storage
Originally, Emacs’s configuration was stored as the single dotfile
~$HOME/.emacs~, an elisp file directly under the user’s home
directory. Any program in Emacs that wanted to store additional
configuration or persistent data would need to make its own dotfile,
and there were no conventions on how to do that. Eventually, Emacs
started understanding ~$HOME/.emacs.d/init.el~ (and later the XDG
standard ~$HOME/.config/emacs/init.el~, which is what I use), giving
these packages a directory to store their own configuration and data.
Without any structure within this ~user-emacs-directory~, though, and
because of the legacy of old packages storing their configuration and
data right in the user’s home directory, in practice packages leave
their junk all over the place.However, most packages have a configuration variable that lets you
customize where the file goes (this is Emacs!). However, making sure
that each and every package’s variables are properly configured is
troublesome to say the least. It’s worth pulling in the [[https://github.com/emacscollective/no-littering][no-littering]]
package, which does this for us. This needs to come very early on in
our configuration, before other packages set their defaults.#+begin_src emacs-lisp
(use-package no-littering
:config (no-littering-theme-backups))
#+end_srcThis package stows as many files as it can under two directories
within the ~user-emacs-directory~—the ~etc~ subdirectory, for
configuration files, and the ~var~ subdirectory, for persistent data.
This is useful in the context of our own published Emacs
configuration, where (by default) ~etc~ files can be stored in the
repo and ~var~ files can be safely ignored.We additionally turn on a (poorly-named) function called
~no-littering-theme-backups~. This causes backups and other temporary
files to be written in one location, rather than spread across the
filesystem. This may not be safe, if you have sensitive data in them.
For me, I’d rather keep all my data in a single place than worry about
this issue.A useful interface provided by this package is the pair of
~no-littering-expand-etc-file-name~ and
~no-littering-expand-var-file-name~, which take a relative path and
returns a correctly stowed path for either configuration (the ~etc~
variant) or persistent data (the ~var~ variant).** Custom File
By default, Emacs modifies our ~init.el~ file to save customizations
made with the /Customize/ mode. I don’t want to mess up my ~init.el~
file, so we keep these customizations in a different file. I’ll first
tell Emacs where that file is, and then I’ll load any customizations
we had.#+begin_src emacs-lisp
(setq custom-file (no-littering-expand-etc-file-name "custom.el"))
(load custom-file)
#+end_src** Language Settings
I want to use Unicode by default, and UTF-8 is best on Unix ([[https://utf8everywhere.org/][and
everywhere]]).#+begin_src emacs-lisp
(set-language-environment "UTF-8")
(setq locale-coding-system 'utf-8)
(prefer-coding-system 'utf-8)
#+end_src** EasyPGP
[[http://epg.sourceforge.jp/][EasyPGP]], which is bundled with Emacs, lets us easily encrypt and
decrypt files with GPG. This is more or less transparent: you can
open a PGP encrypted file, edit the buffer as if it’s normal, and save
it back, encrypting it again. One thing to note is that, because
EasyPGP is bundled with Emacs, we don’t want to download it from the
package manager, so we are sure to set ~:ensure nil~.#+begin_src emacs-lisp
(use-package epa-file
:ensure nil
:config (epa-file-enable))
#+end_src** Auto Compression
Similarly, we want to use [[http://www.emacswiki.org/emacs/AutoCompressionMode][auto-compression-mode]] to allow us to
automatically compress and decompress files with ~gzip~ or ~bzip~. I
don’t know which package to use to store this configuration, so for
the moment I’ll just keep it loose.#+begin_src emacs-lisp
(auto-compression-mode 1)
(setq dired-use-gzip-instead-of-compress t)
#+end_src** Which Key?
There are /a lot/ of keybindings in Emacs, and there’s no way I can
remember them all. Frequently I remember a prefix of a long
keybinding, and then forget the remainder of the keybinding. The
~which-key~ package is a surprisingly nice solution to this, and made
me realize how many keybindings I will never quite remember. It
provides a minor mode that pops up a nice buffer listing all
keybinding continuations a short while after typing an incomplete
keybinding. This means I can type the start of a keybinding, wait a
second, and see all possible completions to that keybinding. This
moreover incentivizes me to keep my own keybindings nice and logically
organized.#+begin_src emacs-lisp
(use-package which-key
:diminish which-key-mode
:config
(which-key-mode))
#+end_src** Free keys
[[https://github.com/Fuco1/free-keys][~free-keys~]] allows me to see which keys are *not* bound in a
particular buffer. This is in some sense the opposite of [[*Which Key?][~which-key~]],
and is helpful for seeing which keybindings I have available for
binding. I need to configure this to also show me keybinding
opportunities with the Hyper key, under which I store some of my
keybindings.#+begin_src emacs-lisp
(use-package free-keys
:commands free-keys
:custom (free-keys-modifier . ("" "C" "M" "C-M" "H" "C-H")))
#+end_src** Passwords
I use [[https://www.passwordstore.org/][pass]] to manage my passwords, because in part because of how easy
it is to synchronize and update the passwords across devices with Git.
Emacs has nice integration with it as well. The =password-store=
package provides a programmatic interface to my pass database, which
lets me keep passwords out of this (public) configuration.#+begin_src emacs-lisp
(use-package password-store)
#+end_src* Movement
The benefits in movement that Emacs gives are probably its killer
feature as a text editor for me. While some people really customize
their editor a lot for keybindings and replacing functionality, I try
to use stock keybindings and built-in packages as much as possible.
That said, each configuration I have here is one that made my life
significantly better than before, so I don’t feel bad about moving
away from the stock functionality here.** Keybindings
There’s some basic movement functionality that I use quite frequently
when I’m writing documents: moving by paragraph and moving by page.
Because my keyboard has a hyper key, let’s bind more convenient
movement keys to them:#+begin_src emacs-lisp
(use-package lisp
:ensure nil
:bind (("H-f" . forward-paragraph)
("H-b" . backward-paragraph)
("C-H-f" . forward-page)
("C-H-b" . backward-page)))
#+end_src** Expand Region
[[https://github.com/magnars/expand-region.el][~expand-region~]] is a useful little package that expands the region by
semantic units: that is for prose, it selects first a word, then a
sentence, then a paragraph, and so forth; for code, it selects first a
token, then an sexpr, then a statement, and so forth. While there is
~M-@~ (~mark-word~) and others, which accomplish this more
immediately, having an interactive command has proven useful to me as
well. Furthermore, there is no ~mark-sentence~ command, which I find
very useful in editing prose, and ~expand-region~ makes this two
keystrokes.#+begin_src emacs-lisp
(use-package expand-region
:bind ("C-=" . er/expand-region))
#+end_src** Multiple Cursors
I used to be a big user of Emacs rectangle commands, especially for
inserting the same text (frequently spaces) on multiple lines. With
~cua-mode~’s visible rectangle highlighting, this was a very nice
workflow, using only built-in functionality. While I still use the
rectangle commands for some purposes, their primary utility for me was
replaced by the amazing ~multiple-cursors~ package, which lets you
insert what seem like multiple points, and do the same edit command at
each point. There are many modes of interacting with this, most of
which I haven’t explored in depth, but the one that’s by far the most
command and useful is the ~mc/edit-lines~ command, which I have bound
to ~C-c m c~. This inserts a cursor on each line in a region: the
mark is changed to a cursor, and every other line gets a cursor of its
own at the same column as the point.#+begin_src emacs-lisp
(use-package multiple-cursors
:bind (("C-c m c" . mc/edit-lines)))
#+end_src~multiple-cursors~ has some downsides, though, especially in modes
that override ~self-insert-command~ for various keys. Then, the mode
will ask if you’re sure you want to do the “unsafe” command at each
cursor, and that sometimes screws up what the command was actually
meant to do. This is annoying, and I would eventually like to figure
out if there’s a fix.Every now and again I see reference to a package called [[https://github.com/victorhge/iedit][~iedit~]], which
seems to be in the same vein as this package. I had heard that
~multiple-cursors~ had superseded it, looking on [2021-09-08 mer] I
found that it’s still updated. It might be worth looking into that as
a replacement to ~multiple-cursors~, if I ever find the time (and,
significantly, if it doesn’t have the same annoyance as I describe
above).* Editing
Even though I consider [[*Movement][movement]] the killer feature of Emacs as an
editor, the actual editing functionality of Emacs is also very useful.
You’ll find that some of these functions are also in other editors,
sometimes even better, but when composed with the effortless ability
to move about the buffer as you please, they provide for a beautiful
editing experience, both for composing prose and developing software.Before we get started, though, there are some basic, global settings
that I think Emacs got wrong. I want to set these to sane values
before anything else.First, we need to never use tabs. In general, tabs are evil for
indentation, but the way Emacs uses them (using tabs just as a
replacement for every 8 consecutive spaces, not using them
semantically) is even worse. We turn tabs indent off by default.
There are very few times when we’ll need them anyway, and it can be
turned back on locally to a project, a mode, or a buffer.#+begin_src emacs-lisp
(setq-default indent-tabs-mode nil)
#+end_srcSimilarly, in UNIX, all files should end with a newline. Emacs can
control this via the variable =require-final-newline=. While we can
tell Emacs to automatically add a newline on saving, on visiting, or
both, I feel a bit worried about this happening without my knowledge.
Although most often git will let me know that the final line was
modified, I’m not always in a git repo, or I may absentmindedly miss
that in the diff. As an extra line of defense, I tell Emacs to ask me
on any buffer that doesn’t have a final newline whether to add one or
not when I save that buffer. This way, I’m in full control.#+begin_src emacs-lisp
(setq-default require-final-newline 'ask)
#+end_srcAnother global truism is that lines should never have trailing
whitespace. This usually does nothing, and again, we can always turn
it off in those specific modes or buffers that require it (or,
alternatively, when we’re working with poorly crafted source files
already that have needless amounts of trailing whitespace—a red flag,
if ever there was one).However, it’s an unfortunate fact that certain automatically generated
Emacs buffers having rampant trailing whitespace (a red flag, if ever
there was one), including ~completing-read~ in the minibuffer. While
we could create a list of modes to turn this setting off, for the
specific problem of special Emacs buffers with trailing whitespace, it
appears the best cut is between /buffers I can edit/ and /buffers I
cannot/—or in other words programming and writing buffers on one hand,
and other buffers on the other. What we do, then, is turn
=show-trailing-whitespace= on only in =text-mode= and =prog-mode=.#+begin_src emacs-lisp
(add-hook 'text-mode-hook #'(lambda () (setq show-trailing-whitespace t)))
(add-hook 'prog-mode-hook #'(lambda () (setq show-trailing-whitespace t)))
#+end_src** Writing
I spend most of my time in Emacs nowadays reading and writing prose,
so the most important configurations in this document relate to
reading and writing.The sections that follow are mostly centered around ~text-mode~ and
modes that derive from it.*** Text
~text-mode~ is probably my most-used major mode, directly and via its
derivative modes.One of the most useful aspects of ~text-mode~ is its understanding of
prose structure. The following keybindings (cognate with the line
movement keybindings) skip around the buffer on a sentence-by-sentence
basis:* ~M-a~ (~backward-sentence~)
* ~M-e~ (~forward-sentence~)
* ~M-k~ (~kill-sentence~)
* ~C-x~ (~backward-kill-sentence~)See [[info:emacs#Sentences][the *Sentences* section of the Emacs manual]] for more information.
By default, though, these commands determine sentence boundaries using
punctuation followed by two spaces. In fact, this is how I type
myself, so this default works well for prose I write. I seem to be in
the minority, though, and whenever I’m working with text written by
someone else, it gets very annoying when the sentence commands don’t
see any sentence boundaries. This is worse than the alternative,
where too many false positives are given for possible sentences. We
could tell Emacs to need only a single space for separating sentences,
as below:#+begin_src emacs-lisp :tangle no
(add-hook 'text-mode-hook
(lambda () (setq sentence-end-double-space nil)))
#+end_srcHowever, there is a problem with this: it deletes the double spaces in
my own documents when I reflow paragraphs. Yuck. For the moment, I
don’t have a good solution to this. I think I’d rather get annoyed
when working with the anemic text documents that lack double spacing,
more than have Emacs muck up my own documents. Maybe someday, I’ll
write a bit of code to automatically detect whether to set
~sentence-end-double-space~ on a buffer-by-buffer basis, à la [[http://mbork.pl/2014-10-28_Single_vs_double_spaces][this
solution by Marcin Borkowski]]. I like the DWIMness of it, but there
are enough open threads to this solution that, again, I think I would
find it more annoying than helpful.*** Filling Paragraphs
Most of the time, I want my paragraphs in plain text formats to be
/filled/, the Emacs jargon for having hard line-breaks before a
certain column. It’s mostly easy to hit the ~M-q~ and refill the
current paragraph as I type, but it’s even easier to let Emacs
automatically break the line at the right point. To do this, I add a
hook to ~text-mode~:#+begin_src emacs-lisp
(add-hook 'text-mode-hook #'auto-fill-mode)
#+end_src*** Typo Mode
[[https://github.com/jorgenschaefer/typoel][~typo.el~]] is a package that contains two minor modes, ~typo-mode~ and
~typo-global-mode~. The former is what we’re interested in: when
enabled, ASCII typographic characters are replaced with Unicode
characters while typing. This is very useful when editing documents,
especially now that we’re in a post-ASCII age. ~typo-global-mode~ is
also useful: it enables a ~C-c 8~ hierarchy to mirror the built-in
~C-x 8~ hierarchy, which allows us to access much of the same
functionality in program modes as we do in text modes, when needed.#+begin_src emacs-lisp
(use-package typo
:hook (text-mode-hook . typo-mode)
:config (typo-global-mode 1)
(setq-default typo-language "English"))
#+end_src~typo.el~ supports converting quotes to their language-specific
surface realizations: for English, that looks like “this”, whereas for
Esperanto, that looks like „this“. It would be great to automatically
detect which to use based on the ispell dictionary, but for the moment
I use English as a default, and manually change the quote style when
needed.Indeed, there is similar functionality built-in to Emacs in the form
of [[info:emacs#Quotation Marks][~electric-quote-mode~]], but it only replaces quotation marks, and
only to their English typographic equivalent. I find myself using
dashes and ellipses quite often, and as well it isn’t infrequent that
I edit texts in other languages, with different typographic
traditions. ~typo.el~ works out of the box.*** Spell Checking
I am terrible at spelling—much more terrible than a recovering
Indo-Europeanist should be. Flyspell marks my spelling errors
on-the-fly, underlining in red words that aren’t in my system’s
English dictionary. This does yield a significant number of false
positives, but it’s good enough to catch most of my spelling mistakes.We turn on flyspell in modes that are derived from ~text-mode~, and we
turn on flyspell only in comments for modes that are derived from
~prog-mode~.#+begin_src emacs-lisp
(use-package flyspell
:diminish flyspell
:hook ((text-mode–hook . flyspell-mode)
(prog-mode-hook . flyspell-prog-mode)))
#+end_srcMy systems tend to have Esperanto as their default language (for
displaying the interface), but most of the text I write is in English
(obviously). The ~auto-dictionary~ package detects which language the
text I’m writing is in and sets the spell-check dictionary to that
language. We’ll turn this on whenever we have flyspell on.#+begin_src emacs-lisp
(use-package auto-dictionary
:after flyspell
:hook (flyspell-mode-hook . auto-dictionary-mode))
#+end_src*** Dictionary
In addition to spell checking, it’s very useful to be able to lookup
the definitions of words in some text. Luckily, Emacs has a built-in
package to search through [[https://datatracker.ietf.org/doc/html/rfc2229][RFC 2229 DICT servers]], called
~dictionary.el~. By default, this will first query a locally
installed DICT server, and if that fails, it will query the default
[[https://dict.org][dict.org]], which aggregates a handful of free sources, including
WordNet.Ideally for me, either the OED or Wiktionary would have DICT
interfaces. The former has amazing entries, and the latter has
English definitions for words in many foreign languages.
Unfortunately, I haven’t been able to find a working gateway for
either of them—[[https://hewgill.com/dict/][the only gateway that I could]] find seems to be
inaccessible. Similarly, the only local dictionaries I can find on
Debian are of lower quality than the ones on dict.org. Perhaps
someday I will install a local dictionary server, but for the moment I
will rely on an internet connection.What this means is that we need to tell ~dictionary.el~ to just use
dict.org rather than first trying localhost, and warning us that there
is no DICT server running on the machine. At the same time, we set up
a keybinding that is very similar to the keybinding for correcting
spelling, but prefixed with ~C-c~.#+begin_src emacs-lisp
(use-package dictionary
:custom
(dictionary-server "dict.org"
"Don’t use localhost dictionary preferentially")
:bind
("C-c M-TAB" . dictionary-search))
#+end_src*** Org Mode
It’s hard to know where to put my [[http://orgmode.org/][Org]] configuration, because of how
deeply Org has inserted its tendrils into everything I do. But, I
suppose, at its heart, Org mode is a markup language and an Emacs
package built on top of that language. Furthermore, I think the
unifying theme of Org mode is one of /writing plain text/; even
without the (very useful) Emacs functions built on top of the Org
markup, they are enabled almost entirely by the plain text, freely
modifiable, and human-readable nature of Org mode.#+begin_src emacs-lisp :noweb yes
(use-package org
:mode "\\.org'"
:init
(setq org-catch-invisible-edits 'smart
org-src-window-setup 'other-window
org-indirect-buffers-display 'other-window
org-src-fontify-natively t
org-highlight-latex-and-related '(native script entities)
org-todo-keywords
'((sequence "TODO(t)" "NEXT(n)" "|" "DONE(d)")
(sequence "DELEGATED(e@/!)" "WAITING(w@/!" "HOLD(h@/!)" "|" "CANCELED(c@/!)" "MEETING(m)"))
org-todo-keyword-faces '(("TODO" :foreground "red" :weight bold)
("NEXT" :foreground "DeepSkyBlue2" :weight bold)
("DONE" :foreground "forest green" :weight bold)
("WAITING" :foreground "orange" :weight bold)
("DELEGATED" :foreground "orange" :weight bold)
("HOLD" :foreground "magenta" :weight bold)
("CANCELED" :foreground "forest green" :weight bold)
("MEETING" :foreground "red" :weight bold)))
<>
:config
<>
:bind (("C-c a" . org-agenda)
("C-c l" . org-store-link)
("C-c !" . org-time-stamp-inactive)
(:map org-mode-map
("C-c n n" . org-id-get-create))))
#+end_srcLet’s take these configurations in turn.
**** Work Tracking
*Note as of [2022-05-06 ven 21:53]*: I haven’t used this in quite a
while, but because I’m slowly moving over my org configuration into a
git repository, I’m keeping it here for the moment.[[info:org#Clocking Work Time][Org mode includes clocking functionality]] that tracks how much time you
spend working on a particular project in your org agenda. I think the
primary use case of this is for contractors who bill by the hour;
having a detailed list of hours spent on each task is simply necessary
for such a job. I am not at all in that position. However, some time
during the early part of 2021, I experimented with tracking my tasks
for a week, to see how much time I spent on different parts of a paper
I was working on. I found this very informative, but moreover, I
found that having something how much time I work on each task made me
focus on each one a bit more, rather than flitting to and fro between
tasks—a bad habit I’ve picked up over the course of the COVID-19
pandemic. I thought this might help me better estimate how much time
I need to spend on something to complete it, but it didn’t help this
at all. It seems to be a purely psychological trick, and part of me
hopes I’ll be able to grow out of it.And so, I am by no means religious about this, and it strikes me that
giving this any more than the minimum amount of thought would be
_counterproductive_ rather than helpful. Luckily, the configuration
for this is dead simple. We teach Emacs to remember both our time
history and the time of any currently running clock across restarts
and also set up hooks that actually perform the persistence.#+begin_src emacs-lisp :tangle no :noweb-ref org-clock-config
(setq org-clock-persist t)
(org-clock-persistence-insinuate) ;weird name, org-mode
#+end_srcAt this point, work tracking works very simply. All the relevant
keybindings live under the ~C-c C-x~ prefix (along with seemingly half
of org’s keybindings). These are the ones I use:- ~C-c C-x C-i~ ::
Start a work clock on the heading under point.
- ~C-u C-c C-x C-i~ ::
Start a work clock on a recent task.
- ~C-c C-x C-o~ ::
Stop a work clock.***** Pomodoro Technique
[[https://www.pomodorotechnique.com/][The Pomodoro Technique]] divides tasks into roughly 25 minute blocks
(“pomodoros”), with 5 minute breaks in between. I prefer to work in
longer blocks when I can, without distraction, but when I don’t have
the motivation or interest to maintain that level of focus, dividing
work into shorter blocks of time can do wonders. It’s a mental trick,
and should be reserved for those times when you need mental tricks to
get something done. It won’t magically make you more productive.If you want to do this, just use a phone timer or such. I don’t think
it’s worth integrating this into Emacs specifically *except* if you
use work timing as above. And even then, it is only a minor
convenience—otherwise there’s really no point. But, if you do use
work tracking in org mode, you can use the [[https://github.com/marcinkoziej/org-pomodoro][org-pomodoro]] package, whose
singular benefit is to start a work clock at the beginning of a
pomodoro, and stop the work clock at the end of the pomodoro.The one configuration we make is to send Pomodoro alerts as system
alerts. Otherwise, I am liable to miss them.#+begin_src emacs-lisp
(use-package org-pomodoro
:after alert
:commands (org-pomodoro) ;only load when we call this
:config
(add-to-list 'alert-user-configuration
'(((:category . "org-pomodoro")) libnotify nil)))
#+end_src**** Exporting
I didn’t used to have much use for Org mode exporting: my main use of
Org mode was for writing, reading, and using the package landscape
built-up around Org mode. However, I’ve found more and more that
exporting has a place in my workflow.For example, although most of my writing for publication is in LaTeX,
I’m trying out doing more and more handouts in Org mode, and then
exporting to LaTeX. This is still somewhat of a manual process,
wherein I check and manually modify the LaTeX document, copying in
some of the preamble commands I need outside of Org mode. As I get
more confident with the Org export functionality, I will start to put
more of this preamble content in configuration here.Also, with [[*Org-roam][org-roam-ui]], I’m consuming a lot more Org content in
exported HTML format as well. It’s becoming more and more important
to have export set up well, in multiple different formats.Let’s get started with our export configuration.
#+begin_src emacs-lisp :tangle no :noweb-ref org-latex-export
(setq org-latex-compiler "lualatex")
#+end_src**** Org-roam
As of [2021-07-04 dim], I’m trying out the new version of org-roam.
The basic concepts behind org-roam have changed pretty dramatically,
as well as its interface, so the configuration for this is very
different. At first, I needed to use Quelpa to download this version,
but it has since become the default version on MELPA.By default, org-roam sets up no global keybindings, but because it’s
such an important part of my workflow, I choose to set up some of my
own. They will live under the ~C-c n~ prefix.#+name: org-roam-keybinds
#+begin_src emacs-lisp :tangle no
("C-c n l" . org-roam-buffer-toggle)
("C-c n f" . org-roam-node-find)
("C-c n c" . org-roam-capture)
("C-c n t" . org-roam-tag-add)
("C-c n a" . org-roam-alias-add)
("C-c n r" . org-roam-ref-add)
("C-c n d T" . org-roam-dailies-capture-today)
("C-c n d D" . org-roam-dailies-capture-date)
("C-c n d t" . org-roam-dailies-goto-today)
("C-c n d d" . org-roam-dailies-goto-date)
#+end_srcFurthermore, when we’re in an org-mode buffer, we might want to insert
some links to org-roam notes, so we add some mode-local keybindings:#+name: org-roam-mode-keybinds
#+begin_src emacs-lisp :tangle no
(:map org-mode-map
("C-c n i" . org-roam-node-insert)
("C-c n I" . org-roam-node-insert-immediate))
#+end_src#+begin_src emacs-lisp :noweb yes
(use-package org-roam
:after org
:custom
(org-roam-directory "~/Dokumentoj/org/notes/")
(org-roam-dailies-directory "daily/")
:bind
<>
<>
:init
(setq org-roam-v2-ack t) ; Don’t display a warning every time we load org-roam
:config
(setq org-roam-capture-templates
'(("d" "default" plain "%?"
:if-new (file+head "%<%Y%m%d%H%M%S>-${slug}.org"
"#+title: ${title}\n#+date: %T\n")
:unnarrowed t)
("p" "person" plain "%?"
:if-new (file+head "${slug}.org"
"#+title: ${title}\n#+date: %T\n#+filetags: person\n")
:unnarrowed t))
org-roam-dailies-capture-templates
'(("d" "default" entry
"* %?\n%U\n"
:if-new (file+head "%<%Y-%m-%d>.org"
"#+title: %<%Y-%m-%d>\n"))
("e" "event" entry
"* %?\n%T\n"
:if-new (file+head "%<%Y-%m-%d>.org"
"#+title: %<%Y-%m-%d>\n"))))
(setq org-roam-mode-section-functions
(list #'org-roam-backlinks-section
#'org-roam-reflinks-section
#'org-roam-unlinked-references-section))
(org-roam-db-autosync-enable))(use-package org-roam-protocol :ensure nil :after org-roam :demand t)
#+end_srcNext, we can set up org-roam-ui, which replaces the old
org-roam-server. This gives us an interactive view of the connections
between our org-roam nodes, which gives a bit more of a global view of
my repository than the default org-roam commands are really able to.
Although until recently this was not on MELPA, as of [2021-11-14 dim],
I can use normal package downloads to get this package.#+begin_src emacs-lisp :tangle no
(use-package org-roam-ui
:after (org-roam websocket simple-httpd)
:config
(setq org-roam-ui-sync-theme t
org-roam-ui-retitle-ref-nodes t
org-roam-ui-follow t
org-roam-ui-update-on-save t
org-roam-ui-open-on-start t))
#+end_src*** Markdown
Markdown support isn’t included by default in Emacs, and Emacs doesn’t
recognize files with the =.markdown= and =.md= extensions. We use
[[http://jblevins.org/projects/markdown-mode/][markdown-mode]] by Jason Blevins and associate these extensions with it.#+begin_src emacs-lisp
(use-package markdown-mode
:mode "\\.md\\'"
:custom
(markdown-asymmetric-header t
"Only put header markup at the start of the line.")
(markdown-enable-highlighting-syntax t
"Use ==this== for highlighter support.")
(markdown-enable-html t
"Font lock for HTML tags and attributes.")
(markdown-enable-math t
"Font lock for inline LaTeX mathematics.")
(markdown-fontify-code-blocks-natively t
"Use the right major mode for font locking in source blocks.")
(markdown-bold-underscore nil
"Use **this** instead of __this__ for boldface.")
(markdown-italic-underscore t
"Use _this_ instead of *this* for italics.")
(markdown-list-indent-width 2
"Indent by two spaces for a list."))
#+end_srcMost of the configuration for this mode is in either font-locking or
in using some of the convenience features the mode gives. Most of my
consumption of Markdown documents is in reading the unrendered markup
code, so I care a lot about making it look nice, and making it easier
to read. This is despite the fact that I write in =org-mode= more
frequently these days (even though I think Markdown is an
easier-to-read markup language), so I don’t always remember the
specifics of using Markdown mode. That said, I want to make it as
easy to write beautiful looking Markdown documents that are as
easy-to-read as possible, and these configurations help enable that.Most of the keybindings for Markdown mode are under the =C-c C-s=
namespace. The ones I (should) use most often are:| Action | Keybinding |
|---------------------------------------+------------------------|
| Insert heading | =C-c C-s H= |
| Bold | =C-c C-s b= |
| Italic | =C-c C-s i= |
| Inline code | =C-c C-s c= |
| Blockquote | =C-c C-s q= |
| Code block | =C-c C-s C= |
| Edit code block | =C-c '= |
| Insert link | =C-c C-l= |
| Insert image | =C-c C-i= |
| Follow link | =C-c C-o= |
| Insert footnote | =C-c C-s f= |
| Jump between reference and definition | =C-c C-d= |
| New list item | =M-RET= |
| Promote/demote list item | =C-c =/== |
| Horizontal rule | =C-c C-s -= |These were distilled from the [[https://leanpub.com/markdown-mode/read][Guide to Markdown Mode]].
** Programming
*** Tree Sitter
For historical reasons, a lot of the Emacs programming functionality
is hacked up around regular expressions, which aren’t powerful enough
to know much about the underlying structure inherent in the code. One
of the ways around this is to use =tree-sitter=, an external project
that parses code from a large number of programming languages into
trees. Emacs can then read these parses and more correctly highlight,
navigate, and modify our code buffers.To do this, we need to enable integration between =tree-sitter= and
Emacs. Currently, this requires an external package, which we have to
install separately, and which integrates highlighting, navigation, and
modification with the results of =tree-sitter=’s parsing.#+begin_src emacs-lisp
(use-package tree-sitter
:hook (tree-sitter-after-on-hook . tree-sitter-hl-mode)
:config (global-tree-sitter-mode))
(use-package tree-sitter-langs)
#+end_src*** LSP
While [[*Tree Sitter][Tree Sitter]] adds syntactic information to Emacs’s editing
capabilities, the Language Server Protocol (LSP) adds semantic
information to it. LSP is another client-server protocol, where Emacs
is the client, and each programming language project has its own
server that Emacs starts. This server uses information derived from
linters, or the compiler toolchain, or other semantic analysis
software, to enable easy refactoring, code navigation, and completion,
among many other things. In short, it gives Emacs many of the tools
of modern IDEs, without needing to implement them separately for each
Emacs major mode.As is to be expected, there are two different packages for Emacs that
implement an LSP client.* [[https://github.com/joaotavora/eglot][eglot]] :: Of the two, this feels the most “Emacs” to me. It’s
lightweight, and it doesn’t radically change the interaction with
Emacs, which I like—in its own words, it “stays out of your way”.
Furthermore, it integrates with many built-in packages, like xref,
eldoc, and Flymake.* [[https://emacs-lsp.github.io/lsp-mode/][lsp-mode]] :: This is the batteries-included LSP client out of the
two. It feels like it integrates with every package ever,
especially ones that I don’t use. It can be a bit heavyweight,
and some of the UI elements can feel a bit intrusive when I don’t
want them.For a long time I used lsp-mode, because I wasn’t able to get eglot
working. However, as I started documenting this section of my
configuration file on [2022-03-07 lun], I decided to give eglot
another try.Eglot works by starting a server using the current buffer major mode,
scoped local to the project (using =project.el=). Information from
the server is exposed mostly through other packages, mirroring the
built-in functionality for writing Emacs LISP code in particular.* finding symbols can be done with =xref=,
* diagnostics can be given using =flymake=,
* symbol documentation can be given using =eldoc= and optionally
=markdown=,* completion is taken care of by =completion-at-point= and
=company=, and* code snippets can be inserted by =yasnappet=.
LSP does provide some additional functionality that isn’t exactly
replicated by existing Emacs packages, such as* =eglot-rename= to rename symbols across the entire project,
* =eglot-format= to automatically format a piece of code, and
* =eglot-code-actions= (and some predefined shortcuts
=eglot-code-action-X= for action /X/) to modify the code on a
language-by-language basis.Configuring eglot is trivial:
#+begin_src emacs-lisp
(use-package eglot)
#+end_srcPerhaps I may eventually set up keybindings for some of those actions,
but I don’t use them frequently enough at the moment to worry about
that.*** Haskell Mode
The language I spend most of my time in as of [2022-03-07 lun] is
Haskell. Although I’m a systems programmer at heart, Haskell lets me
write code for my research that is very close to the mathematical
formalisms I’m working with.First of all, the biggest configuration we can do is turn on [[*LSP][LSP]] with
eglot. Otherwise, Haskell Mode comes with quite a number of minor
modes we can turn on, many of which are useful. We set up our mode
hook to turn on the ones we want.#+begin_src emacs-lisp
(use-package haskell-mode
:hook (haskell-mode-hook . eglot-ensure)
(haskell-mode-hook . haskell-unicode-input-method-enable)
(haskell-mode-hook . haskell-indentation-mode)
(haskell-mode-hook . haskell-decl-scan-mode)
(haskell-mode-hook . interactive-haskell-mode))
#+end_srcThere are some keybindings in Haskell Mode that are very useful, but
that I have a particularly bad time remembering:* =M-x haskell-mode-format-imports= (=C-c C-,=) sorts and aligns the
imports at the top of a file.* Reading
The time I spend in Emacs now is dominated by reading documents of
various kinds: academic articles, books, conference proceedings, and
so forth. Unfortunately, this is exactly a place where the built-in
functionality of Emacs does not shine (which is not the biggest
surprise, as it is at its heart a text editor). The configurations in
this section to me represent the most disheartening, in that they move
me the furthest away from what Emacs out-of-the-box is designed to do.** PDF Tools
While Emacs does come with a built-in document reading package, called
[[info:emacs#Document View][DocView]], it is a very unpleasant experience to use it. It can read a
wide variety of different document types (including Microsoft Word
documents), but where possible, I would like to use better tools.[[https://github.com/vedang/pdf-tools][PDF Tools]] is a significantly better tool to read PDFs in Emacs. It
functions as a drop-in replacement for DocView, so any package that
opens a PDF can open it in PDF Tools without issue, and some of the
basic keybindings for navigation are shared with DocView, making the
switch to (or in my case, from) DocView more pleasant. Furthermore,
it is fully maintained (writing as of [2021-10-28 ĵaŭ]), and hopefully
more features and bugfixes will be on the way.The biggest downside of PDF Tools is that it relies on a server
program called ~epdfinfo~ that communicates with Emacs and provides it
with enough information to handle the complex searching and annotation
functionality that the Emacs frontend provides. This server program
must be compiled before the package can be used, which relies on the
system having a compiler and development tooling, as well as the
required libraries for PDF. We will set it up to build everything we
need when we load a PDF for the first time.#+begin_src emacs-lisp
(use-package pdf-tools
:mode "\\.pdf\\'"
:magic ("%PDF" . pdf-view-mode)
:config
(require 'pdf-tools)
(require 'pdf-view)
(require 'pdf-misc)
(require 'pdf-occur)
(require 'pdf-util)
(require 'pdf-annot)
(require 'pdf-info)
(require 'pdf-isearch)
(require 'pdf-history)
(require 'pdf-links)
(setq pdf-view-continuous nil)
:functions
(pdf-tools-disable-cursor pdf-tools-advice-evil-refresh-cursor))
#+end_src** Bibliography
Being able to read PDFs is not enough. I also need to be able to
organize my corner of the literature, so I can effectively search
through it, take notes and process it, and then cite it in my own
writing. There are many tools to do this, even within Emacs, but I
would like a system that isn’t exclusive to Emacs: I should be able to
easily and effectively add entries and read documents without Emacs
(or without any other particular program).The natural solution is a plaintext BibLaTeX file and a directory of
PDF files. I could have some hierarchy to these PDF files, but if my
BibLaTeX file is good enough, it can serve as an index to them, much
as notmuch is an index to my mail. To that end, I choose a directory
[[~/Biblioteko][~Biblioteko~]] under my home directory, which stores a BibLaTeX file
[[~/Biblioteko/biblioteko.bib][~biblioteko.bib~]] and a loose collection of PDFs, each named after
their BibLaTeX key. This makes it easy to store in a git annex
repository, and thus to sync the library across each computer. For
managing this directory and the files within, I have settled on two
packages: Ebib, for editing and organizing the collection, and
ivy-bibtex, for searching through and citing entries.*** BibTeX mode
Because our whole enterprise is based on Bib(La)TeX, we need to make
sure we set up Emacs’s BibTeX mode up properly. The most import thing
is for me is to make sure it automatically generates keys in format I
find most useful. While some people may find this key format verbose,
I find that it makes the backing source code for these citing
documents easier to read directly. While the code to make this happen
is a bit verbose, my end goal is to get keys like
=chomsky1957syntactic= for Chomsky’s (1957) /Syntactic Structures/.#+begin_src emacs-lisp
(use-package bibtex
:ensure nil
:config
(setq bibtex-autokey-names 1
bibtex-autokey-names-stretch 1
bibtex-autokey-additional-names "etal"
bibtex-autokey-name-separator ""bibtex-autokey-name-year-separator ""
bibtex-autokey-year-length 4
bibtex-autokey-year-title-separator ""
bibtex-autokey-titleword-first-ignore '("the" "a" "if" "and" "an")
bibtex-autokey-titleword-length 30
bibtex-autokey-titlewords 1bibtex-autokey-use-crossref t
bibtex-autokey-edit-before-use t
bibtex-autokey-before-presentation-function #'downcase))
#+end_src*** Org-cite
Org-cite is a relatively recent addition to org-mode, which lets me
include bibliography citations in my org files. Although most serious
things I write in LaTeX, setting this up is still very useful for my
org-roam notes.For the moment, I do very little configuration of this, just setting
where my bibliography is:#+begin_src emacs-lisp
(use-package oc ; org-cite is oc.el :(
:ensure nil
:after org
:custom (org-cite-global-bibliography '("~/Biblioteko/biblioteko.bib")))
#+end_src*** Ebib
[[https://joostkremers.github.io/ebib/][Ebib]] is a very nice replacement for software programs like [[https://www.mendeley.com/download-reference-manager/][Mendeley]],
[[https://www.zotero.org/][Zotero]], and [[https://www.jabref.org/][JabRef]]. Each of these had some downside:* [[https://www.zotero.org/support/kb/mendeley_import#mendeley_database_encryption][Mendeley started encrypting its local database of PDFs after
Zotero wrote an importer for Mendeley users.]] How is this okay?
* Zotero felt very heavyweight when I tried it briefly, and
integrating it with BibLaTeX was a pain. Furthermore, its
apparent killer feature, the ability to download bibliographic
information, neither was unique to it nor even gave particularly
high quality results.
* JabRef was nice, but it’s still a quite heavy Java program for a
task so fundamentally simple, hogging more RAM than it has any
right to.Ebib has served as a good replacement for each of these programs.
Because it’s in Emacs, of course, it integrates nicely with other
packages, but it also has some nice features of its own: main and
dependent databases, a visual editor for BibLaTeX fields, and
integration with LaTeX and Org mode for citations and notes. This
makes my workflow quite a bit easier, and makes adding and editing my
PDF database a breeze. Moreover, since its backend is just a BibLaTeX
file, I don’t need to worry about interfacing with other authors who
use different programs, or importing my database from one system to
another. It should just work.The [[info:ebib#Top][ebib manual]] is a good read to understand all that this package can
do. My usage at the moment is quite simple, but like much of Emacs,
it’s very easy to grow into the rest of the functionality, as it were.#+begin_src emacs-lisp
(use-package ebib
:after xdg
:bind ("" . ebib)
:custom ((ebib-bibtex-dialect 'biblatex)
(ebib-preload-bib-files '("biblioteko.bib"))
(ebib-bib-search-dirs '("~/Biblioteko"))
(ebib-file-search-dirs '("~/Biblioteko"))
(ebib-reading-list-file "~/Dokumentoj/org/reading.org")
(ebib-use-timestamp t)
(ebib-use-timestamp-format "%Y-%m-%d")
(ebib-import-directory (or (xdg-user-dir "DOWNLOAD") "~/Downloads"))
(ebib-layout 'window)
(ebib-file-associations '())))
#+end_src* Projects
For working with my different writing and programming projects, I have
some external Emacs packages to make things easier.** Magit
Version control is a big part of my project management, and I almost
exclusively use git as version control. While git has a very nice
data model, its user interface is somewhat lacking. Luckily, the
Emacs ecosystem provides us with a much nicer interface to git. In
fact, I would go as far as to say that [[http://magit.vc/][Magit]] is *the best* git
porcelain, by far.#+begin_src emacs-lisp
(use-package magit
:config (setq magit-repository-directories '(("~" . 3)))
:commands (magit-status magit-blame magit-log-buffer-file magit-log-all))
#+end_srcWe want Magit to search for all repositories nested three directories
under home. ([2022-04-04 lun 21:13] At some point, I will look into
how this relates to Emacs’s own built-in project search; it’s not
clear to me that I really need this configuration when I use
~project.el~.)#+begin_src emacs-lisp
(use-package magit-filenotify :after magit)
(use-package forge :after magit)
#+end_srcWe also want to integrate Magit into Emacs’s project functionality:
#+begin_src emacs-lisp
(use-package magit-extras
:ensure nil)
#+end_src** Git Commit
#+begin_src emacs-lisp
(use-package git-commit
:custom ((git-commit-major-mode . 'markdown-mode)))
#+end_src*** Magit Imerge
#+begin_src emacs-lisp
(use-package magit-imerge
:after magit)
#+end_src** Highlight Uncommited Changes
Even though the ~M-x magit-status~ command gives a good overview of
uncommited and unstaged changse, it is also useful to see a visual
representation of those changes inline in a buffer. For this, we use
the ~diff-hl~ package, which displays a marker in the gutter (on the
left side of the window) for hunks that are uncommited (or in git’s
case, unstaged as well).#+begin_src emacs-lisp
(use-package diff-hl
:hook (magit-post-refresh-hook . diff-hl-magit-post-refresh)
:config (global-diff-hl-mode +1)
:custom-face
(diff-hl-change ((t (:background "#444466" :foreground "blue3"))))
(diff-hl-delete ((t (:background "#553333" :foreground "red3" :inherit diff-removed))))
(diff-hl-change ((t (:background "#335533" :foreground "green4" :inherit diff-added)))))
#+end_src** Xref
#+begin_src emacs-lisp
(use-package xref
:custom ((xref-search-program #'ripgrep "Use ripgrep for identifier search.")))
#+end_src* Calendar
** Alerts
Emacs’s built-in way of alerting you of something is by appending to
the =Messages= buffer, which also shows up in the minibuffer. This is
often hard to see, and it doesn’t interface nicely with other
notifications my system gives me, using =libnotify=. John Wiegley’s
=alert= package gives us a consistent interface to any number of
notification backends; this works on Windows, OSX, and UNIX-y systems,
and can be easily extended. For the moment, since the only systems I
use Emacs on are graphical Linux systems, I unconditionally configure
=alert= to use a backend that forwards to =libnotify=. On GNOME
Shell, this makes a little notification pop up at the top of the
screen, with a customizable title, icon, and actions.#+begin_src emacs-lisp
(use-package alert
:config
(setq alert-default-style 'notifications))
#+end_srcAny package that uses =alert= will now show us notifications in GNOME
Shell.** Org Agenda
I use =org-agenda= to manage my TODOs and schedule, so many of the
more important parts of my configuration for what I do day-to-day are
here. First, we want to let Emacs know where I keep my TODO files.#+begin_src emacs-lisp
(setq org-agenda-files '("~/Dokumentoj/org/"
"~/Dokumentoj/org/notes/daily/"))
#+end_srcNot everything in Emacs land knows about =org-agenda=. The older,
built-in package =diary.el= includes things like BBDB anniversaries,
sunrise/sunset times, and more. In order to include these in the
=org-agenda=, we need to set the following variable:#+begin_src emacs-lisp
(setq org-agenda-include-diary t)
#+end_srcThere is [[info:org#Weekly/daily agenda][an alternative way to do the above]], which is apparently
somewhat faster: you can add specific diary expressions into your org
file, and add only those diary entries (anniversaries, holidays, etc.)
that you need. I haven’t seen any issues with the above, but I will
want to keep this in mind just in case.#+begin_src emacs-lisp
(setq org-agenda-category-icon-alist nil)
#+end_src#+begin_src emacs-lisp
(setq org-agenda-use-time-grid t)
#+end_srcI also use the ~LOCATION~ property quite frequently in my tasks, and I
would like to see those in my agenda along with the time. Do do this,
we reach for the ~org-agenda-property~ package, which is a very small
addition to org-agenda, but gives this information to be very easily.#+begin_src emacs-lisp
(use-package org-agenda-property
:custom (org-agenda-property-list '("LOCATION")))
#+end_src** Sunrise and Sunset
I would like to put local sunrise and sunset times in my [[*Org Agenda][Org Agenda]],
as events at the correct time of day. This can be done using the
~solar.el~ functionality present within Emacs, and using Emacs diary
expressions.First, we need to figure out where we are currently located. Emacs
uses a handful of variables to figure this out:
#+begin_src emacs-lisp
(setq calendar-latitude 42.36164
calendar-longitude -71.090255
calendar-location-name "Cambridge, MA")
#+end_src
For the moment, I have this hard-coded, but really I should be using
[[https://gitlab.freedesktop.org/geoclue/geoclue/-/wikis/home][GeoClue]] to figure out where the system is.Next, I make two functions that I can call in an org file with diary
expressions, which return a string with the sunrise and sunset,
respectively:
#+begin_src emacs-lisp
(defun pmn:diary-sunrise ()
"Local time of sunrise as a diary entry.
Accurate to a few seconds."
;; To be called from diary-list-sexp-entries, where DATE is bound.
(with-no-warnings (defvar date))
(or (and calendar-latitude calendar-longitude calendar-time-zone)
(solar-setup))
(let ((l (solar-sunrise-sunset date)))
(if (car l)
(format "🌅 Sunrise %s at %s"
(apply #'solar-time-string (car l))
(eval calendar-location-name))
"No sunrise")))(defun pmn:diary-sunset ()
"Local time of sunset as a diary entry.
Accurate to a few seconds."
;; To be called from diary-list-sexp-entries, where DATE is bound.
(with-no-warnings (defvar date))
(or (and calendar-latitude calendar-longitude calendar-time-zone)
(solar-setup))
(let ((l (solar-sunrise-sunset date)))
(if (cadr l)
(format "🌅 Sunset %s (%s hrs daylight)"
(apply #'solar-time-string (cadr l))
(nth 2 l))
"No sunset")))
#+end_src* Internet
Plain text is incredibly versatile as a means for interacting with
computers, and Emacs is incredibly versatile as a means for
interacting with plain text. A substantial amount of my time is spent
in Emacs, and that includes working with networked applications. This
section details my configuration for several important such
applications, all entirely in plain text!
My email is synced locally using IMAP and SMTP, so I always have a
copy of my email archives. Because these archives are all stored in
Maildirs, I can read them, search them, and interact with them in my
choice of mail clients; if any of them ever fails me, it’s little
effort to use another one temporarily. My preferred mail clients have
all been applications within Emacs, though. Though early on I used
the builtin GNUS, I found it too heavyweight and confusing to use
daily. For a while, I used mu4e, which I loved, but it physically
moves mail messages within my maildirs when I retag messages. This
always worried me, as I do not want to lose any mail accidentally.So instead, I’ve settled on notmuch for indexing my mail, and the
included notmuch Emacs interface for reading my mail. Notmuch is
blazingly fast, integrates with other mail clients, like ~mutt~, and
also comes with a nice command line interface for working with my
large mail archive. Moreover, and most importantly, it maintains a
separate index of my email archive, so it doesn’t touch the ground
truth: the emails themselves.Because the notmuch binary and the notmuch Emacs interface are tightly
coupled, we don’t want to search for the latest notmuch package on
MELPA or such. Instead, we just load the package that is already
installed on the system.#+begin_src emacs-lisp
(use-package notmuch
:ensure nil
:bind (("" . notmuch))
:init
(setenv "EMAIL_QUEUE_QUIET" "true")
(setq sendmail-program "/home/pniedzielski/.local/bin/msmtpq"
message-send-mail-function 'message-send-mail-with-sendmail
;; mail-from-style 'angles
mail-host-address "pniedzielski.net"
mail-specify-envelope-from t
;; needed for debian’s message.el, cf. README.Debian.gz
message-sendmail-f-is-evil nil
mail-envelope-from 'header
message-sendmail-envelope-from 'header
message-citation-line-format "%f skribis:\n"
message-citation-line-function 'message-insert-formatted-citation-line
notmuch-fcc-dirs '(("[email protected]" . "personal/Sent")
("[email protected]" . "mit/Sent"))
mml-secure-openpgp-sign-with-sender t)
:custom
(notumch-search-oldest-first nil
"Give me the most recent mail at the top."))
#+end_src** RSS
RSS has been and remains one of the best ways of consuming syndicated
articles on the web. Although its heyday seems to have gone (being
too commonly replaced by [[https://indieweb.org/walled_garden][walled gardens]] and email newsletters), there
still are plenty of RSS feeds out there, as well as services that
convert public data into RSS feeds.I use [[https://freshrss.org/][FreshRSS]] on my personal server to collect and synchronize my
feeds across all my devices (personal computers, web, and mobile
phone), and so I need an Emacs package that supports one of FreshRSS’s
APIs. Luckily, the Emacs feedreader [[https://github.com/skeeto/elfeed][elfeed]], when augmented with a
separate extension named [[https://github.com/fasheng/elfeed-protocol][elfeed-protocol]], is able to communicate with
my FreshRSS server, synchronizing its local database with the online
server.#+begin_src emacs-lisp
(use-package elfeed
:config (setq elfeed-use-curl t)
(elfeed-set-timeout 36000)
:bind (:map elfeed-search-mode-map
("m" . elfeed-toggle-star)
("M" . elfeed-toggle-star)))(use-package elfeed-goodies
:config (elfeed-goodies/setup))(use-package elfeed-protocol
:defer nil
:init
(defun pmn:elfeed-refresh ()
"Refresh elfeed feeds from Fever API."
(interactive)
(save-mark-and-excursion
(push-mark (point))
(push-mark (point-max) nil t)
(goto-char (point-min))
(cl-loop for entry in (elfeed-search-selected)
do (elfeed-untag-1 entry 'unread))
(elfeed-search-update--force)
(elfeed-protocol-fever-reinit "https://[email protected]")))
:config
(setq elfeed-feeds
`(("fever+https://[email protected]"
:api-url "https://rss.pniedzielski.net/api/fever.php"
:password ,(password-store-get "api/rss.pniedzielski.net")))
elfeed-protocol-enabled-protocols '(fever))
(elfeed-protocol-enable)
:bind (:map elfeed-search-mode-map
("G" . pmn:elfeed-refresh)))
#+end_src** AI Assistants
AI Assistants like ChatGPT are very useful tools to help with writing,
programming, and thinking, so long as you are aware of their strengths
and weaknesses. There are already a few ChatGPT clients for Emacs,
but I’m trying Karthick Chikmagalur’s [[https://github.com/karthink/gptel][GPTel]] client, which allows me to
interact with ChatGPT from any buffer, using Org Mode syntax, and
asynchronously!GPTel provides a transient-based interface as its starting point, so I
bind this command to an easy-to-remember global keybinding ~C-c g~.#+begin_src emacs-lisp :noweb yes :noweb-prefix no
(use-package gptel
:bind (("C-c g" . gptel-menu))
:init
<>
:hook
(<>)
:config
<>
;; Set this in `config` section to avoid recursive `require`.
(setq gptel-backend <>)
:custom
(<>
(gptel-model "phi4"
"Use Phi4 as our default model.")
<>
<>
(gptel-expert-commands t
"Let’s try experimental features.")
(gptel-org-branching-context t
"LLMs use only parent headings as context.")))
#+end_srcI have three different model backends configured:
- OpenAI :: Despite its cost and being annoyingly censored in what
it’s willing to produce (often mistaking perfectly harmless things
for things more sinister), I still think OpenAI’s models are the
best out there. Like ChatGPT, its flagship product (I’ve trained
him to respond to “Chad”), OpenAI’s API still sets the standard
for remote LLM access.GPTel has native support for Chad’s API. ALl we have to do is
configure the ~gptel-api-key~ variable to a function that returns
our API key:#+name: gptel-openai
#+begin_src emacs-lisp :tangle no
(gptel-api-key
(lambda () (password-store-get-field "ai/openai.com" "GPTEL"))
"Retrieve the OpenAI key from `pass`.")
#+end_src- Groq :: Groq provides incredibly fast access for many open-source
models, because they make use of their own proprietary hardware
for running LLMs. They are also free, and in fact do not yet
provide a paid usage tier. Despite being free and fast, though,
Groq has relatively low token limits. It’s fun to play around
with, and can work pretty well in certain circumstances.Configuring GPTel to use Groq is also fairly easy, because Groq
provides an OpenAI-compatible API.#+name: gptel-groq
#+begin_src emacs-lisp :tangle no
(gptel-make-openai "Groq"
:host "api.groq.com"
:endpoint "/openai/v1/chat/completions"
:stream t
:key (lambda ()
(password-store-get-field "ai/groq.com" "GPTEL"))
:models '("llama3-70b-8192"
"mixtral-8x7b-32768"
"llama-3.3-70b-versatile"
"deepseek-r1-distill-llama-70b"))
#+end_src- Ollama :: Ollama runs LLMs completely locally on your own GPU.
While this means I can only run smaller models, the results tend
to be very fast and can still be useful, even if the smaller
models don’t know as much or struggle in certain reasoning tasks.#+name: gptel-ollama
#+begin_src emacs-lisp :tangle no
(gptel-make-ollama "Ollama"
:host "localhost:11434"
:stream t
:models (pmn:get-ollama-models))
#+end_srcGPTel does not have the ability to dynamically query from Ollama
what models are available, though, so we provide our own function
~pmn:get-ollama-models~ that does this for us.#+name: gptel-ollama-get-models
#+begin_src emacs-lisp :tangle no
(defun pmn:get-ollama-models ()
"Fetch the list of installed Ollama models."
(let* ((output (shell-command-to-string "ollama list"))
(lines (split-string output "\n" t))
models)
(dolist (line (cdr lines)) ;Skip the first line
(when (string-match "^\\([^[:space:]]+\\)" line)
(push (match-string 1 line) models)))
(nreverse models)))
#+end_srcI also want to do a little post-processing on the output of models.
By default GPTel converts output to org-mode if we tell it to use
org-mode as our conversations’ mode:#+name: gptel-org-mode
#+begin_src emacs-lisp :tangle no
(gptel-default-mode 'org-mode "Make LLMs output in `org-mode`.")
#+end_srcWhile we’re at it, GPTel defaults to having our prompts be 3rd level
headers in both Markdown and org-mode. Let’s fix that:#+name: gptel-org-headings
#+begin_src emacs-lisp :tangle no
(gptel-prompt-prefix-alist '((markdown-mode . "# ")
(org-mode . "* ")
(text-mode . "# "))
"Use top-level headers for our conversation prompts.")
#+end_srcLLMs also don’t respect our fill column, either, but we can easily fix
this by adding ~fill-region~ as a hook after LLMs insert.#+name: gptel-fill-region
#+begin_src emacs-lisp :tangle no
(gptel-post-response-functions . fill-region)
#+end_srcNow, from any buffer, we can call ~M-x gptel-send~ to send the current
region to a new conversation with ChatGPT, or ~C-u M-x gptel-send~ to
modify the chat parameters. If we want a dedicated buffer, we can use
~M-x gptel~, and ~C-u M-x gptel~ to start a new session.* Finances
** Ledger Mode
I use John Wiegley’s [[https://www.ledger-cli.org/][ledger]] program to manage my finances. Ledger is
a plain text-based double-entry accounting system that lets me keep
track of exactly where every cent goes. Luckily, ledger has an Emacs
mode as well, which helps in managing my budget.#+begin_src emacs-lisp
(use-package ledger-mode)
#+end_src* Completion
** Vertico
For quite a long time, I used Ido for ~completing-read~, and it worked
great. It was nice, it was lightweight, and it was easy to setup.
Especially compared to the only alternative at the time—Helm—it didn’t
take up much screen real estate and never got in my way. However,
Ido was very limited with what it could do, and (through probably no
fault of its own) was not well-integrated with third-party packages.
When I stared playing with helm-bibtex/ivy-bibtex, it finally got me
to switch to Ivy, the lighter-weight of those two alternatives.Nonetheless, although Ivy is not as heavy, it is not especially
Emacs-y. I’ve never fully understood the division of labor between
Ivy and its related package Counsel, and the Ivy-ecosystem package
that seems to be touted as its killer feature, Swiper, seems worse
than isearch+occur. For this reason, I’m taking a look at the new kid
on the block, Vertico ([2022-04-03 dim]).As I understand it, Vertico is designed to do one thing well: be a
~completing-read~ implementation. It outsources many of the things in
Ivy and Counsel to other packages to deal with. In that way, I always
found that ecosystem more confusing, but I think that’s because it’s
exposing inherent complexity that that was always behind the scenes in
Ivy. This should also make it easier for me to modify exactly how my
searches work, like giving me the ability to add tags back to the
org-roam search very easily.Because I found this so unintuitive though, this is how I understand
the ecosystem:- selectrum :: This was the predecessor to Vertico, and it
implements ~completing-read~, i.e., reading a string from the
minibuffer and providing completion. It was meant to be a lighter
version of Ivy, and to integrate more nicely with the default
Emacs ~completing-read~ framework.- vertico :: Vertico provides an implementation of Emacs’s
~completing-read~, allowing you to read a string from the
minibuffer and providing completion from a list of candidates.
Like selectrum before it, it slots into Emacs’s existing
~completing-read~ framework.- consult :: Consult enhances many of the commands in Emacs that use
~completing-read~, and adds a few more ~completing-read~ commands
as well. It’s analogous to Counsel in the Ivy world.- embark :: Once you’ve found what you’re looking for with
~completing-read~, Embark lets you run other commands on it than
the one you invoked. For instance, if you search apropos for a
function, you can instead insert the function name. It’s similar
to ~ivy-hydra~.- marginalia :: Marginalia adds nice annotations to
~completing-read~, like ~ivy-rich~.- orderless :: This adds another filtering mechanism to
~completing-read~, which is based on substring matching with
multiple space-deliminated terms.For the moment, I’m starting small: I’m only using Vertico, so I can
get used to it, and Marginalia, since the annotations are very, very
useful to me. In a way, this feels like using Ido mode again,
although it has better integration across-the-board than Ido did when
I last used it.#+begin_src emacs-lisp
(use-package vertico :demand
:diminish vertico-mode
:custom
(vertico-resize t "Grow and shrink the Vertico minibuffer")
:config
(vertico-mode))
#+end_srcVertico has a handful of extensions that are included along with it.
One very nice one makes editing text in the minibuffer more like Ido,
which is something that I greatly missed when I switched to Ivy.#+begin_src emacs-lisp
(use-package vertico-directory
:after vertico
:ensure nil ; included with vertico
;; Make vertico editing more like Ido.
:bind (:map vertico-map
("RET" . vertico-directory-enter)
("DEL" . vertico-directory-delete-char)
("M-DEL" . vertico-directory-delete-word))
;; Tidy shadowed names.
:hook (rfn-eshadow-update-overlay-hook . vertico-directory-tidy))
#+end_srcVertico [[info:vertico#org-refile][has some issues with org-refile]], if I use a completion style
that isn’t just ~basic~—or more precisely, org-refile has some issues
with other completion styles. I never tried to fix this with Ido or
Ivy, so when I started using ~org~ more, I went from ~flex~ completion
back to ~basic~. ~flex~ completion is nice, so at some point I will
use one of the two solutions documented in the Vertico manual.** Completion Annotations
The Marginalia package adds nice annotations to ~completing-read~, in
the manner of ~ivy-rich~. This is something I really liked about the
Ivy ecosystem, and I’m glad to have it still with Vertico.This configuration is mostly taken from the [[https://github.com/minad/marginalia/][Marginalia README]], but
with one change: The README says to start ~marginalia-mode~ in the
~:init~ section of my ~use-package~ declaration:#+begin_src emacs-lisp :tangle no
;; Must be in the :init section of use-package such that the mode gets
;; enabled right away. Note that this forces loading the package.
#+end_srcThis doesn’t comport with what the [[info:use-package#preface init config][~use-package~ manual says]], namely
that ~:init~ is run before the package is loaded! We need to load
Marginalia before we can turn on the mode. I’m not sure why that
configuration seems to work (does it have something to do with
autoloading?), but the correct way to override the lazy loading from
~:bind~ is [[info:use-package#defer demand][using ~:demand~]].#+begin_src emacs-lisp
(use-package marginalia :demand
:diminish marginalia-mode
:bind (:map minibuffer-local-map
("M-A" . marginalia-cycle))
:config (marginalia-mode))
#+end_srcI haven’t found a use yet for ~marginalia-cycle~, but having it bound
to ~M-A~ as the documentation suggests doesn’t seem to hurt anything.** Smarter Completion
Vertico and other packages that only replace the completing-read
mechanism can only do so much to the interface of commonly-used
commands, as it is limited by the completing-read interface. While
this interface is very flexible, we can do even better if we override
those commonly-used commands with versions that do more with the
information from completing-read.Consult is one such package, the equivalent in the Vertico/Selectrum
ecosystem of Ivy’s Counsel. Consult provides versions of many
built-in Emacs commands, but which provide a richer interface,
including real-time previews, in-minibuffer formatting, and completion
narrowing.#+begin_src emacs-lisp
(use-package consult
:bind (;; C-c bindings (mode-specific-map)
("C-c h" . consult-history)
("C-c m" . consult-mode-command)
("C-c k" . consult-kmacro)
;; C-x bindings (ctl-x-map)
("C-x M-:" . consult-complex-command) ;; orig. repeat-complex-command
("C-x b" . consult-buffer) ;; orig. switch-to-buffer
("C-x 4 b" . consult-buffer-other-window) ;; orig. switch-to-buffer-other-window
("C-x 5 b" . consult-buffer-other-frame) ;; orig. switch-to-buffer-other-frame
("C-x r b" . consult-bookmark) ;; orig. bookmark-jump
("C-x p b" . consult-project-buffer) ;; orig. project-switch-to-buffer
;; Custom M-# bindings for fast register access
("M-#" . consult-register-load)
("M-'" . consult-register-store) ;; orig. abbrev-prefix-mark (unrelated)
("C-M-#" . consult-register)
;; Other custom bindings
("M-y" . consult-yank-pop) ;; orig. yank-pop
(" a" . consult-apropos) ;; orig. apropos-command
;; M-g bindings (goto-map)
("M-g e" . consult-compile-error)
("M-g f" . consult-flymake) ;; Alternative: consult-flycheck
("M-g g" . consult-goto-line) ;; orig. goto-line
("M-g M-g" . consult-goto-line) ;; orig. goto-line
("M-g o" . consult-outline) ;; Alternative: consult-org-heading
("M-g m" . consult-mark)
("M-g k" . consult-global-mark)
("M-g i" . consult-imenu)
("M-g I" . consult-imenu-multi)
;; M-s bindings (search-map)
("M-s d" . consult-find)
("M-s D" . consult-locate)
("M-s g" . consult-grep)
("M-s G" . consult-git-grep)
("M-s r" . consult-ripgrep)
("M-s l" . consult-line)
("M-s L" . consult-line-multi)
("M-s m" . consult-multi-occur)
("M-s k" . consult-keep-lines)
("M-s u" . consult-focus-lines)
;; Isearch integration
("M-s e" . consult-isearch-history)
:map isearch-mode-map
("M-e" . consult-isearch-history) ;; orig. isearch-edit-string
("M-s e" . consult-isearch-history) ;; orig. isearch-edit-string
("M-s l" . consult-line) ;; needed by consult-line to detect isearch
("M-s L" . consult-line-multi) ;; needed by consult-line to detect isearch
;; Minibuffer history
:map minibuffer-local-map
("M-s" . consult-history) ;; orig. next-matching-history-element
("M-r" . consult-history)) ;; orig. previous-matching-history-element;; Enable automatic preview at point in the *Completions* buffer.
;; This is relevant when you use the default completion UI.
:hook (completion-list-mode . consult-preview-at-point-mode);; The :init configuration is always executed (Not lazy)
:init;; Optionally configure the register formatting. This improves the
;; register preview for `consult-register', `consult-register-load',
;; `consult-register-store' and the Emacs built-ins.
(setq register-preview-delay 0.5
register-preview-function #'consult-register-format);; Optionally configure the register formatting. This improves the register
;; preview for `consult-register', `consult-register-load',
;; `consult-register-store' and the Emacs built-ins.
(setq register-preview-delay 0.5
register-preview-function #'consult-register-format);; Optionally tweak the register preview window.
;; This adds thin lines, sorting and hides the mode line of the window.
(advice-add #'register-preview :override #'consult-register-window);; Optionally replace `completing-read-multiple' with an enhanced version.
(advice-add #'completing-read-multiple :override #'consult-completing-read-multiple);; Use Consult to select xref locations with preview
(setq xref-show-xrefs-function #'consult-xref
xref-show-definitions-function #'consult-xref);; Configure other variables and modes in the :config section,
;; after lazily loading the package.
:config;; Optionally configure preview. The default value
;; is 'any, such that any key triggers the preview.
;; (setq consult-preview-key 'any)
;; (setq consult-preview-key (kbd "M-."))
;; (setq consult-preview-key (list (kbd "") (kbd "")))
;; For some commands and buffer sources it is useful to configure the
;; :preview-key on a per-command basis using the `consult-customize' macro.
(consult-customize
consult-theme
:preview-key '(:debounce 0.2 any)
consult-ripgrep consult-git-grep consult-grep
consult-bookmark consult-recent-file consult-xref
consult--source-bookmark consult--source-recent-file
consult--source-project-recent-file
:preview-key (kbd "M-."));; Optionally configure the narrowing key.
;; Both < and C-+ work reasonably well.
(setq consult-narrow-key "<") ;; (kbd "C-+");; Optionally make narrowing help available in the minibuffer.
;; You may want to use `embark-prefix-help-command' or which-key instead.
;; (define-key consult-narrow-map (vconcat consult-narrow-key "?") #'consult-narrow-help);; By default `consult-project-function' uses `project-root' from project.el.
;; Optionally configure a different project root function.
;; There are multiple reasonable alternatives to chose from.
;;;; 1. project.el (the default)
;; (setq consult-project-function #'consult--default-project--function)
;;;; 2. projectile.el (projectile-project-root)
;; (autoload 'projectile-project-root "projectile")
;; (setq consult-project-function (lambda (_) (projectile-project-root)))
;;;; 3. vc.el (vc-root-dir)
;; (setq consult-project-function (lambda (_) (vc-root-dir)))
;;;; 4. locate-dominating-file
;; (setq consult-project-function (lambda (_) (locate-dominating-file "." ".git")))
)
#+end_src** Minibuffer History
I used to use [[https://github.com/DarwinAwardWinner/amx/][Amx]], and before that [[https://github.com/nonsequitur/smex][Smex]], as an enhanced
~execute-extended-command~, which prioritized commands by use and
showing keyboard shortcuts along with the command name. Now that I’m
using [[*Vertico][Vertico]], which (along with its ancillary packages) provides most
of the functionality in Amx, it doesn’t make sense to use Amx anymore.The only missing functionality is saving the command history across
sessions of Emacs. Luckily, the built-in ~savehist~ minor mode
replicates this functionality. We just need to load it and turn it
on, and it will periodically save our command history to a file,
preserving it when Emacs is shutdown.#+begin_src emacs-lisp
(use-package savehist
:config (savehist-mode +1))
#+end_src** Completion at Point :testing:
Although minibuffer completion is by far the most important sort of
completion to my Emacs usage, I do make use of /completion at point/
fairly frequently as well, especially when programming. However, even
moreso than the default completing read, I find the default completion
at point to be very annoying to use whenever there are more than a
small number of options. For this reason, I have always replaced
completion at point with a 3rd party package that display a popup for
completion candidates, rather than opening a side window to display
them. For the longest time, I used [[https://company-mode.github.io/][company-mode]], which does work very
well, but has started to feel very big and bolted-on, especially as
Emacs’s built-in completion framework has matured. I’m now trying
[[https://github.com/minad/corfu][corfu]], which performs the same basic UI function as company-mode, but
which relies only on the Emacs completion functionality to propose
candidates.#+begin_src emacs-lisp
(use-package corfu
:custom
(tab-always-indent 'complete
"On TAB, first indent the line, then complete.")
:config
(global-corfu-mode))
#+end_srcUnlike company-mode, corfu does not provide any backends for
collecting completion candidates, instead relying on Emacs’s built-in
completion at point functions to do that job. This is, by and large,
fine, because the completion at point backends have gotten much better
in recent versions of Emacs. Furthermore, more and more modes and
packages, notably including [[*LSP][Eglot]], have targetted completion at point
functions to provide completion candidates. If I ever need something
like the addition backends that Company provides, I can always try the
[[https://github.com/minad/cape][Cape]] package, which provides similar backends as completion at point
functions.One of the nice things about corfu is how extensible it is. This
comes in several forms, such as the pluggable completion at point
backends above, but even its UI can be modified. There are two
packages that I’m trying with corfu, because I used their equivalents
with company:1. [[https://github.com/galeo/corfu-doc][corfu-doc]] provides inline documentation for candidates that
include it, making it easier to select between similar
alternatives when programming.
#+begin_src emacs-lisp
(use-package corfu-doc
:after corfu
:hook (corfu-mode . corfu-doc-mode)
:bind
(:map corfu-map
("M-p" . corfu-doc-scroll-down)
("M-n" . corfu-doc-scroll-up)
("M-d" . corfu-doc-toggle)))
#+end_src2. [[https://github.com/jdtsmith/kind-icon][kind-icon]] uses the ~:company-kind~ property to add icons or
visual text prefixes to completion candidates, making it easier
to filter completion candidates at a glance.
#+begin_src emacs-lisp
(use-package kind-icon
:after corfu
:custom
(kind-icon-default-face 'corfu-default
"Use the same font as corfu for icons")
:config
(add-to-list 'corfu-margin-formatters
#'kind-icon-margin-formatter))
#+end_src* Visual Theming
This is honestly the least important part of my configuration. Fonts
and pretty are nice, and customizing the display to save a bit of
space is useful, but setting up my packages and configuration as above
is so much more important to me.One important aspect of this configuration is that I try my hardest
for it to work both with X11 GUI clients and with terminal clients. I
use terminal clients significantly more often than I think most Emacs
GUI users do, and I don’t want my theming to make terminal clients
unusable.** GUI
There are certain GUI defaults in Emacs that I’m not a big fan of,
because they either take up space or distract me. Foremost among
these is the toolbar, which I see very little use for: it only has the
commonly used buffer and file operations whose keybindings I have no
trouble remembering. Tool tips and blinking cursors are similar—they
only distract me when I’m trying to do something. Let’s turn these
off:#+begin_src emacs-lisp
(dolist (mode
'(tool-bar-mode
tooltip-mode
blink-cursor-mode
menu-bar-mode
scroll-bar-mode))
(funcall mode 0))
#+end_srcThe final two things we turn off here are ones that I do think have
great utility, and that I turn on manually every once in a while.
First, ~menu-bar-mode~ is great, especially when learning a new major
mode or package. While my eventual goal is to learn the keybindings
or command names, the GUI menu is a great way to discover the
functionality of the package. For that reason, I turn it on manually
when I’m training myself on a new package. By default, though, I keep
it off. Second, ~scroll-bar-mode~ is quite nice for seeing roughly
where you are in a large buffer, without taking up much space. For
some of this, I can use the line number on the modeline, coupled with
~count-words~ to see how many lines are in the (maybe narrowed)
buffer. But, it can still be a nice visual reminder. On those
occasions, I turn the scroll bar back on manually. Otherwise, though,
I keep it off, to take up less space.My desktop environment uses the mouse to select which X11 window has
keyboard input focus. I’d like this to carry over into Emacs, where
the mouse also selects which Emacs window has focus. This isn’t my
primary means of moving between windows (see the [[*Windmove][Windmove]] section for
details), but when I’m working with other programs that use the mouse,
this makes life a bit easier.#+begin_src emacs-lisp
(setq-default mouse-autoselect-window t)
#+end_srcWe also don’t want to see a startup screen. Instead, I’d rather be
taken directly to scratch—I know how to find the GNU Manifesto on my
own.#+begin_src emacs-lisp
(setq-default inhibit-startup-message t)
#+end_srcWe do, though, want the line and column numbers to be displayed on the
modeline.#+begin_src emacs-lisp
(line-number-mode 1)
(column-number-mode 1)
#+end_srcFinally, let’s make sure the margins are there, but aren’t bigger than
necessary.#+begin_src emacs-lisp
(setq left-margin-width 1
right-margin-width 1)
#+end_src** Fonts and Ligatures
The font I’m currently using is [[https://github.com/microsoft/cascadia-code][Cascadia Code]] (which, luckily, is
packaged in Debian as =fonts-cascadia-code=). I find this font is
very easy to read for long periods of time, and at different sizes; I
value that more than its looks or its ligatures.#+begin_src emacs-lisp
(set-face-attribute 'default nil :font "Cascadia Code PL")
#+end_src** Dimming Unused Windows
It can be hard to tell which window has input focus at-a-glance. I
don’t find the visual clues of the mode-line sufficient to help me
know where I’m typing. The [[https://github.com/gonewest818/dimmer.el][~dimmer.el~]] package tones down the colors
of windows that are not in focus, making it easier to see which window
is in focus.#+begin_src emacs-lisp :noweb yes
(use-package dimmer
:config <>
<>
(dimmer-mode))
#+end_srcThe default dimming fraction (20%) isn’t quite enough to make it easy
to see which window is in focus, so I increase this to 40%.#+name: dimmer-config-fraction
#+begin_src emacs-lisp :tangle no
(setq dimmer-fraction 0.4)
#+end_srcBy default, ~dimmer.el~ doesn’t dim the minibuffer and echo areas.
There are some packages, though, that use multiple windows in their
normal interface. Among these are [[*Magit][Magit]], [[*Org Mode][Org Mode]], and [[*Which Key?][Which Key?]].#+name: dimmer-config-packages
#+begin_src emacs-lisp :tangle no
(dimmer-configure-magit)
(dimmer-configure-org)
(dimmer-configure-which-key)
#+end_srcFor a full list of supported packages, see the [[https://github.com/gonewest818/dimmer.el#configuration][Configuration]] section
of the ~dimmer.el~ documentation.** Pretty Pages
I use Emacs’s page functionality quite a lot, so it’s a nice quality
of life improvement to have the ugly ~^L~ characters displayed as
lines stretching across the window. There are a few packages that can
do this: the big ones seem to be Steve Purcell’s [[https://github.com/purcell/page-break-lines][page-break-lines]] and
Vasilij Schneidermann’s [[https://depp.brause.cc/form-feed/][form-feed]]. For whatever reason, I chose the
latter, and I haven’t had any problems with it.Let’s set up form-feed to prettify our page breaks globally across all
Emacs modes and buffers.#+begin_src emacs-lisp
(use-package form-feed
:config (global-form-feed-mode))
#+end_src** Icons
In most cases, having icons in a text editor serve a similar purpose
as syntax-highlighting: used sparingly, they can help understand new
information at a glance. The ~nerd-icons~ package gives us icons for
most file/buffer types, using icon fonts.#+begin_src emacs-lisp
(use-package nerd-icons)
#+end_srcI enable icons in four different places (which, of course, means I
need four extra packages…): in dired, in ibuffer, and in
completion-at-point, in minibuffer completion. In each of these
cases, the icons are complementary to the filenames themselves. That
is to say, if I know what the file I’m looking for is named, I don’t
need to worry about the icons, or if I know what type of file I’m
looking for, I can filter out everything else; if, on the other hand,
I’m looking at a cluttered directory for the first time, or otherwise
don’t know what exactly I’m searching for, icons give a good easy
overview.First, set up the dired icons, by running a hook when dired is loaded.
#+begin_src emacs-lisp
(use-package nerd-icons-dired :demand
:after (dired nerd-icons)
:diminish nerd-icons-dired-mode
:hook (dired-mode-hook . nerd-icons-dired-mode))
#+end_srcNext, set up the ibuffer icons. Of course, it could’t be parallel to
the above, so we need to enable a minor mode.#+begin_src emacs-lisp
(use-package nerd-icons-ibuffer :demand
:after (ibuffer nerd-icons)
:diminish nerd-icons-ibuffer-mode
:hook (ibuffer-mode-hook . nerd-icons-ibuffer-mode))
#+end_srcThird, set up completion-at-point icons in corfu:
#+begin_src emacs-lisp
(use-package nerd-icons-corfu :demand
:after (corfu nerd-icons)
:config
(add-to-list 'corfu-margin-formatters #'nerd-icons-corfu-formatter)(setq nerd-icons-corfu-mapping
'((array :style "cod" :icon "symbol_array" :face font-lock-type-face)
(boolean :style "cod" :icon "symbol_boolean" :face font-lock-builtin-face)
(t :style "cod" :icon "code" :face font-lock-warning-face))))
#+end_srcFourth and finally, set up icons in filename completion.
#+begin_src emacs-lisp
(use-package nerd-icons-completion :demand
:after (marginalia nerd-icons)
:diminish nerd-icons-completion-mode
:config (nerd-icons-completion-mode)
:hook (marginalia-mode-hook . nerd-icons-completion-marginalia-setup))
#+end_src** Display Fill Column
A nice built-in feature of modern Emacsen is to display an indicator
line at the fill-column. This gives agood visual for how long lines
should be in a given project, whetehr or not you are manually or
automatically filling paragraphs.I like to have this minor mode on in both text- and program-editing
modes, but I leave it off for all other modes, where at best it
doesn’t provide any useful information, and at worst it can throw-off
the layout.#+begin_src emacs-lisp
(use-package display-fill-column-indicator
:ensure nil ;built-in
:hook ((prog-mode-hook text-mode-hook) .
display-fill-column-indicator-mode))
#+end_src** ANSI Coloring
More and more terminal programs have been using [[https://en.wikipedia.org/wiki/ANSI_escape_code][ANSI color codes]], or
their extensions, such as [[https://invisible-island.net/xterm/ctlseqs/ctlseqs.html][XTERM-256]], to provide richer formatting of
their human-readable outputs. However, by default, Emacs does not
render these correctly (namely, as colors) in ~compilation-mode~ or
Eshell—the two places where I am most likely to encounter has become
more of an issue as I write more Haskell code, as the commonly used
[[https://hackage.haskell.org/package/tasty][Tasty testing framework]] by default prints test results with color,
making it easier to see at a glance which tests have passed and which
tests have failed. This is decidedly a good thing, but when I run the
tests in ~compilation-mode~, I see the raw escape codes.There are two solutions to this problem: the first is the built-in
~ansi-color.el~, which filters a buffer or region for ANSI color codes
and sets text properties accordingly. This is fairly easy to set up,
but requires us to configure each of the places we want to use
it individually:#+begin_src emacs-lisp :noweb yes
(use-package ansi-color
:ensure nil ;built-in
:defer nil
:hook (
<>
<>
<>))
#+end_srcModes that are derived from ~compilation-mode~ accomplish this by
adding a filter function to ~compilation-filter-hook~:#+name: ansi-color-compilation-hook
#+begin_src emacs-lisp :tangle no
(compilation-filter-hook . ansi-color-compilation-filter)
#+end_srcSimilarly, for ~comint~ modes (like ~shell-mode~), we add a filter
function to a hook. This hook, however, is an [[info:emacs#Hooks][abnormal hook]], which
takes an argument, and we need to use the function
~ansi-color-process-output~:#+name: ansi-color-comint-hook
#+begin_src emacs-lisp :tangle no
(comint-preoutput-filter-functions . ansi-color-process-output)
#+end_srcThe process for configuring Eshell is the same:
#+name: ansi-color-eshell-hook
#+begin_src emacs-lisp :tangle no
(eshell-preoutput-filter-functions . ansi-color-process-output)
#+end_srcThe second solution for this problem is the [[https://github.com/atomontage/xterm-color][xterm-color package]], which
supports more control codes than the basic ANSI ones. At the moment I
don’t need to use it, but it’s worth keeping in mind if I find I need
to commonly use an application that uses XTERM 256 or Truecolor
output.** Custom Theme
Nowadays, Emacs has built-in support for custom themes. It used to be
that you needed to manually set colors, or load a package
=color-theme= to provide a theme, as follows:#+begin_src emacs-lisp :tangle no
(use-package color-theme-modern)
#+end_srcBecause support for custom themes is built-in, though, we don’t need
to do this anymore.Normally, I use the [[https://github.com/juba/color-theme-tangoxtango][tangotango theme]], which is a bit old, and doesn’t
support everything, but is high contrast where it matters and
easy-to-read.#+begin_src emacs-lisp
(use-package tangotango-theme
:defer nil
:config
(load-theme 'tangotango t))
#+end_srcHowever, sometimes a dark theme does not work well: for instance, on a
laptop screen, the glare can make the dark theme hard-to-read. In
this case, it’s useful to have alternative light themes available.
Let’s load some alternative themes up that I can manually engage.#+begin_src emacs-lisp
(use-package color-theme-modern)
#+end_src(This package contains all the color themes that the old =color-theme=
package contains, but updated for the new, built-in custom theme
mechanism.