86 KiB
GNU Emacs configuration
This configuration is inspired from the work of my hero Prot. I've copied straight from his config file, because I think the explanations he has created are worthwhile and should be read by everyone. Prot's files are all prefixed with prot-emacs-*
. I have changed this to unravel-*
. The reason for this is that I have picked up only what I need and changed it where needed. As such, any issues you face with this configuration are likely introduced by me.
Any quote without attribution is directly taken from Prot's org file. You should read through it if you want detailed explanations of things you find here.
What you are now reading is not a common literate configuration of Emacs. In most such cases, you have a generic
init.el
with a call to theorg-babel-load-file
function that gets an Org document as its value. That method works but is very slow, because we have to load Org before starting Emacs (and Org loads a bunch of other things we do not need at such an early stage).Whereas this Org document serves as (i) a single point of entry to my Emacs setup and (ii) the origin of all of my Emacs configurations. While I am defining everything in a single Org file, I am not actually starting Emacs by reading this file. Rather, I am instructing Org to put the code blocks defined herein in standalone files, organised by scope. The end result is something where you cannot tell whether a literate program was executed or not.
This is the beauty of it. I can keep editing a single file as the "source of truth", though I can still handle each of the files individually (e.g. someone wants to see how I do a specific thing, so I share only that file as an email attachment—no need to send over this massive document).
When I want to modify my Emacs setup, I edit this file and then evaluate the following code block or do
C-c C-v C-t
. All files will be updated accordingly.
#+src emacs-lisp :tangle no :results none (org-babel-tangle)
#+end_src
- The
early-init.el
file - The
init.el
file- The
init.el
tweaks to make native compilation silent - The
init.el
setting to sendcustom-file
to oblivion - The
init.el
settings to enable commands disabled by default - The
init.el
settings to disable unnecessary commands enabled by default - Add the modules folder to the load-path
- The
init.el
settings for packages (package.el
) - The
init.el
macro to do nothing with Elisp code (prot-emacs-comment
) - The
init.el
macro to define abbreviations (prot-emacs-abbrev
) - The
init.el
final part to load the individual modules
- The
- The
unravel-theme.el
module- The
unravel-theme.el
section for cool, modern themes (ef-themes
) - The
unravel-theme.el
section forlin
- The
unravel-theme.el
section forspacious-padding
- The
unravel-theme.el
section forrainbow-mode
- The
unravel-theme.el
section forcursory
- The
unravel-theme.el
section fortheme-buffet
- The
unravel-theme.el
section aboutfontaine
- The
unravel-theme.el
section aboutvariable-pitch-mode
and font resizing - Finally, we provide the
unravel-theme.el
module
- The
- The
unravel-essentials.el
module- The
unravel-essentials.el
block with basic configurations - The
unravel-essentials.el
configuration to track recently visited files (recentf
) - The
unravel-essentials.el
settings for bookmarks - The
unravel-essentials.el
settings for registers - The
unravel-essentials.el
section fordelete-selection-mode
- The
unravel-essentials.el
settings for tooltips - The
unravel-essentials.el
arrangement to run Emacs as a server - The
prot-emacs-essentials.el
section aboutexpreg
(tree-sitter mark syntactically) - The
unravel-essentials.el
section for Battery display - Finally, we provide the
unravel-essentials.el
module
- The
- The
unravel-completion.el
module- The
unravel-completion.el
settings for completion styles - The
unravel-completion.el
for theorderless
completion style - The
unravel-completion.el
settings to ignore letter casing - The
unravel-completion.el
settings for common interactions - The
unravel-completion.el
generic minibuffer UI settings - The
unravel-completion.el
settings for saving the history (savehist-mode
) - The
unravel-completion.el
settings for dynamic text expansion (dabbrev
) - The
unravel-completion.el
for in-buffer completion popup (corfu
) - The
unravel-completion.el
settings forconsult
- The
unravel-completion.el
section aboutembark
- The
unravel-completion.el
section to configure completion annotations (marginalia
) - The
unravel-completion.el
section forvertico
- Finally, we provide the
unravel-completion.el
module
- The
- The
unravel-langs.el
module- The
unravel-langs.el
settings for TAB - The
unravel-langs.el
settingsshow-paren-mode
- The
unravel-langs.el
settings foreldoc
- The
unravel-langs.el
settings foreglot
(LSP client) - The
unravel-langs.el
settings formarkdown-mode
- The
prot-emacs-langs.el
settings forcsv-mode
- The
unravel-langs.el
settings for spell checking (flyspell
) - The
unravel-langs.el
settings for code linting (flymake
) - The
unravel-langs.el
settings foroutline-minor-mode
- The
prot-emacs-langs.el
settings fordictionary
- The
unravel-langs.el
settings fordenote
(notes and file-naming) - Finally, we provide the
unravel-langs.el
module
- The
Here is what the generated directory structure should look like:
fd -e el -e org -E elpa | tree --fromfile
. ├── early-init.el ├── init.el ├── unravel-emacs.org └── unravel-modules ├── unravel-completion.el ├── unravel-essentials.el ├── unravel-langs.el └── unravel-theme.el
2 directories, 7 files
To make a change to this Emacs configuration, edit this file and then type C-c C-v C-t
(M-x org-babel-tangle
) to republish all the relevant files.
The early-init.el
file
This is the first file that Emacs reads when starting up. It should contain code that does not depend on any package or the proportions of the Emacs frame. In general, this early initialisation file is meant to set up a few basic things before Emacs produces the initial frame by delegating to the =init.el
The early-init.el
basic frame settings
;;;; No GUI
;; I do not use those graphical elements by default, but I do enable
;; them from time-to-time for testing purposes or to demonstrate
;; something. NEVER tell a beginner to disable any of these. They
;; are helpful.
(menu-bar-mode -1)
(scroll-bar-mode -1)
(tool-bar-mode -1)
The early-init.el
tweaks to startup time and garbage collection
;; A big contributor to startup times is garbage collection.
;; We up the gc threshold to temporarily prevent it from running, then
;; reset it later by enabling `gcmh-mode'. Not resetting it will cause
;; stuttering/freezes.
(setq gc-cons-threshold most-positive-fixnum)
If you have both .el and .elc files, load the newer one
;; When both .el and .elc / .eln files are available,
;; load the latest one.
(setq load-prefer-newer t)
The init.el
file
This is the main initialisation file of Emacs. Everything loads from here, even if it has been split into multiple files for convenience.
The init.el
tweaks to make native compilation silent
These warnings are unnecessarily scary.
The
--with-native-compilation=yes
build option of Emacs is very nice: it enables the "native compilation" of Emacs Lisp, translating it down to machine code. However, the default setting for reporting errors is set to a verbose value which, in my coaching experience, confuses users: it produces warnings for compilation issues that only the developer of the given package needs to deal with. These include innocuous facts like docstrings being wider than a certain character count. To make things even worse, the buffer that shows these warnings uses the stop sign character, resulting in a long list of lines with red spots everywhere, as if we have totally broken Emacs.
;; Make native compilation silent and prune its cache.
(when (native-comp-available-p)
(setq native-comp-async-report-warnings-errors 'silent) ; Emacs 28 with native compilation
(setq native-compile-prune-cache t)) ; Emacs 29
The init.el
setting to send custom-file
to oblivion
There is no need to use the M-x customize
infrastructure. It's easier to just rely on the init file instead.
I would prefer to just have an option to avoid the Custom infrastructure altogether, but this is not possible. So here we are…
;; Disable custom.el by making it disposable.
(setq custom-file (make-temp-file "emacs-custom-"))
The init.el
settings to enable commands disabled by default
These commands are actually useful, especially in org-mode.
;; Enable these commands which have been disabled by default
(mapc
(lambda (command)
(put command 'disabled nil))
'(list-timers narrow-to-region narrow-to-page upcase-region downcase-region))
The init.el
settings to disable unnecessary commands enabled by default
These commands are "unsafe", in that we should be using the alternatives (like vterm
and org
)
;; Disable these commands which have been enabled by default
(mapc
(lambda (command)
(put command 'disabled t))
'(eshell project-eshell overwrite-mode iconify-frame diary))
Add the modules folder to the load-path
This is where all the custom configuration sits for all the packages we use. We write configuration on a per-file basis instead of in a giant file, because these smaller files are more readable, approachable and shareable.
(mapc
(lambda (string)
(add-to-list 'load-path (locate-user-emacs-file string)))
'("unravel-modules"))
The init.el
settings for packages (package.el
)
The
package.el
is built into Emacs and is perfectly fine for my use-case. We do not need to load it explicitly, as it will be called byuse-package
when it needs it. Since the introduction of theearly-init.el
file, we also do not need to initialise the packages at this point: we activate the cache instead (Theearly-init.el
initialises the package cache).With regard to the settings here, make sure to read my article about package archives, pinning packages, and setting priorities: <https://protesilaos.com/codelog/2022-05-13-emacs-elpa-devel/>.
;;;; Packages
(setq package-vc-register-as-project nil) ; Emacs 30
(add-hook 'package-menu-mode-hook #'hl-line-mode)
;; Also read: <https://protesilaos.com/codelog/2022-05-13-emacs-elpa-devel/>
(setq package-archives
'(("gnu-elpa" . "https://elpa.gnu.org/packages/")
("gnu-elpa-devel" . "https://elpa.gnu.org/devel/")
("nongnu" . "https://elpa.nongnu.org/nongnu/")
("melpa" . "https://melpa.org/packages/")))
;; Highest number gets priority (what is not mentioned has priority 0)
(setq package-archive-priorities
'(("gnu-elpa" . 3)
("melpa" . 2)
("nongnu" . 1)))
;; Let `package-install' suggest upgrades for built-in packages too.
(setq package-install-upgrade-built-in t)
The init.el
macro to do nothing with Elisp code (prot-emacs-comment
)
This is something I learnt while studying Clojure: a
comment
macro that wraps some code, effectively commenting it out, while keeping indentation and syntax highlighting intact.What I have here is technically not commenting out the code, because the expansion of the macro is nil, not the actual code with comments around it.
(defmacro prot-emacs-comment (&rest body) "Do nothing with BODY and return nil, with no side effects." (declare (indent defun)) nil)
The above is an example. What I actually use is the following. It behaves the same as above, except when it reads a plist of the form
(:eval t)
. The idea is for me to quickly activate something I want to test by passing that to the macro. So here we have it:
(defmacro prot-emacs-comment (&rest body)
"Determine what to do with BODY.
If BODY contains an unquoted plist of the form (:eval t) then
return BODY inside a `progn'.
Otherwise, do nothing with BODY and return nil, with no side
effects."
(declare (indent defun))
(let ((eval))
(dolist (element body)
(when-let* (((plistp element))
(key (car element))
((eq key :eval))
(val (cadr element)))
(setq eval val
body (delq element body))))
(when eval `(progn ,@body))))
The init.el
macro to define abbreviations (prot-emacs-abbrev
)
[ Watch Prot's video: abbreviations with abbrev-mode (quick text expansion) (2024-02-03). ]
(defmacro prot-emacs-abbrev (table &rest definitions)
"Expand abbrev DEFINITIONS for the given TABLE.
DEFINITIONS is a sequence of (i) string pairs mapping the
abbreviation to its expansion or (ii) a string and symbol pair
making an abbreviation to a function."
(declare (indent 1))
(unless (zerop (% (length definitions) 2))
(error "Uneven number of key+command pairs"))
`(if (abbrev-table-p ,table)
(progn
,@(mapcar
(lambda (pair)
(let ((abbrev (nth 0 pair))
(expansion (nth 1 pair)))
(if (stringp expansion)
`(define-abbrev ,table ,abbrev ,expansion)
`(define-abbrev ,table ,abbrev "" ,expansion))))
(seq-split definitions 2)))
(error "%s is not an abbrev table" ,table)))
The init.el
final part to load the individual modules
Now we are ready to load our per-module configuration files:
(require 'unravel-theme)
(require 'unravel-essentials)
;; (require 'unravel-modeline)
(require 'unravel-completion)
;; (require 'unravel-search)
;; (require 'unravel-dired)
;; (require 'unravel-window)
;; (require 'unravel-git)
;; (require 'unravel-org)
(require 'unravel-langs)
The unravel-theme.el
module
This module defines everything related to the aesthetics of Emacs.
;;; Everything related to the look of Emacs
The unravel-theme.el
section for cool, modern themes (ef-themes
)
I use themes from the ef-themes
package exclusively.
Prot is the lead developer and maintainer of this package.
- Package name (GNU ELPA):
ef-themes
- Official manual: <https://protesilaos.com/emacs/ef-themes>
- Change log: <https://protesilaos.com/emacs/ef-themes-changelog>
-
Git repositories:
- GitHub: <https://github.com/protesilaos/ef-themes>
- GitLab: <https://gitlab.com/protesilaos/ef-themes>
;;; The Ef (εὖ) themes
;; The themes are customisable. Read the manual:
;; <https://protesilaos.com/emacs/ef-themes>.
(use-package ef-themes
:ensure t
:demand t
:bind
(("<f5>" . ef-themes-rotate)
("C-<f5>" . ef-themes-select))
:config
(setq ef-themes-to-toggle '(ef-elea-light ef-elea-dark)
ef-themes-variable-pitch-ui t
ef-themes-mixed-fonts t
ef-themes-headings ; read the manual's entry of the doc string
'((0 . (variable-pitch light 1.9))
(1 . (variable-pitch light 1.8))
(2 . (variable-pitch regular 1.7))
(3 . (variable-pitch regular 1.6))
(4 . (variable-pitch regular 1.5))
(5 . (variable-pitch 1.4)) ; absence of weight means `bold'
(6 . (variable-pitch 1.3))
(7 . (variable-pitch 1.2))
(agenda-date . (semilight 1.5))
(agenda-structure . (variable-pitch light 1.9))
(t . (variable-pitch 1.1))))
(ef-themes-select 'ef-elea-light))
The unravel-theme.el
section for lin
lin
is an improvement on hl-line-mode
.
Prot is the lead developer and maintainer of this package.
- Package name (GNU ELPA):
lin
- Official manual: <https://protesilaos.com/emacs/lin>
- Change log: <https://protesilaos.com/emacs/lin-changelog>
-
Git repositories:
- GitHub: <https://github.com/protesilaos/lin>
- GitLab: <https://gitlab.com/protesilaos/lin>
;;;; Lin
;; Read the lin manual: <https://protesilaos.com/emacs/lin>.
(use-package lin
:ensure t
:hook (after-init . lin-global-mode) ; applies to all `lin-mode-hooks'
:config
(setopt lin-face 'lin-cyan))
The unravel-theme.el
section for spacious-padding
spacious-padding
gives us a comfortable reading experience.
Prot is the lead developer and maintainer of this package.
Inspiration for this package comes from Nicolas Rougier's impressive designs and Daniel Mendler's
org-modern
package.
- Package name (GNU ELPA):
spacious-padding
- Official manual: <https://protesilaos.com/emacs/spacious-padding>
-
Git repositories:
;;;; Increase padding of windows/frames
;; <https://protesilaos.com/codelog/2023-06-03-emacs-spacious-padding/>.
(use-package spacious-padding
:ensure t
:if (display-graphic-p)
:hook (after-init . spacious-padding-mode)
:init
;; These are the defaults, but I keep it here for visiibility.
(setq spacious-padding-widths
'(:internal-border-width 30
:header-line-width 4
:mode-line-width 6
:tab-width 4
:right-divider-width 30
:scroll-bar-width 8
:left-fringe-width 20
:right-fringe-width 20))
;; Read the doc string of `spacious-padding-subtle-mode-line' as
;; it is very flexible.
(setq spacious-padding-subtle-mode-line t))
The unravel-theme.el
section for rainbow-mode
This package produces an in-buffer preview of a colour value. I use those while developing my themes, hence the
prot/rainbow-mode-in-themes
to activaterainbow-mode
if I am editing a theme file.
;;;; Rainbow mode for colour previewing (rainbow-mode.el)
(use-package rainbow-mode
:ensure t
:init
(setq rainbow-ansi-colors nil)
(setq rainbow-x-colors nil)
(defun prot/rainbow-mode-in-themes ()
(when-let* ((file (buffer-file-name))
((derived-mode-p 'emacs-lisp-mode))
((string-match-p "-theme" file)))
(rainbow-mode 1)))
:bind ( :map ctl-x-x-map
("c" . rainbow-mode)) ; C-x x c
:hook (emacs-lisp-mode . prot/rainbow-mode-in-themes))
The unravel-theme.el
section for cursory
My
cursory
package provides a thin wrapper around built-in variables that affect the style of the Emacs cursor on graphical terminals. The intent is to allow the user to define preset configurations such as "block with slow blinking" or "bar with fast blinking" and set them on demand. The use-case for such presets is to adapt to evolving interface requirements and concomitant levels of expected comfort, such as in the difference between writing and reading.
Prot is the lead developer and maintainer.
- Package name (GNU ELPA):
cursory
- Official manual: <https://protesilaos.com/emacs/cursory>
- Change log: <https://protesilaos.com/emacs/cursory-changelog>
-
Git repositories:
- GitHub: <https://github.com/protesilaos/cursory>
- GitLab: <https://gitlab.com/protesilaos/cursory>
;;; Cursor appearance (cursory)
;; Read the manual: <https://protesilaos.com/emacs/cursory>.
(use-package cursory
:ensure t
:demand t
:if (display-graphic-p)
:config
(setq cursory-presets
'((box
:blink-cursor-interval 1.2)
(box-no-blink
:blink-cursor-mode -1)
(bar
:cursor-type (bar . 2)
:blink-cursor-interval 0.8)
(bar-no-other-window
:inherit bar
:cursor-in-non-selected-windows nil)
(bar-no-blink
:cursor-type (bar . 2)
:blink-cursor-mode -1)
(underscore
:cursor-type (hbar . 3)
:blink-cursor-interval 0.3
:blink-cursor-blinks 50)
(underscore-no-other-window
:inherit underscore
:cursor-in-non-selected-windows nil)
(underscore-thick
:cursor-type (hbar . 8)
:blink-cursor-interval 0.3
:blink-cursor-blinks 50
:cursor-in-non-selected-windows (hbar . 3))
(underscore-thick-no-blink
:blink-cursor-mode -1
:cursor-type (hbar . 8)
:cursor-in-non-selected-windows (hbar . 3))
(t ; the default values
:cursor-type box
:cursor-in-non-selected-windows hollow
:blink-cursor-mode 1
:blink-cursor-blinks 10
:blink-cursor-interval 0.2
:blink-cursor-delay 0.2)))
;; I am using the default values of `cursory-latest-state-file'.
;; Set last preset or fall back to desired style from `cursory-presets'.
(cursory-set-preset (or (cursory-restore-latest-preset) 'box))
(cursory-mode 1))
The unravel-theme.el
section for theme-buffet
The theme-buffet
package automatically changes the theme based on time of day.
Bruno Boal is the lead developer and Prot is a co-maintainer.
- Package name (GNU ELPA):
theme-buffet
-
Git repo on SourceHut: <https://git.sr.ht/~bboal/theme-buffet>
-
Mirrors:
- GitHub: <https://github.com/BBoal/theme-buffet>
- Codeberg: <https://codeberg.org/BBoal/theme-buffet>
-
- Mailing list: <https://lists.sr.ht/~bboal/general-issues>
;;;; Theme buffet
;; <https://git.sr.ht/~bboal/theme-buffet>
(use-package theme-buffet
:ensure t
:after (:any modus-themes ef-themes)
:defer 1
:config
(let ((modus-themes-p (featurep 'modus-themes))
(ef-themes-p (featurep 'ef-themes)))
(setq theme-buffet-menu 'end-user)
(setq theme-buffet-time-offset 0)
(setq theme-buffet-end-user
'(:night (ef-dark ef-winter ef-autumn ef-night ef-duo-dark ef-symbiosis ef-owl)
:morning (ef-light ef-cyprus ef-spring ef-frost ef-duo-light ef-eagle)
:afternoon (ef-arbutus ef-day ef-kassio ef-summer ef-elea-light ef-maris-light ef-melissa-light ef-trio-light ef-reverie)
:evening (ef-rosa ef-elea-dark ef-maris-dark ef-melissa-dark ef-trio-dark ef-dream)))
(when (or modus-themes-p ef-themes-p)
(theme-buffet-timer-hours 2))))
The unravel-theme.el
section about fontaine
[ Watch Prot's video: Customise Emacs fonts (2024-01-16) ]
My
fontaine
package allows the user to define detailed font configurations and set them on demand. For example, one can have aregular-editing
preset and another forpresentation-mode
(these are arbitrary, user-defined symbols): the former uses small fonts which are optimised for writing, while the latter applies typefaces that are pleasant to read at comfortable point sizes.
Prot is the lead developer and maintainer.
- Package name (GNU ELPA):
fontaine
- Official manual: <https://protesilaos.com/emacs/fontaine>
- Change log: <https://protesilaos.com/emacs/fontaine-changelog>
-
Git repositories:
- GitHub: <https://github.com/protesilaos/fontaine>
- GitLab: <https://gitlab.com/protesilaos/fontaine>
Another section defines some complementary functionality
(The unravel-theme.el
section about variable-pitch-mode
and font resizing).
;;;; Fontaine (font configurations)
;; Read the manual: <https://protesilaos.com/emacs/fontaine>
(use-package fontaine
:ensure t
:if (display-graphic-p)
:hook
;; Persist the latest font preset when closing/starting Emacs and
;; while switching between themes.
((after-init . fontaine-mode)
(after-init . (lambda ()
;; Set last preset or fall back to desired style from `fontaine-presets'.
(fontaine-set-preset (or (fontaine-restore-latest-preset) 'regular)))))
:config
;; This is defined in Emacs C code: it belongs to font settings.
(setq x-underline-at-descent-line nil)
;; And this is for Emacs28.
(setq-default text-scale-remap-header-line t)
;; This is the default value. Just including it here for
;; completeness.
(setq fontaine-latest-state-file (locate-user-emacs-file "fontaine-latest-state.eld"))
(setq fontaine-presets
'((small
:default-height 80)
(regular) ; like this it uses all the fallback values and is named `regular'
(medium
:default-weight semilight
:default-height 115
:bold-weight extrabold)
(large
:inherit medium
:default-height 150)
(live-stream
:default-family "Iosevka"
:default-height 150
:default-weight medium
:fixed-pitch-family "Iosevka"
:variable-pitch-family "Iosevka"
:bold-weight extrabold)
(presentation
:default-height 180)
(jumbo
:default-height 260)
(t
;; I keep all properties for didactic purposes, but most can be
;; omitted. See the fontaine manual for the technicalities:
;; <https://protesilaos.com/emacs/fontaine>.
:default-family "Iosevka"
:default-weight regular
:default-slant normal
:default-width normal
:default-height 100
:fixed-pitch-family "Iosevka Fixed"
:fixed-pitch-weight nil
:fixed-pitch-slant nil
:fixed-pitch-width nil
:fixed-pitch-height 1.0
:fixed-pitch-serif-family nil
:fixed-pitch-serif-weight nil
:fixed-pitch-serif-slant nil
:fixed-pitch-serif-width nil
:fixed-pitch-serif-height 1.0
:variable-pitch-family "Iosevka"
:variable-pitch-weight nil
:variable-pitch-slant nil
:variable-pitch-width nil
:variable-pitch-height 1.0
:mode-line-active-family "Iosevka Term"
:mode-line-active-weight nil
:mode-line-active-slant nil
:mode-line-active-width nil
:mode-line-active-height 1.0
:mode-line-inactive-family "Iosevka Term"
:mode-line-inactive-weight nil
:mode-line-inactive-slant nil
:mode-line-inactive-width nil
:mode-line-inactive-height 1.0
:header-line-family "Iosevka Term"
:header-line-weight nil
:header-line-slant nil
:header-line-width nil
:header-line-height 1.0
:line-number-family "Iosevka Term"
:line-number-weight nil
:line-number-slant nil
:line-number-width nil
:line-number-height 1.0
:tab-bar-family "Iosevka Term"
:tab-bar-weight nil
:tab-bar-slant nil
:tab-bar-width nil
:tab-bar-height 1.0
:tab-line-family "Iosevka Term"
:tab-line-weight nil
:tab-line-slant nil
:tab-line-width nil
:tab-line-height 1.0
:bold-family "Iosevka"
:bold-slant nil
:bold-weight bold
:bold-width nil
:bold-height 1.0
:italic-family "Iosevka"
:italic-weight nil
:italic-slant italic
:italic-width nil
:italic-height 1.0
:line-spacing nil))))
The unravel-theme.el
section about variable-pitch-mode
and font resizing
[ Watch Prot's video: Customise Emacs fonts (2024-01-16) ]
The built-in
variable-pitch-mode
makes the current buffer use a proportionately spaced font. In technical terms, it remaps thedefault
face tovariable-pitch
, so whatever applies to the latter takes effect over the former. I take care of their respective font families in myfontaine
setup (Theprot-emacs-theme.el
section aboutfontaine
).I want to activate
variable-pitch-mode
in all buffers where I normally focus on prose. The exact mode hooks are specified in the variableprot/enable-variable-pitch-in-hooks
. Exceptions to these are major modes that I do not consider related to prose (and which in my opinion should not be derived fromtext-mode
): these are excluded in the functionprot/enable-variable-pitch
.
;;;;; `variable-pitch-mode' setup
(use-package face-remap
:ensure nil
:functions prot/enable-variable-pitch
:bind ( :map ctl-x-x-map
("v" . variable-pitch-mode))
:hook ((text-mode notmuch-show-mode elfeed-show-mode) . prot/enable-variable-pitch)
:config
;; NOTE 2022-11-20: This may not cover every case, though it works
;; fine in my workflow. I am still undecided by EWW.
(defun prot/enable-variable-pitch ()
(unless (derived-mode-p 'mhtml-mode 'nxml-mode 'yaml-mode)
(variable-pitch-mode 1)))
;;;;; Resize keys with global effect
:bind
;; Emacs 29 introduces commands that resize the font across all
;; buffers (including the minibuffer), which is what I want, as
;; opposed to doing it only in the current buffer. The keys are the
;; same as the defaults.
(("C-x C-=" . global-text-scale-adjust)
("C-x C-+" . global-text-scale-adjust)
("C-x C-0" . global-text-scale-adjust)))
The unravel-essentials.el
module
The unravel-essentials.el
block with basic configurations
;;; Essential configurations
(use-package emacs
:ensure nil
:demand t
:config
;;;; General settings and common custom functions
(setq help-window-select t)
(setq next-error-recenter '(4)) ; center of the window
(setq find-library-include-other-files nil) ; Emacs 29
(setq tramp-connection-timeout (* 60 10)) ; seconds
(setq save-interprogram-paste-before-kill t)
(setq mode-require-final-newline t)
(setq-default truncate-partial-width-windows nil)
(setq eval-expression-print-length nil)
(setq kill-do-not-save-duplicates t)
(setq scroll-error-top-bottom t)
(setq echo-keystrokes-help t) ; Emacs 30
(setq epa-keys-select-method 'minibuffer)) ; Emacs 30
The unravel-essentials.el
configuration to track recently visited files (recentf
)
(use-package recentf
:ensure nil
:hook (after-init . recentf-mode)
:config
(setq recentf-max-saved-items 100)
(setq recentf-max-menu-items 25) ; I don't use the `menu-bar-mode', but this is good to know
(setq recentf-save-file-modes nil)
(setq recentf-keep nil)
(setq recentf-auto-cleanup nil)
(setq recentf-initialize-file-name-history nil)
(setq recentf-filename-handlers nil)
(setq recentf-show-file-shortcuts-flag nil))
The unravel-essentials.el
settings for bookmarks
Bookmarks are compartments that store arbitrary information about a file or buffer. The records are used to recreate that file/buffer inside of Emacs. Put differently, we can easily jump back to a file or directory (or anything that has a bookmark recorder+handler, really). Use the
bookmark-set
command (C-x r m
by default) to record a bookmark and then visit one of your bookmarks withbookmark-jump
(C-x r b
by default).
;;;; Built-in bookmarking framework (bookmark.el)
(use-package bookmark
:ensure nil
:commands (bookmark-set bookmark-jump bookmark-bmenu-list)
:hook (bookmark-bmenu-mode . hl-line-mode)
:config
(setq bookmark-use-annotations nil)
(setq bookmark-automatically-show-annotations nil)
(setq bookmark-fringe-mark nil) ; Emacs 29 to hide bookmark fringe icon
;; Write changes to the bookmark file as soon as 1 modification is
;; made (addition or deletion). Otherwise Emacs will only save the
;; bookmarks when it closes, which may never happen properly
;; (e.g. power failure).
(setq bookmark-save-flag 1))
The unravel-essentials.el
settings for registers
[ Watch Prot's video: Mark and register basics (2023-06-28). ]
Much like bookmarks, registers store data that we can reinstate quickly (The
unravel-essentials.el
settings for bookmarks). A common use-case is to write some text to a register and then insert that text by calling the given register. This is much better than relying on thekill-ring
, because registers are meant to be overwritten by the user, whereas thekill-ring
accumulates lots of text that we do not necessarily need.To me, registers are essential for keyboard macros. By default, registers do not persist between Emacs sessions, though I do need to re-use them from time to time, hence the arrangement to record them with
savehist-mode
(Theunravel-completion.el
settings for saving the history (savehist-mode
)).
;;;; Registers (register.el)
(use-package register
:ensure nil
:defer t ; its commands are autoloaded, so this will be loaded then
:config
(setq register-preview-delay 0.8
register-preview-function #'register-preview-default)
(with-eval-after-load 'savehist
(add-to-list 'savehist-additional-variables 'register-alist)))
The unravel-essentials.el
section for delete-selection-mode
;;;; Delete selection
(use-package delsel
:ensure nil
:hook (after-init . delete-selection-mode))
The unravel-essentials.el
settings for tooltips
With these settings in place, Emacs will use its own faces and frame infrastructure to display tooltips. I prefer it this way because then we can benefit from the text properties that can be added to these messages (e.g. a different colour or a slant).
;;;; Tooltips (tooltip-mode)
(use-package tooltip
:ensure nil
:hook (after-init . tooltip-mode)
:config
(setq tooltip-delay 0.5
tooltip-short-delay 0.5
x-gtk-use-system-tooltips t
tooltip-frame-parameters
'((name . "tooltip")
(internal-border-width . 10)
(border-width . 0)
(no-special-glyphs . t))))
The unravel-essentials.el
arrangement to run Emacs as a server
The "server" is functionally like the daemon, except it is run by the first Emacs frame we launch. With a running server, we can connect to it through a new
emacsclient
call. This is useful if we want to launch new frames that share resources with the existing running process.
;;;; Emacs server (allow emacsclient to connect to running session)
(use-package server
:ensure nil
:defer 1
:config
(setq server-client-instructions nil)
(unless (server-running-p)
(server-start)))
The prot-emacs-essentials.el
section about expreg
(tree-sitter mark syntactically)
The
expreg
package by Yuan Fu (aka casouri) uses the tree-sitter framework to incrementally expand the region from the smallest to the largest syntactic unit in the given context. This is a powerful feature, though it (i) requires Emacs to be built with tree-sitter support and (ii) for the user to be running a major mode that is designed for tree-sitter (Lisp seems to work regardless).The package offers the
expreg-expand
andexpreg-contract
commands.
;;; Mark syntactic constructs efficiently if tree-sitter is available (expreg)
(when (treesit-available-p)
(use-package expreg
:ensure t
:functions (prot/expreg-expand prot/expreg-expand-dwim)
:bind ("C-M-SPC" . prot/expreg-expand-dwim) ; overrides `mark-sexp'
:config
(defun prot/expreg-expand (n)
"Expand to N syntactic units, defaulting to 1 if none is provided interactively."
(interactive "p")
(dotimes (_ n)
(expreg-expand)))
(defun prot/expreg-expand-dwim ()
"Do-What-I-Mean `expreg-expand' to start with symbol or word.
If over a real symbol, mark that directly, else start with a
word. Fall back to regular `expreg-expand'."
(interactive)
(let ((symbol (bounds-of-thing-at-point 'symbol)))
(cond
((equal (bounds-of-thing-at-point 'word) symbol)
(prot/expreg-expand 1))
(symbol (prot/expreg-expand 2))
(t (expreg-expand)))))))
The unravel-essentials.el
section for Battery display
;;;; Show battery status on the mode line (battery.el)
(use-package battery
:ensure nil
:hook (after-init . display-battery-mode)
:config
(setq battery-mode-line-format
(cond
((eq battery-status-function #'battery-linux-proc-acpi)
"⏻ %b%p%%,%d°C ")
(battery-status-function
"⏻ %b%p%% "))))
The unravel-completion.el
module
The unravel-completion.el
settings for completion styles
The
completion-styles
are pattern matching algorithms. They interpret user input and match candidates accordingly.
- emacs22
- Prefix completion that only operates on the text before point. If we are in
prefix|suffix
, with|
representing the cursor, it will consider everything that expandsprefix
and then add back to it thesuffix
.- basic
- Prefix completion that also accounts for the text after point. Using the above example, this one will consider patterns that match all of
emacs22
as well as anything that completessuffix
.- partial-completion
- This is used for file navigation. Instead of typing out a full path like
~/.local/share/fonts
, we do~/.l/s/f
or variants thereof to make the matches unique such as~/.l/sh/fon
. It is a joy to navigate the file system in this way.- substring
- Matches the given sequence of characters literally regardless of where it is in a word. So
pro
will matchprofessional
as well asreproduce
.- flex
- Completion of an in-order subset of characters. It does not matter where the charactes are in the word, so long as they are encountered in the given order. The input
lad
will thus matchlist-faces-display
as well aspulsar-highlight-dwim
.- initials
- Completion of acronyms and initialisms. Typing
lfd
will thus matchlist-faces-display
. This completion style can also be used for file system navigation, though I prefer to only havepartial-completion
handle that task.- orderless
- This is the only completion style I use which is not built into Emacs and which I tweak further in a separate section (The
prot-emacs-completion.el
for theorderless
completion style). It matches patterns out-of-order. Patterns are typically words separated by spaces, though they can also be regular expressions, and even styles that are the same as the aforementionedflex
andinitials
.Now that you know about the completion styles I use, take a look at the value of my
completion-styles
. You will notice thatorderless
, which is the most powerful/flexible is placed last. I do this because Emacs tries the styles in the given order from left to right, moving the next one until it finds a match. As such, I usually want to start with tight matches (e.g.li-fa-di
forlist-faces-display
) and only widen the scope of the search as I need to. This is easy to do because none of the built-in completion styles parses the empty space, so as soon as I type a space after some characters I am usingorderless
.
(There are more details in Prot's file, for the interested reader)
;;; General minibuffer settings
(use-package minibuffer
:ensure nil
:config
;;;; Completion styles
(setq completion-styles '(basic substring initials flex orderless)) ; also see `completion-category-overrides'
(setq completion-pcm-leading-wildcard t) ; Emacs 31: make `partial-completion' behave like `substring'
;; Reset all the per-category defaults so that (i) we use the
;; standard `completion-styles' and (ii) can specify our own styles
;; in the `completion-category-overrides' without having to
;; explicitly override everything.
(setq completion-category-defaults nil)
;; A non-exhaustve list of known completion categories:
;;
;; - `bookmark'
;; - `buffer'
;; - `charset'
;; - `coding-system'
;; - `color'
;; - `command' (e.g. `M-x')
;; - `customize-group'
;; - `environment-variable'
;; - `expression'
;; - `face'
;; - `file'
;; - `function' (the `describe-function' command bound to `C-h f')
;; - `info-menu'
;; - `imenu'
;; - `input-method'
;; - `kill-ring'
;; - `library'
;; - `minor-mode'
;; - `multi-category'
;; - `package'
;; - `project-file'
;; - `symbol' (the `describe-symbol' command bound to `C-h o')
;; - `theme'
;; - `unicode-name' (the `insert-char' command bound to `C-x 8 RET')
;; - `variable' (the `describe-variable' command bound to `C-h v')
;; - `consult-grep'
;; - `consult-isearch'
;; - `consult-kmacro'
;; - `consult-location'
;; - `embark-keybinding'
;;
(setq completion-category-overrides
;; NOTE 2021-10-25: I am adding `basic' because it works better as a
;; default for some contexts. Read:
;; <https://debbugs.gnu.org/cgi/bugreport.cgi?bug=50387>.
;;
;; `partial-completion' is a killer app for files, because it
;; can expand ~/.l/s/fo to ~/.local/share/fonts.
;;
;; If `basic' cannot match my current input, Emacs tries the
;; next completion style in the given order. In other words,
;; `orderless' kicks in as soon as I input a space or one of its
;; style dispatcher characters.
'((file (styles . (basic partial-completion orderless)))
(bookmark (styles . (basic substring)))
(library (styles . (basic substring)))
(embark-keybinding (styles . (basic substring)))
(imenu (styles . (basic substring orderless)))
(consult-location (styles . (basic substring orderless)))
(kill-ring (styles . (emacs22 orderless)))
(eglot (styles . (emacs22 substring orderless))))))
The unravel-completion.el
for the orderless
completion style
The
orderless
package by Omar Antolín Camarena provides one of the completion styles that I use (Theprot-emacs-completion.el
settings for completion styles). It is a powerful pattern matching algorithm that parses user input and interprets it out-of-order, so thatin pa
will coverinsert-pair
as well aspackage-install
. Components of the search are space-separated, by default, though we can modify the user optionorderless-component-separator
to have something else (but I cannot think of a better value). In the section about completion styles, I explain how I useorderless
and why its power does not result in lots of false positives.
;;; Orderless completion style
(use-package orderless
:ensure t
:demand t
:after minibuffer
:config
;; Remember to check my `completion-styles' and the
;; `completion-category-overrides'.
(setq orderless-matching-styles '(orderless-prefixes orderless-regexp))
;; SPC should never complete: use it for `orderless' groups.
;; The `?' is a regexp construct.
:bind ( :map minibuffer-local-completion-map
("SPC" . nil)
("?" . nil)))
The unravel-completion.el
settings to ignore letter casing
I never really need to match letters case-sensitively in the minibuffer. Let's have everything ignore casing by default.
(setq completion-ignore-case t)
(setq read-buffer-completion-ignore-case t)
(setq-default case-fold-search t) ; For general regexp
(setq read-file-name-completion-ignore-case t)
The unravel-completion.el
settings for common interactions
Here I combine several small tweaks to improve the overall minibuffer experience.
- The need to
resize-mini-windows
arises on some occasions where Emacs has to show text spanning multiple lines in the "mini windows". A common scenario for me is in Org mode buffers where I set theTODO
keyword of a task withC-c C-t
(M-x org-todo
) and have this as my setting:(setq org-use-fast-todo-selection 'expert)
Otherwise, this is not an issue anyway and I may also like other options fororg-use-fast-todo-selection
.- The
read-answer-short
is complementary touse-short-answers
. This is about providing the shorter version to some confirmation prompt, such asy
instead ofyes
.- The
echo-keystrokes
is set to a low value to show in the echo area the incomplete key sequence I have just typed. This is especially helpful for demonstration purposes but also to double check that I did not mistype something (I cannot touch-type, so this happens a lot).- The
minibuffer-prompt-properties
and advice tocompleting-read-multiple
make it so that (i) the minibuffer prompt is not accessible with regular motions to avoid mistakes and (ii) prompts that complete multiple targets show an indicator about this fact. With regard to the latter in particular, we have prompts like that of Org to set tags for a heading (withC-c C-q
elseM-x org-set-tags-command
) where more than one candidate can be provided using completion, provided each candidate is separated by thecrm-separator
(a comma by default, though Org uses:
in that scenario). Remember that when using completion in the minibuffer, you can hitTAB
to expand the selected choice without exiting with it. For cases when multiple candidates can be selected, you select the candidate,TAB
, then input thecrm-separator
, and repeat until you are done selecting at which point you typeRET
.- Finally the
file-name-shadow-mode
is a neat little feature to remove the "shadowed" part of a file prompt while using something likeC-x C-f
(M-x find-file
). File name shadowing happens when we invokefind-file
and instead of first deleting the contents of the minibuffer, we start typing out the file system path we wish to visit. For example, I am in~/Git/Projects/
and type directly after it something like~/.local/share/fonts/
, so Emacs displays~/Git/Projects/~/.local/share/fonts/
with the original part greyed out. Withfile-name-shadow-mode
the "shadowed" part is removed altogether. This is especially nice when combined with the completion style calledpartial-completion
(Theunravel-completion.el
settings for completion styles).
(use-package rfn-eshadow
:ensure nil
:hook (minibuffer-setup . cursor-intangible-mode)
:config
;; Not everything here comes from rfn-eshadow.el, but this is fine.
(setq resize-mini-windows t)
(setq read-answer-short t) ; also check `use-short-answers' for Emacs28
(setq echo-keystrokes 0.25)
(setq kill-ring-max 60) ; Keep it small
;; Do not allow the cursor to move inside the minibuffer prompt. I
;; got this from the documentation of Daniel Mendler's Vertico
;; package: <https://github.com/minad/vertico>.
(setq minibuffer-prompt-properties
'(read-only t cursor-intangible t face minibuffer-prompt))
;; Add prompt indicator to `completing-read-multiple'. We display
;; [`completing-read-multiple': <separator>], e.g.,
;; [`completing-read-multiple': ,] if the separator is a comma. This
;; is adapted from the README of the `vertico' package by Daniel
;; Mendler. I made some small tweaks to propertize the segments of
;; the prompt.
(defun crm-indicator (args)
(cons (format "[`completing-read-multiple': %s] %s"
(propertize
(replace-regexp-in-string
"\\`\\[.*?]\\*\\|\\[.*?]\\*\\'" ""
crm-separator)
'face 'error)
(car args))
(cdr args)))
(advice-add #'completing-read-multiple :filter-args #'crm-indicator)
(file-name-shadow-mode 1))
The unravel-completion.el
generic minibuffer UI settings
These are some settings for the default completion user interface.
(use-package minibuffer
:ensure nil
:demand t
:config
(setq completions-format 'one-column)
(setq completion-show-help nil)
(setq completion-auto-help 'always)
(setq completion-auto-select nil)
(setq completions-detailed t)
(setq completion-show-inline-help nil)
(setq completions-max-height 6)
(setq completions-header-format (propertize "%s candidates:\n" 'face 'bold-italic))
(setq completions-highlight-face 'completions-highlight)
(setq minibuffer-completion-auto-choose t)
(setq minibuffer-visible-completions t) ; Emacs 30
(setq completions-sort 'historical))
The unravel-completion.el
settings for saving the history (savehist-mode
)
Minibuffer prompts can have their own history. When they do not, they share a common history of user inputs. Emacs keeps track of that history in the current session, but loses it as soon as we close it. With
savehist-mode
enabled, all minibuffer histories are written to a file and are restored when we start Emacs again.
Since we are already recording minibuffer histories, we can instruct
savehist-mode
to also keep track of additional variables and restore them next time we use Emacs. Hencesavehist-additional-variables
. I do this in a few of places:
- The
unravel-completion.el
for in-buffer completion popup and preview (corfu
)- The
unravel-essentials.el
settings for registersNote that the user option
history-length
applies to each individual history variable: it is not about all histories combined.Overall, I am happy with this feature and benefit from it on a daily basis.
;;;; `savehist' (minibuffer and related histories)
(use-package savehist
:ensure nil
:hook (after-init . savehist-mode)
:config
(setq savehist-file (locate-user-emacs-file "savehist"))
(setq history-length 100)
(setq history-delete-duplicates t)
(setq savehist-save-minibuffer-history t)
(add-to-list 'savehist-additional-variables 'kill-ring))
The unravel-completion.el
settings for dynamic text expansion (dabbrev
)
The built-in
dabbrev
package provides a text completion method that reads the contents of a buffer and expands the text before the cursor to match possible candidates. This is done withM-/
(dabbrev-expand
) which is what I use most of the time to perform in-buffer completions.The term "dabbrev" stands for "dynamic abbreviation". Emacs also has static, user-defined abbreviations (Settings for static text expansion (
abbrev
)).
(use-package dabbrev
:ensure nil
:commands (dabbrev-expand dabbrev-completion)
:config
;;;; `dabbrev' (dynamic word completion (dynamic abbreviations))
(setq dabbrev-abbrev-char-regexp "\\sw\\|\\s_")
(setq dabbrev-abbrev-skip-leading-regexp "[$*/=~']")
(setq dabbrev-backward-only nil)
(setq dabbrev-case-distinction 'case-replace)
(setq dabbrev-case-fold-search nil)
(setq dabbrev-case-replace 'case-replace)
(setq dabbrev-check-other-buffers t)
(setq dabbrev-eliminate-newlines t)
(setq dabbrev-upcase-means-case-search t)
(setq dabbrev-ignored-buffer-modes
'(archive-mode image-mode docview-mode pdf-view-mode)))
The unravel-completion.el
for in-buffer completion popup (corfu
)
I generally do not rely on in-buffer text completion. I feel it slows me down and distracts me. When I do, however, need to rely on it, I have the
corfu
package by Daniel Mendler: it handles the task splendidly as it works with Emacs' underlying infrastructure forcompletion-at-point-functions
.Completion is triggered with the
TAB
key, which produces a popup where the cursor is. The companioncorfu-popupinfo-mode
will show a secondary documentation popup if we move over a candidate but do not do anything with it.Also see the
prot-emacs-completion.el
settings for dynamic text expansion (dabbrev
).
;;; Corfu (in-buffer completion popup)
(use-package corfu
:ensure t
:hook (after-init . global-corfu-mode)
;; I also have (setq tab-always-indent 'complete) for TAB to complete
;; when it does not need to perform an indentation change.
:bind (:map corfu-map ("<tab>" . corfu-complete))
:config
(setq corfu-preview-current nil)
(setq corfu-min-width 20)
(setq corfu-popupinfo-delay '(1.25 . 0.5))
(corfu-popupinfo-mode 1) ; shows documentation after `corfu-popupinfo-delay'
;; Sort by input history (no need to modify `corfu-sort-function').
(with-eval-after-load 'savehist
(corfu-history-mode 1)
(add-to-list 'savehist-additional-variables 'corfu-history)))
The unravel-completion.el
settings for consult
consult~ is another wonderful package by Daniel Mendler. It provides a number of commands that turbocharge the minibuffer with advanced capabilities for filtering, asynchronous input, and previewing of the current candidate's context.
- A case where filtering is in use is the
consult-buffer
command, which many users have as a drop-in replacement to the genericC-x b
(M-x switch-to-buffer
). It is a one-stop-shop for buffers, recently visited files (ifrecentf-mode
is used—I don't), bookmarks (Theunravel-essentials.el
settings for bookmarks), and, in principle, anything else that defines a source for this interface. To filter those source, we can type at the empty minibufferb SPC
, which will insert a filter specific to buffers. Delete back to remove the[Buffer]
filter and insert another filter. Available filters are displayed by typing?
at the prompt (I define it this way to call the commandconsult-narrow-help
). Every multi-source command fromconsult
relies on this paradigm.- Asynchronous input pertains to the intersection between Emacs and external search programs. A case in point is
consult-grep
, which calls the system'sgrep
program. The prompt distinguishes between what is sent to the external program and what is only shown to Emacs by wrapping the former inside of#
. So the input#prot-#completion
will sendprot-
to thegrep
program and then usecompletion
inside of the minibuffer to perform the subsequent pattern-matching (e.g. with help fromorderless
(Theunravel-completion.el
for theorderless
completion style). The part that is sent to the external program does not block Emacs. It is handled asynchronously, so everything stays responsive.- As for previewing,
consult
commands show the context of the current match and update the window as we move between completion candidates in the minibuffer. For example, theconsult-line
command performs an in-buffer search and lets us move between matches in the minibuffer while seeing in the window above what the surrounding text looks like. This is an excellent feature when we are trying to find something and do not quite remember all the search terms to narrow down to it simply by typing at the minibuffer prompt.Also check: The
unravel-search.el
module.
;;; Enhanced minibuffer commands (consult.el)
(use-package consult
:ensure t
:hook (completion-list-mode . consult-preview-at-point-mode)
:bind
(:map global-map
("M-g M-g" . consult-goto-line)
("M-K" . consult-keep-lines) ; M-S-k is similar to M-S-5 (M-%)
("M-F" . consult-focus-lines) ; same principle
("M-s M-b" . consult-buffer)
("M-s M-f" . consult-find)
("M-s M-g" . consult-grep)
("M-s M-h" . consult-history)
("M-s M-i" . consult-imenu)
("M-s M-l" . consult-line)
("M-s M-m" . consult-mark)
("M-s M-y" . consult-yank-pop)
("M-s M-s" . consult-outline)
:map consult-narrow-map
("?" . consult-narrow-help))
:config
(setq consult-line-numbers-widen t)
;; (setq completion-in-region-function #'consult-completion-in-region)
(setq consult-async-min-input 3)
(setq consult-async-input-debounce 0.5)
(setq consult-async-input-throttle 0.8)
(setq consult-narrow-key nil)
(setq consult-find-args
(concat "find . -not ( "
"-path */.git* -prune "
"-or -path */.cache* -prune )"))
(setq consult-preview-key 'any)
(setq consult-project-function nil) ; always work from the current directory (use `cd' to switch directory)
(add-to-list 'consult-mode-histories '(vc-git-log-edit-mode . log-edit-comment-ring))
;; the `imenu' extension is in its own file
(require 'consult-imenu))
The unravel-completion.el
section about embark
The
embark
package by Omar Antolín Camarena provides a mechanism to perform relevant actions in the given context. What constitutes "the given context" depends on where the cursor is, such as if it is at the end of a symbolic expression in Lisp code or inside the minibuffer. The single point of entry is theembark-act
command or variants likeembark-dwim
.With
embark-act
we gain access to a customisable list of commands for the given context. If we are over a Lisp symbol, one possible action is to describe it (i.e. produce documentation about it). If we are browsing files in the minibuffer, possible actions include file operations such as to delete or rename the file. And so on for everything.The
embark-dwim
command always performs the default action for the given context. It is like invokingembark-act
and then typing theRET
key.A killer feature of
embark
is the concepts of "collect" and "export". These are used in the minibuffer to produce a dedicated buffer that contains all the completion candidates. For example, if we are reading documentation aboutembark-
and have 10 items there, we can "collect" the results in their own buffer and then navigate it as if it were the minibuffer:RET
will perform the action that the actual minibuffer would have carried out (to show documentation, in this case). Similarly, the export mechanism takes the completion candidates out of the minibuffer, though it also puts them in a major mode that is appropriate for them. Files, for instance, will be placed in a Dired buffer (Theunravel-dired.el
module).Depending on the configurations about the "indicator", the
embark-act
command will display an informative buffer with keys and their corresponding commands.One downside of
embark
is that it is hard to know what the context is. I have had this experience myself several times, where I though I was targeting the URL at point while the actions were about Org source blocks, headings, and whatnot. Embark is probably correct in such a case, though I cannot make my brain think the way it expects.Another downside, which is also true for
which-key
, is the sheer number of options for each context. I feel that the defaults should be more conservative, to have 3-4 actions per context to make it easier to find stuff. Those who need more, can add them. Documentation can also be provided to that end. Adding commands to such a list is not a trivial task, because the user must modify keymaps and thus understand the relevant concepts. Sure, we can all learn, but this is not your usualsetq
tweak.All things considered, I do not recommend
embark
to new users as I know for a fact that people have trouble using it effectively. Power users can benefit from it, though you will notice in the following code block and inprot-embark.el
how even power users need to put in some work (Theprot-embark.el
library). Whether it is worth it or not depends on one's use-case.Karthik Chikmagalur has an excellently written and presented essay on Fifteen ways to use Embark. If you plan on becoming an
embark
power user, this will help you.
;;; Extended minibuffer actions and more (embark.el and prot-embark.el)
(use-package embark
:ensure t
:defer 1
:config
(setq embark-confirm-act-all nil)
(setq embark-mixed-indicator-both nil)
(setq embark-mixed-indicator-delay 1.0)
(setq embark-indicators '(embark-mixed-indicator embark-highlight-indicator))
(setq embark-verbose-indicator-nested nil) ; I think I don't have them, but I do not want them either
(setq embark-verbose-indicator-buffer-sections '(bindings))
(setq embark-verbose-indicator-excluded-actions
'(embark-cycle embark-act-all embark-collect embark-export embark-insert))
;; I never cycle and want to disable the damn thing. Normally, a
;; nil value disables a key binding but here that value is
;; interpreted as the binding for `embark-act'. So I just add
;; some obscure key that I do not have. I absolutely do not want
;; to cycle!
(setq embark-cycle-key "<XF86Travel>")
;; I do not want `embark-org' and am not sure what is loading it.
;; So I just unsert all the keymaps... This is the nuclear option
;; but here we are.
(with-eval-after-load 'embark-org
(defvar prot/embark-org-keymaps
'(embark-org-table-cell-map
embark-org-table-map
embark-org-link-copy-map
embark-org-link-map
embark-org-src-block-map
embark-org-item-map
embark-org-plain-list-map
embark-org-export-in-place-map)
"List of Embark keymaps for Org.")
;; Reset `prot/embark-org-keymaps'.
(seq-do
(lambda (keymap)
(set keymap (make-sparse-keymap)))
prot/embark-org-keymaps)))
;; I define my own keymaps because I only use a few functions in a
;; limited number of contexts.
(use-package prot-embark
:ensure nil
:after embark
:bind
( :map global-map
("C-," . prot-embark-act-no-quit)
("C-." . prot-embark-act-quit)
:map embark-collect-mode-map
("C-," . prot-embark-act-no-quit)
("C-." . prot-embark-act-quit)
:map minibuffer-local-filename-completion-map
("C-," . prot-embark-act-no-quit)
("C-." . prot-embark-act-quit))
:config
(setq embark-keymap-alist
'((buffer prot-embark-buffer-map)
(command prot-embark-command-map)
(expression prot-embark-expression-map)
(file prot-embark-file-map)
(function prot-embark-function-map)
(identifier prot-embark-identifier-map)
(package prot-embark-package-map)
(region prot-embark-region-map)
(symbol prot-embark-symbol-map)
(url prot-embark-url-map)
(variable prot-embark-variable-map)
(t embark-general-map))))
;; Needed for correct exporting while using Embark with Consult
;; commands.
(use-package embark-consult
:ensure t
:after (embark consult))
The unravel-completion.el
section to configure completion annotations (marginalia
)
The
marginalia
package, co-authored by Daniel Mendler and Omar Antolín Camarena, provides helpful annotations to the side of completion candidates. We see its effect, for example, when we callM-x
: each command has a brief description next to it (taken from its doc string) as well as a key binding, if it has one.
;;; Detailed completion annotations (marginalia.el)
(use-package marginalia
:ensure t
:hook (after-init . marginalia-mode)
:config
(setq marginalia-max-relative-age 0)) ; absolute time
The unravel-completion.el
section for vertico
The
vertico
package by Daniel Mendler displays the minibuffer in a vertical layout. Under the hood, it takes care to be responsive and to handle even massive completion tables gracefully.
;;; Vertical completion layout (vertico)
(use-package vertico
:ensure t
:hook (after-init . vertico-mode)
:config
(setq vertico-scroll-margin 0)
(setq vertico-count 5)
(setq vertico-resize t)
(setq vertico-cycle t)
(with-eval-after-load 'rfn-eshadow
;; This works with `file-name-shadow-mode' enabled. When you are in
;; a sub-directory and use, say, `find-file' to go to your home '~/'
;; or root '/' directory, Vertico will clear the old path to keep
;; only your current input.
(add-hook 'rfn-eshadow-update-overlay-hook #'vertico-directory-tidy)))
The unravel-langs.el
module
The unravel-langs.el
settings for TAB
;;;; Tabs, indentation, and the TAB key
(use-package emacs
:ensure nil
:demand t
:config
(setq tab-always-indent 'complete)
(setq tab-first-completion 'word-or-paren-or-punct) ; Emacs 27
(setq-default tab-width 4
indent-tabs-mode nil))
The unravel-langs.el
settings show-paren-mode
The built-in
show-paren-mode
highlights the parenthesis on the opposite end of the current symbolic expression. It also highlights matching terms of control flow in programming languages that are not using parentheses like Lisp: for instance, in abash
shell script it highlights theif
andfi
keywords. This mode also works for prose and I use it globally. Simple and effective!
;;;; Parentheses (show-paren-mode)
(use-package paren
:ensure nil
:hook (prog-mode . show-paren-local-mode)
:config
(setq show-paren-style 'mixed)
(setq show-paren-when-point-in-periphery nil)
(setq show-paren-when-point-inside-paren nil)
(setq show-paren-context-when-offscreen 'overlay)) ; Emacs 29
The unravel-langs.el
settings for eldoc
The built-in
eldoc
feature is especially useful in programming modes. While we are in a function call, it produces an indicator in the echo area (where the minibuffer appears upon invocation) that shows the name of the function, the arguments it takes, if any, and highlights the current argument we are positioned at. This way, we do not have to go back to review the signature of the function just to remember its arity. Same principle for variables, whereeldoc-mode
puts the first line of their documentation string in the echo area.
;;;; Eldoc (Emacs live documentation feedback)
(use-package eldoc
:ensure nil
:hook (prog-mode . eldoc-mode)
:config
(setq eldoc-message-function #'message)) ; don't use mode line for M-x eval-expression, etc.
The unravel-langs.el
settings for eglot
(LSP client)
The built-in
eglot
feature, developed and maintained by João Távora, is Emacs' own client for the Language Server Protocol (LSP). The LSP technology is all about enhancing the ability of a text editor to work with a given programming language. This works by installing a so-called "language server" on your computer, which the "LSP client" (i.e.eglot
) will plug into. A typical language server provides the following capabilities:
- Code completion
- This can be visualised for in-buffer automatic expansion of function calls, variables, and the like (The
unravel-completion.el
for in-buffer completion popup (corfu
)).- Code linting
- To display suggestions, warnings, or errors. These are highlighted in the buffer, usually with an underline, and can also be displayed in a standalone buffer with the commands
flymake-show-buffer-diagnostics
,flymake-show-project-diagnostics
(Theunravel-langs.el
settings for code linting (flymake
)).- Code navigation and cross-referencing
- While over a symbol, use a command to jump directly to its definition. The default key bindings for going forth and then back are
M-.
(xref-find-definitions
) andM-,
(xref-go-back
).…
Assuming the language server is installed, to start using the LSP client in a given file, do
M-x eglot
. To make this happen automatically for every newly visited file, add a hook like this:(add-hook 'SOME-MAJOR-mode #'eglot-ensure)
;;;; Eglot (built-in client for the language server protocol)
(use-package eglot
:ensure nil
:functions (eglot-ensure)
:commands (eglot)
:config
(setq eglot-sync-connect nil)
(setq eglot-autoshutdown t))
The unravel-langs.el
settings for markdown-mode
The
markdown-mode
lets us edit Markdown files. We get syntax highlighting and several extras, such as the folding of headings and navigation between them. The mode actually provides lots of added functionality for GitHub-flavoured Markdown and to preview a Markdown file's HTML representation on a web page. Though I only use it for basic text editing.
;;; Markdown (markdown-mode)
(use-package markdown-mode
:ensure t
:defer t
:config
(setq markdown-fontify-code-blocks-natively t))
The prot-emacs-langs.el
settings for csv-mode
The package
csv-mode
provides support for.csv
files. I do need this on occasion, even though my use-case is pretty basic. For me, the killer feature is the ability to create a virtual tabulated listing with the commandcsv-align-mode
: it hides the field delimiter (comma or space) and shows a tab stop in its stead.
;;; csv-mode
(use-package csv-mode
:ensure t
:commands (csv-align-mode))
The unravel-langs.el
settings for spell checking (flyspell
)
;;; Flyspell
(use-package flyspell
:ensure nil
:bind
( :map flyspell-mode-map
("C-;" . nil)
:map flyspell-mouse-map
("<mouse-3>" . flyspell-correct-word))
:config
(setq flyspell-issue-message-flag nil)
(setq flyspell-issue-welcome-flag nil)
(setq ispell-program-name "aspell")
(setq ispell-dictionary "en_GB"))
The unravel-langs.el
settings for code linting (flymake
)
The built-in
flymake
feature defines an interface for viewing the output of linter programs. A "linter" parses a file and reports possible notes/warnings/errors in it. Withflymake
we get these diagnostics in the form of a standalone buffer as well as inline highlights (typically underlines combined with fringe indicators) for the portion of text in question. The linter report is displayed with the commandflymake-show-buffer-diagnostics
, orflymake-show-project-diagnostics
. Highlights are shown in the context of the file.The built-in
eglot
feature usesflymake
internally to handle the LSP linter output (Theunravel-langs.el
settings foreglot
).As for what I have in this configuration block, the essentials for me are the user options
flymake-start-on-save-buffer
andflymake-start-on-flymake-mode
as they make the linter update its report when the buffer is saved and whenflymake-mode
is started, respectively. Otherwise, we have to run it manually, which is cumbersome.The
package-lint-flymake
package by Steve Purcell adds the glue code to makeflymake
report issues with Emacs Lisp files for the purposes of packaging. I use it whenever I work on my numerous Emacs packages.
;;; Flymake
(use-package flymake
:ensure nil
:bind
(:map flymake-mode-map
("C-c ! s" . flymake-start)
("C-c ! l" . flymake-show-buffer-diagnostics) ; Emacs28
("C-c ! L" . flymake-show-project-diagnostics) ; Emacs28
("C-c ! n" . flymake-goto-next-error)
("C-c ! p" . flymake-goto-prev-error))
:config
(setq flymake-fringe-indicator-position 'left-fringe)
(setq flymake-suppress-zero-counters t)
(setq flymake-no-changes-timeout nil)
(setq flymake-start-on-flymake-mode t)
(setq flymake-start-on-save-buffer t)
(setq flymake-proc-compilation-prevents-syntax-check t)
(setq flymake-wrap-around nil)
(setq flymake-mode-line-format
'("" flymake-mode-line-exception flymake-mode-line-counters))
;; NOTE 2023-07-03: `prot-modeline.el' actually defines the counters
;; itself and ignores this.
(setq flymake-mode-line-counter-format
'("" flymake-mode-line-error-counter
flymake-mode-line-warning-counter
flymake-mode-line-note-counter ""))
(setq flymake-show-diagnostics-at-end-of-line nil)) ; Emacs 30
;;; Elisp packaging requirements
(use-package package-lint-flymake
:ensure t
:after flymake
:config
(add-hook 'flymake-diagnostic-functions #'package-lint-flymake))
The unravel-langs.el
settings for outline-minor-mode
;;; General configurations for prose/writing
;;;; `outline' (`outline-mode' and `outline-minor-mode')
(use-package outline
:ensure nil
:bind
("<f10>" . outline-minor-mode)
:config
(setq outline-minor-mode-highlight nil) ; emacs28
(setq outline-minor-mode-cycle t) ; emacs28
(setq outline-minor-mode-use-buttons nil) ; emacs29---bless you for the nil option!
(setq outline-minor-mode-use-margins nil)) ; as above
The prot-emacs-langs.el
settings for dictionary
Use the entry point M-x dictionary-search
;;;; `dictionary'
(use-package dictionary
:ensure nil
:config
(setq dictionary-server "dict.org"
dictionary-default-popup-strategy "lev" ; read doc string
dictionary-create-buttons nil
dictionary-use-single-buffer t))
The unravel-langs.el
settings for denote
(notes and file-naming)
This is another one of my packages and is extended by my
consult-denote
package (Theunravel-langs.el
integration between Consult and Denote (consult-denote
)).Denote is a simple note-taking tool for Emacs. It is based on the idea that notes should follow a predictable and descriptive file-naming scheme. The file name must offer a clear indication of what the note is about, without reference to any other metadata. Denote basically streamlines the creation of such files while providing facilities to link between them.
Denote's file-naming scheme is not limited to "notes". It can be used for all types of file, including those that are not editable in Emacs, such as videos. Naming files in a consistent way makes their filtering and retrieval considerably easier. Denote provides relevant facilities to rename files, regardless of file type.
Prot is the developer and maintainer of this package.
- Package name (GNU ELPA):
denote
- Official manual: <https://protesilaos.com/emacs/denote>
- Change log: <https://protesilaos.com/emacs/denote-changelog>
-
Git repositories:
- GitHub: <https://github.com/protesilaos/denote>
- GitLab: <https://gitlab.com/protesilaos/denote>
- Video demo: <https://protesilaos.com/codelog/2022-06-18-denote-demo/>
- Backronyms: Denote Everything Neatly; Omit The Excesses. Don't Ever Note Only The Epiphenomenal.
;;; Denote (simple note-taking and file-naming)
;; Read the manual: <https://protesilaos.com/emacs/denote>. This does
;; not include all the useful features of Denote. I have a separate
;; private setup for those, as I need to test everything is in order.
(use-package denote
:ensure t
:hook
;; If you use Markdown or plain text files you want to fontify links
;; upon visiting the file (Org renders links as buttons right away).
((text-mode . denote-fontify-links-mode-maybe)
;; Highlight Denote file names in Dired buffers. Below is the
;; generic approach, which is great if you rename files Denote-style
;; in lots of places as I do.
;;
;; If you only want the `denote-dired-mode' in select directories,
;; then modify the variable `denote-dired-directories' and use the
;; following instead:
;;
;; (dired-mode . denote-dired-mode-in-directories)
(dired-mode . denote-dired-mode))
:bind
;; Denote DOES NOT define any key bindings. This is for the user to
;; decide. Here I only have a subset of what Denote offers.
( :map global-map
("C-c n n" . denote)
("C-c n N" . denote-type)
("C-c n o" . denote-sort-dired) ; "order" mnemonic
;; Note that `denote-rename-file' can work from any context, not
;; just Dired buffers. That is why we bind it here to the
;; `global-map'.
;;
;; Also see `denote-rename-file-using-front-matter' further below.
("C-c n r" . denote-rename-file)
;; If you intend to use Denote with a variety of file types, it is
;; easier to bind the link-related commands to the `global-map', as
;; shown here. Otherwise follow the same pattern for
;; `org-mode-map', `markdown-mode-map', and/or `text-mode-map'.
:map text-mode-map
("C-c n i" . denote-link) ; "insert" mnemonic
("C-c n I" . denote-add-links)
("C-c n b" . denote-backlinks)
;; Also see `denote-rename-file' further above.
("C-c n R" . denote-rename-file-using-front-matter)
:map org-mode-map
("C-c n d l" . denote-org-extras-dblock-insert-links)
("C-c n d b" . denote-org-extras-dblock-insert-backlinks)
;; Key bindings specifically for Dired.
:map dired-mode-map
("C-c C-d C-i" . denote-dired-link-marked-notes)
("C-c C-d C-r" . denote-dired-rename-marked-files)
("C-c C-d C-k" . denote-dired-rename-marked-files-with-keywords)
("C-c C-d C-f" . denote-dired-rename-marked-files-using-front-matter))
:config
;; Remember to check the doc strings of those variables.
(setq denote-directory (expand-file-name "~/Documents/notes/"))
(setq denote-file-type 'text) ; Org is the default file type
;; If you want to have a "controlled vocabulary" of keywords,
;; meaning that you only use a predefined set of them, then you want
;; `denote-infer-keywords' to be nil and `denote-known-keywords' to
;; have the keywords you need.
(setq denote-known-keywords '("emacs" "philosophy" "politics" "economics"))
(setq denote-infer-keywords t)
(setq denote-sort-keywords t)
(setq denote-excluded-directories-regexp nil)
(setq denote-date-format nil) ; read its doc string
(setq denote-rename-confirmations nil) ; CAREFUL with this if you are not familiar with Denote!
(setq denote-backlinks-show-context nil)
(setq denote-rename-buffer-format "[D] %t%b")
(setq denote-buffer-has-backlinks-string " (<--->)")
;; Automatically rename Denote buffers when opening them so that
;; instead of their long file name they have a literal "[D]"
;; followed by the file's title. Read the doc string of
;; `denote-rename-buffer-format' for how to modify this.
(denote-rename-buffer-mode 1)
;; ----- PERSONAL TWEAKS FOR EXPERIMENTS -----
(setq denote-text-front-matter "title: %s\n\n")
(defun prot/denote-add-text-front-matter-separator ()
"Add separator equal to the length of the title.
Do this when the `denote-file-type' is `text'."
(when (and (eq denote-file-type 'text)
;; Not `string=' because there may be a .gpg extension as well.
(string-match-p (file-name-extension buffer-file-name) "txt"))
(save-excursion
(goto-char (point-min))
(when (re-search-forward "title:" nil t)
(let ((text (buffer-substring-no-properties (line-beginning-position) (line-end-position))))
(if (re-search-forward "^$" nil t)
(insert (make-string (length text) ?-))
(error "Could not find an empty line after the front matter")))))))
(add-hook 'denote-after-new-note-hook #'prot/denote-add-text-front-matter-separator))
The unravel-langs.el
integration between Consult and Denote (consult-denote
)
This is another package of mine which extends my
denote
package (Theunravel-langs.el
settings fordenote
(notes and file-naming)).This is glue code to integrate
denote
with Daniel Mendler'sconsult
(Theunravel-completion.el
settings forconsult
). The idea is to enhance minibuffer interactions, such as by providing a preview of the file-to-linked/opened and by adding more sources to theconsult-buffer
command.
Prot is the developer of this package.
- Package name (GNU ELPA):
consult-denote
- Official manual: not available yet.
-
Git repositories:
(use-package consult-denote
:ensure t
:bind
(("C-c n f" . consult-denote-find)
("C-c n g" . consult-denote-grep))
:config
(consult-denote-mode 1))