Skip to content

Latest commit

 

History

History
5441 lines (4518 loc) · 186 KB

emacs-config-26-nox.org

File metadata and controls

5441 lines (4518 loc) · 186 KB

Emacs configuration

1 Notes

This Emacs configuration is intended for TTY-only, with minimal and optional external program dependencies.

1.1 Using this Emacs config

This configuration assumes Emacs version ~27~ or newer.

Clone this repository and initialize and update the submodules (see “Cloning a Project with Submodules” under Submodules in Pro Git).

$ git clone https://github.com/matheuristic/emacs-config.git
$ cd emacs-config

Make sure GNU Stow is installed. Run the following to create the required directory structure in the user home directory and symlink the individual files.

$ stow -t $HOME --no-folding emacs-26-nox

This configuration uses several external tools, which need to be compiled, installed or configured. Search for headings with an external tag in this Org file, and follow the instructions there.

Note that Emacs versions 27 and newer also support XDG Base Directory Specification, so it is possible to use ~/.config/emacs as an alternative emacs directory. If desired, use the follow GNU Stow command instead of the one above.

$ mkdir -p $HOME/.config/emacs
$ stow -d ./emacs -t $HOME/.config/emacs --no-folding .emacs.d

1.2 Generating the Emacs Lisp config files

The Emacs Lisp (Elisp) config files for Emacs can be generated with M-x org-babel-tangle or C-c C-v C-t while in this file’s buffer. The following files will be created in the emacs-26-nox/.emacs.d directory:

  • init.el: Main configuration file.

2 Tags

  • elpa: Uses a package from GNU ELPA.
  • external: Configuration code in the section uses external tools that are not typically packaged with the default userland on Linux and BSD systems (including macOS), and which need to be installed at the system-level outside of the Emacs package.el mechanisms. Also includes Elisp scripts that need to be downloaded manually.
  • transient: Enables, creates or modifies Transient definitions.
  • melpa: Uses a package from MELPA.
  • semiearly: Configuration that should be loaded early in init.el because other configuration code depend on them.
  • workaround: Section contains a workaround for Emacs or package bug, which can be removed if and when the issues are fixed upstream.

3 Front matter

3.1 Header and lexical binding

File header comment indicating the filename, along with declaring any file-specific variables. One file-specific variable that should generally be set is enabling lexical-binding (link), which has the following benefits:

  • Closures.
  • Better performance.
  • Less bugs.
(concat ";;; " feature ".el --- " summary " -*- lexical-binding: t; -*-")

3.2 File generation timestamp

Tangled initialization files are timestamped to track when they were last generated.

(concat ";; Generated: " (current-time-string))

3.3 Author info

Author information and where to get the newest version of this configuration.

;; Author: matheuristic
;; URL: https://github.com/matheuristic/emacs-config

3.4 File commentary

File descriptions.

3.4.1 init

;; Emacs initialization configuration file, symlink or copy to
;; ~/.emacs.d/init.el or $XDG_CONFIG_HOME/.emacs.d/init.el

;; In Emacs 26, the sequence of initialization is
;; 1. package.el
;; 2. init.el

4 Customize file and local configuration

Emacs has a text GUI interface for customizing the editor, and settings configured with this interface are saved in custom-file. To avoid the M-x customize settings clobbering the tangled initialization files (which it does by default), set custom-file to to something that is not the Emacs init file.

Other local configuration should go into a lisp/init-local.el file in user-emacs-directory (usually ~/.emacs.d/lisp/init-local.el). This file should provide the init-local feature when it is loaded. In the below configuration, this is loaded after initialization but before custom-file is loaded.

Template for init-local.el file.

;;; init-local.el --- Emacs local config file -*- lexical-binding: t; -*-

;;; Commentary:

;; Emacs configuration that is machine-local, typically loaded before
;; the Customize file.

;; This file should be located at lisp/init-local.el within
;; `user-emacs-directory', typically ~/.emacs.d/lisp/init-local.el

;;; Code:

;; Local configuration code goes here ...

(provide 'init-local)
;;; init-local.el ends here
;; store Customize settings in a separate file, custom.el
(setq custom-file (expand-file-name "custom.el" user-emacs-directory))

;; load local init configuration and Customize settings on startup
(add-hook 'after-init-hook
          (lambda ()
            (require 'init-local nil t) ; don't raise errors
            (load custom-file 'noerror))
          10) ; load this after regular `after-init-hook' functions

5 Custom variables and utility functions

Various utility functions used in multiple config locations, usually non-interactive and added to hooks or as advice to other functions, and the custom variables that get used by them and/or other specific-use functions.

5.1 Custom variables

5.1.1 System command for opening paths and URLS externally

my-system-open-command should be set to the system command for opening generic file paths and URLS, for example xdg-open in Linux and open in macOS.

(defcustom my-system-open-command "xdg-open"
  "System command to open file/URL according to preferred app by filetype.
Usually \"xdg-open\" on Linux and \"open\" on Mac."
  :type 'string
  :group 'convenience)

5.2 Utility functions

5.2.1 After jump context actions

Define a function that performs a laundry list of useful context-specific actions useful after jumping to a new location.

Some examples of these context actions:

  • Run org-show-context after jumping to an Org buffer location.
(defun my-after-jump-context-actions (&rest _)
  "Useful context actions to perform after jumping to a new location.
This is meant for use with `advice-add' with the :after
combinator.

One useful context action example is to run `org-show-context'
after jumping to an Org buffer location to ensure the region
around the new point location is visible."
  (cond ((eq major-mode 'org-mode) (org-show-context))))

5.2.2 Save and bury buffer

Save current buffer and bury it.

(defun my-save-and-bury-buffer (&rest _)
  "Save and bury the current buffer."
  (save-buffer)
  (bury-buffer))

5.2.3 Install ELPA-compatible repository version of a built-in package

Generally, use-package does not install the ELPA-compatible repository version of a package when there is already a built-in version of it. This function works around that limitation by forcing installed of the ELPA-compatible repository version if it is not already present in the ELPA package cache.

;; hacky workaround to install ELPA/MELPA version of a package
;; adapated from https://github.com/jwiegley/use-package/issues/319
(defun my-install-elpa-package (pkg-symb)
  "Install the ELPA-compatible repository version of package PKG-SYMB.
Useful for working around `use-package' behavior of not
installing the repository version of a package when a built-in
version is present (even if pinned to a specific repository)."
  (let ((pkg-pattern (concat package-user-dir
                             "/" (symbol-name pkg-symb) "-[0-9]*")))
    (unless (file-expand-wildcards pkg-pattern)
      (package-install (elt (cdr (assoc pkg-symb
                                        package-archive-contents))
                            0)))))

6 Package management

6.1 Prefer newer Elisp files

When multiple versions of an Elisp file exist (compiled and uncompiled), load the newest.

;; when multiple versions of a package are installed, load the newest
(setq load-prefer-newer t)

6.2 Local packages

Add the lisp/ and site-lisp/ directories in the user Emacs directory to the load path to facilitate loading of user maintained and local copies of third-party packages.

;; add user packages in lisp/ to load path
(defvar lisp-dir (expand-file-name "lisp" user-emacs-directory))
(unless (file-exists-p lisp-dir) (make-directory lisp-dir))
(add-to-list 'load-path lisp-dir)
(dolist (project (directory-files lisp-dir t "\\w+"))
  (when (file-directory-p project) (add-to-list 'load-path project)))

;; add third-party packages in site-lisp/ and its subdirs to load path
(defvar site-lisp-dir (expand-file-name "site-lisp" user-emacs-directory))
(unless (file-exists-p site-lisp-dir) (make-directory site-lisp-dir))
(add-to-list 'load-path site-lisp-dir)
(dolist (project (directory-files site-lisp-dir t "\\w+"))
  (when (file-directory-p project) (add-to-list 'load-path project)))

6.3 ELPA-compatible package repositories

Set ELPA-compatible repositories to fetch and install packages from, and their priorities. When the packages with the same name exist on multiple repositories, the version on the repository with the highest priority is preferred.

The following package repositories are the most well-known:

Only ELPA and MELPA are used here so the latest package versions are installed, and because there isn’t generally a need for all the contributed files for Org.

;; set ELPA-compatible package repositories and their priorities
(setq package-archives '(("ELPA"   . "https://elpa.gnu.org/packages/")
                         ("MELPA" . "https://melpa.org/packages/"))
      package-archive-priorities '(("ELPA"  . 1)
                                   ("MELPA" . 2)))

6.4 Package initialization

Initialize package loading support. Disable auto-package loading and load packages explicitly for faster initialization times.

;; initialize package.el
(require 'package)
(package-initialize)

6.5 use-package

Download the use-package if not already on the system. Load it, which will provide configuration macros for installing, loading and configuring packages. Also load its subpackage bind-key, which provides macros for key bindings.

;; bootstrap use-package, provides configuration macros
;; for info, see https://github.com/jwiegley/use-package
(unless (package-installed-p 'use-package)
  (package-refresh-contents)
  (package-install 'use-package))

;; preload use-package and bind-key packages
;; configure imenu support for the `require' and `use-package' keywords
(eval-when-compile
  (setq use-package-enable-imenu-support t)
  (require 'use-package)
  (require 'bind-key)
  (setq use-package-always-ensure t)) ;; default to ":ensure t"

;; gather use-package stats, "M-x use-package-report" to see report
(setq use-package-compute-statistics t)

6.6 Convenience function for reinstalling packages

At times, it is useful to be able to reinstall and reload an Emacs package without restarting.

Define a convenience function my-reinstall-package that unloads all loaded features associated with a given package, reinstalls the package and reloads the features that were unloaded.

Adapted from:

;; convenience function to reinstall and reload an Emacs package
(require 'cl-macs)
(require 'seq)
(defun my-package-reinstall (pkg)
  "Prompts for an installed package PKG and reinstalls it.

All loaded features that correspond to Elisp filenames in the
package install directory (but not its subdirectories) are
unloaded, the package reinstalled, and the previously unloaded
features are reloaded."
  (interactive (list (intern (completing-read
                              "Reinstall package: "
                              (mapcar #'car package-alist)))))
  (let* ((pkg-desc (car (alist-get pkg package-alist)))
         (pkg-dir (file-name-as-directory
                   (cl-struct-slot-value 'package-desc 'dir pkg-desc)))
         (pkg-files (directory-files pkg-dir nil "\\.el$"))
         (pkg-features (mapcar
                        (lambda (fname)
                          (intern (file-name-sans-extension fname)))
                        pkg-files))
         (reload-features (seq-filter 'featurep pkg-features)))
    (dolist (feat reload-features)
      (ignore-errors ; handle when pkg is a dependency of another package
        (unload-feature feat t)))
    (package-reinstall pkg)
    (dolist (feat reload-features)
      (require feat))))

7 Backend and frontend frameworks for building user interfaces

7.1 Edit-indirect

Backend package that allows editing regions in a separate buffer, much like how C-c ' works in Org source blocks. This is used by other packages, like markdown-mode.

;; edit regions in separate buffers, used by other packages like markdown-mode
(use-package edit-indirect)

7.2 Text completion with Company

Company is a text completion framework for Emacs that supports pluggable back-ends and front-ends for retrieving and displaying completion candidates. Many other Emacs packages support this.

This can get in the way for non-programming modes, so it is enabled by default only in programming modes.

;; text completion framework
(use-package company
  :defer t
  :init
  (with-eval-after-load 'prog-mode
    (add-hook 'prog-mode-hook 'company-mode))
  (setq company-dabbrev-downcase nil
        company-idle-delay 0.5
        company-minimum-prefix-length 2
        company-selection-wrap-around t
        company-show-numbers t ;; use M-<num> to directly choose completion
        company-tooltip-align-annotations t))

8 Backups

Backup files to the ~/.backup/ directory, keeping only the newest three versions.

;; backup files to ~/.backup/
(let ((backup-dir (expand-file-name "~/.backup/")))
  (when (not (file-directory-p backup-dir))
    (make-directory backup-dir t))
  (setq backup-directory-alist `(("." . ,backup-dir))
        version-control t ;; use version numbers for backups
        kept-new-versions 3 ;; number of newest versions to keep
        kept-old-versions 0 ;; number of oldest versions to keep
        delete-old-versions t ;; don't ask before deleting old backups
        backup-by-copying t)) ;; backup by copying instead of renaming

9 Bookmarks and history

9.1 Recent files

The built in recentf provides functionality to track and list recently opened files.

;; recently opened files
(setq recentf-max-menu-items 10
      recentf-max-saved-items 100
      recentf-auto-cleanup 'mode) ;; clean up recent list when turning on mode
(recentf-mode 1)
;; exclude compressed files
(add-to-list 'recentf-exclude ".gz")
(add-to-list 'recentf-exclude ".xz")
(add-to-list 'recentf-exclude ".zip")
;; exclude source code files in installed packages from ELPA-compatible repos
(add-to-list 'recentf-exclude
             (concat "^" (expand-file-name "elpa/" user-emacs-directory)))
;; exclude files opened with SSH so TRAMP is not spammed with stat calls
;; exclude files opened as the superuser with su or sudo
(add-to-list 'recentf-exclude "^/\\(?:scp\\|ssh\\|su\\|sudo\\)?:")
;; exclude files from /var/folder as these are temp files
(add-to-list 'recentf-exclude "^/var/folders")
;; exclude files in `org-agenda-files'
;; these files are quickly accessible from their respective tooling
(add-hook 'after-init-hook
          (lambda ()
            (dolist (file-list (list org-agenda-files))
              (dolist (exclude-file file-list)
                (add-to-list 'recentf-exclude
                             (concat "^" exclude-file))))))

;; binding for recentf
(global-set-key (kbd "C-c f") #'recentf-open-files)

;; select file to open from `recentf-list' using `completing-read'
(defun my-recentf-find-file ()
  "Use `completing-read' to find a recent file."
  (interactive)
  (find-file (completing-read "Find recent file: " recentf-list)))

;; binding for `my-recentf-open-files' when in recentf dialog buffers
(define-key recentf-dialog-mode-map (kbd "f") #'my-recentf-find-file)

9.2 Save location in file

Enable saveplace to automatically save location in file, so that the next time the file is visited the point will automatically go to the last place it was at during the previous visit.

(save-place-mode 1)

9.3 Save minibuffer and other history

Enable savehist to automatically save minibuffer command history, which can be leverage by different completion packages. Other history (like search history, registers, the kill ring, and the macro ring) can also be saved. The default history file location is history in the user-emacs-directory directory, and can be changed by setting the savehist-file variable. The number of items saved is determined by the history-length variable.

;; save minibuffer and other history across sessions
;; don't persist kill-ring if in the habit of copy-pasting passwords
(setq history-delete-duplicates t
      history-length 100
      ;; if `desktop-save-mode' is enabled, it saves `register-alist'
      ;; and `search-ring' by default so it is unnecessary to add
      ;; those to `savehist-additional-variables'
      savehist-additional-variables '(Info-history-list
                                      ;; kill-ring
                                      kmacro-ring
                                      regexp-search-ring
                                      ;; register-alist
                                      last-kbd-macro
                                      ;; search-ring
                                      shell-command-history))

;; enable save history mode
(savehist-mode 1)

10 Buffers, windows, frames, workspaces

10.1 Buffer management

10.1.1 Protect scratch and message buffers

Protect the *scratch* and *Message* buffers, locking them to make them unkillable.

;; protect these buffers, locking them to make them unkillable
(dolist (buf '("*scratch*" "*Messages*"))
  (with-current-buffer buf
    (emacs-lock-mode 'kill)))

10.1.2 Advanced buffer management with Ibuffer

Use Ibuffer to manage buffers.

;; advanced buffer management with Ibuffer
(setq ibuffer-expert t ; skip extraneous confirm messages
      ibuffer-show-empty-filter-groups nil)

(global-set-key (kbd "C-x C-b") #'ibuffer)

10.1.2.1 Ibuffer filter groups

Set default rules for grouping files in Ibuffer. Collapse some groups, like Org journal files, by default.

;; configure Ibuffer filter groups
(with-eval-after-load 'ibuffer
  (defun my-ibuffer-org-agenda-files-filter ()
    "Ibuffer filter for checking if current buffer is an Org agenda file.

Specifically, the current buffer is checked to see if it is in
`org-agenda-files', is the agenda inbox file
`my-org-agenda-inbox', or is the someday inbox file
`my-org-someday-inbox'."
    (let* ((bufname (buffer-file-name))
           (fname (and bufname (file-truename bufname))) ; filename if a file buffer, nil otherwise
           (agenda-fnames (mapcar #'file-truename (append (org-agenda-files) ; agenda and inbox filenames
                                                          (list my-org-agenda-inbox
                                                                my-org-someday-inbox)))))
      (and fname
           (member fname agenda-fnames))))
  (setq ibuffer-saved-filter-groups
        ;; files are grouped by the first matching filter group in the list
        '(("default"
           ("Emacs" (or (name . "^\\*scratch\\*$")
                        (name . "^\\*Messages\\*$")))
           ("Calendar" (or (name . "^\\*?[Cc]alendar.*$")
                           (name . "^diary$")))
           ("DocView" (mode . doc-view-mode))
           ("Images" (mode . image-mode))
           ("Web" (or (mode . eww-mode)
                      (mode . eww-bookmark-mode)))
           ("Shell" (or (mode . eshell-mode)
                        (mode . shell-mode)
                        (mode . term-mode)))
           ("Data" (or (name . ".csv")
                       (name . ".json")
                       (mode . nxml-mode)))
           ("Programming" (derived-mode . prog-mode))
           ("Agenda" (or (mode . org-agenda-mode)
                         (predicate . (my-ibuffer-org-agenda-files-filter))))
           ("Org" (derived-mode . org-mode))
           ("Text" (derived-mode . text-mode))
           ("Fundamental" (mode . fundamental-mode))
           ("Dired" (mode . dired-mode))
           ("Magit" (derived-mode . magit-mode))
           ("Help" (or (derived-mode . apropos-mode)
                       (derived-mode . help-mode)
                       (derived-mode . Info-mode))))))
  (defun my-ibuffer-filter-groups-setup ()
    "Custom configuration to load when a new Ibuffer buffer gets created."
    ;; use "default" saved filter groups list by default
    (ibuffer-switch-to-saved-filter-groups "default"))
  (add-hook 'ibuffer-mode-hook #'my-ibuffer-filter-groups-setup))

10.1.3 Visual buffer switching using a grid of windows

buffer-expose visualizes buffers in a grid of windows and allows the user to switch to a selected buffer in that grid. Integrates with ace-window if available (set buffer-expose-auto-init-aw to t for automatically initializing grid buffer views with ace-window enabled).

;; visual buffer switching using a grid of windows
(use-package buffer-expose
  :init
  (setq buffer-expose-show-current-buffer t)
  ;; set auto initialization with ace-window if it is loaded
  (with-eval-after-load 'ace-window
    (setq buffer-expose-auto-init-aw t)))

10.1.4 Minor mode for reverting buffer to disk version without query

This minor mode manages the addition and removal of the file being visited by the current buffer to and from revert-without-query. Enabling the mode adds the file to revert-without-query, and disabling the mode does the opposite. This is useful when working with viewing a file which is changing frequently on disk.

(define-minor-mode revert-without-query-mode
  "Minor mode for adding/removing current file to/from `revert-without-query'.

Enabling the minor mode adds the file to `revert-without-query'.

Disabling the minor mode removes the file from `revert-without-query'.

This minor mode has no effect when the buffer is not visiting a file."
  :init-value nil
  :lighter " 🅠"
  :keymap nil
  ;; match filename from `find-file-noselect'
  (let ((fname (abbreviate-file-name (expand-file-name
                                      (buffer-file-name)))))
    (if buffer-file-name
        (if (symbol-value revert-without-query-mode)
            (progn
              (setq revert-without-query (add-to-list 'revert-without-query fname))
              (message "Buffer revert without query ON."))
          (setq revert-without-query (remove fname revert-without-query))
          (message "Buffer revert without query OFF."))
      (message "Current buffer is NOT visiting a file."))))

10.2 Window management

10.2.1 Traverse window configuration history using Winner mode

Winner mode allows the traversal of window configuration history using C-c <left> (undo) and C-c <right> (redo).

;; traverse window config changes, C-c left/right to undo/redo
;; uncomment to not bind C-c left/right keys by default
;; (setq winner-dont-bind-my-keys t)
;; enable winner-mode at end of initialization
(add-hook 'after-init-hook #'winner-mode)

10.2.2 More convenient binding for cycling between windows

Use M-o (easier than C-x o) to cycle between visible windows in a frame.

;; more convenient bindings for `other-window' and `other-frame'
(global-set-key (kbd "M-o") #'other-window)

10.2.3 Rotate window buffers

Helper function to rotate the buffers in the current frame’s windows. This rotation preserves the window configuration but shifts the buffers displayed in each window.

(defun my-rotate-window-buffers (rotations)
  "Rotate buffers in the windows of the current frame ROTATIONS times.
ROTATIONS can be negative, which rotates in the opposite direction."
  (interactive "P")
  (let* (;; windows that do not contain transient buffers
         (windows (seq-filter (lambda (w)
                                (not
                                 (string= (buffer-name
                                           (window-buffer w))
                                          transient--buffer-name)))
                              (window-list)))
         (num-windows (length windows)))
    (if (not (> num-windows 1))
        (message "Only one window in the frame. Nothing to rotate.")
      (let* (;; original window order properties
             (window-props (mapcar (lambda (w)
                                     `(:buffer ,(window-buffer w)
                                       :start ,(window-start w)
                                       :point ,(window-point w)))
                                   windows))
             ;; new window order after rotation
             (window-moves (mapcar
                            (lambda (k)
                              (elt windows (mod (+ k rotations)
                                                num-windows)))
                            (number-sequence 0 (1- num-windows))))
             ;; create alist for easier looping later
             (wins-props (cl-mapcar #'cons window-moves window-props)))
        ;; iteratively assign orig window props in new window order
        (dolist (w-p wins-props)
          (let ((win (car w-p))
                (prop (cdr w-p)))
            (set-window-buffer-start-and-point
             win
             (plist-get prop :buffer)
             (plist-get prop :start)
             (plist-get prop :point))))))))

(defun my-rotate-buffers-forward ()
  "Rotate buffers in current frame's windows forward."
  (interactive)
  (my-rotate-window-buffers 1))
(defun my-rotate-buffers-backward ()
  "Rotate buffers in current frame's windows backward."
  (interactive)
  (my-rotate-window-buffers -1))

;; bind "C-x 4 [" and "C-x 4 ]" to rotation of window buffers
(global-set-key (kbd "C-x 4 [") #'my-rotate-buffers-backward)
(global-set-key (kbd "C-x 4 ]") #'my-rotate-buffers-forward)

10.2.4 Select help windows automatically

Automatically focus on newly opened help buffers in existing or new windows.

;; automatically focus on help windows when they are opened
(setq help-window-select t)

10.3 Frame management

10.3.1 Resize frames pixelwise

Resize frame by pixels rather than by characters (the default). This helps to resolve issues with fullscreen and maximized windows not filling up the entire screen in some window managers when the screen width and height are not multiples of the character width and height.

;; resize frames by pixel instead of by character
(setq frame-resize-pixelwise t)

10.3.2 transpose-frame for rotating frames

transpose-frame allows for the rotation of the frame to get a new window layout that is rotated from the original.

See the package Elisp code for more details.

(use-package transpose-frame
  :bind (("C-x 5 [" . rotate-frame-anticlockwise)
         ("C-x 5 ]" . rotate-frame-clockwise)))

10.3.3 More convenient binding for cycling between frames

Use M-O (easier than C-x 5 o) to cycle between frames.

;; more convenient bindings for `other-frame'
(global-set-key (kbd "M-O") #'other-frame)

10.4 Workspace management

10.4.1 desktop.el for saving and restoring sessions

desktop.el provides capabilities for saving and restoring sessions manually and automatically.

Configuration:

  • Enable desktop-save-mode which automatically saves on exit and loads on entry, but set desktop-auto-save-timeout to disable default behavior of auto-saving on a timer.
;; settings for desktop.el
;; desktops are saved to ~/.emacs.d/.emacs.desktop
;; and locks are saved to ~/.emacs.d/.emacs.desktop.lock
;; - enable desktop-save-mode to save on exit and load on entry;
;;   this is added to `after-init-hook' to avoid a prompt on startup
;;   warning about the desktop file being in use that occurs when
;;   `desktop-save-mode' is enabled before initialization is done,
;;   even though the Emacs process PID is the owner of the lock file;
;;   might be specific to emacs-mac port
;; - set `desktop-autosave-timeout' to nil to disable timer auto-saves
;; - restore frames to their original displays
;; - re-use existing frames
(setq desktop-auto-save-timeout nil
      desktop-restore-in-current-display nil
      desktop-restore-reuses-frames t
      desktop-files-not-to-save (concat "\\("
                                        (mapconcat
                                         'identity
                                         '("\\`/[^/:]*:"
                                           "(ftp)\\'"
                                           "\\.log"
                                           "\\.gz")
                                         "\\|"
                                         )
                                        "\\)"))
(add-hook 'after-init-hook
          (lambda ()
            (desktop-save-mode 1)
            (desktop-read))
          50) ; load after all other `after-init-hook' functions

11 Command-line interaction

11.1 Eshell

Eshell is an Elisp shell-like command interpreter that can be used in place of term-mode and bash. More information on Eshell usage.

Customizations:

  • Increase the size of the history input ring from 128 to 1024.
  • Don’t review quick commands (those that have no output and returns a 0 exit code indicating success).
  • Have space go to the end of the buffer when it is visible.
  • Have point jump to the beginning of the last command after each command.
  • Load em-smart which adds some quality of life improvements.

Usage note:

  • When searching history using the beginning of a command, eshell-previous-matching-input-from-input (UP), M-p or C-c M-r is much friendlier than eshell-previous-matching-input (M-r). Type the first few characters of the command, and press the UP or M-p key repeatedly to cycle only through the matching commands in the history. Copied from StackOverflow answer here.
(setq eshell-history-size 1024
      eshell-review-quick-commands nil
      eshell-smart-space-goes-to-end t
      eshell-where-to-jump 'begin)
(require 'em-smart)

11.1.1 Run visual commands in a separate term buffers

Some “visual” commands present and update a full-screen interface instead of streaming output to stdout. Run these commands inside a separate term buffer instead.

;; enable Eshell to spawn visual commands inside
(require 'em-term)
;; run visual commands and subcommands in term sessions
(dolist (cmd '("htop" "lftp" "ssh" "vi" "vim" "watch"))
  (add-to-list 'eshell-visual-commands cmd))
(dolist (subcmd '(("tail" "-f" "-F")
                  ("sudo" "vi" "vim")
                  ("vagrant" "ssh")))
  (add-to-list 'eshell-visual-subcommands subcmd))

11.1.2 Disabling Git pagers so Git can be used in Eshell

;; ensure Git does not launch a pager for easier usage with eshell
(setenv "GIT_PAGER" "")

11.1.3 Named Eshell buffers for easier management of multiple Eshell buffers

Provide a binding to a wrapper function that spawns or switches to a named Eshell buffer. This allows for easier access to and management of multiple Eshell buffers.

;; adapted from https://arte.ebrahimi.org/blog/named-eshell-buffers
(defun my-eshell-with-name ()
  "Prompts for the name of a eshell buffer to open or switch to.
If the NAME given at the prompt is not an existing eshell buffer,
a new one named *eshell*<NAME> will be opened. If no name is
provided, the default interactive `eshell' command is run."
  (interactive)
  (let* ((my-es-bufs (seq-filter
                      (lambda (buf)
                        (string-match-p "*eshell*" (buffer-name buf)))
                      (buffer-list)))
         (my-es-buf-name-list (mapcar #'buffer-name my-es-bufs))
         (my-es-buf-name (completing-read
                          "Eshell Buffer : " my-es-buf-name-list)))
    (if (member my-es-buf-name (mapcar #'buffer-name (buffer-list)))
        (switch-to-buffer my-es-buf-name)
      (if (string= "" my-es-buf-name)
          (eshell)
        (progn
          (eshell 42)
          (rename-buffer (concat "*eshell*<" my-es-buf-name ">")))))))

11.1.4 Integrate Eshell with bookmark.el

Extend bookmark.el to support bookmarking Eshell buffers opened to a specific directory. Usual bookmarking commands apply, like C-x r m to capture a bookmark and C-x r l to restore a bookmark.

(use-package eshell-bookmark
  :after eshell
  :config
  (add-hook 'eshell-mode-hook #'eshell-bookmark-setup))

11.2 Command interpreters for other shells

11.2.1 Make command interpreter prompts read-only

Make the command interpreter (comint) prompts read-only.

;; make shell prompts read-only
(setq comint-prompt-read-only t)

11.2.2 Kill term buffers using “q” after session end

Kill term buffers after session end on a “q” keypress.

;; kill term buffers with 'q' after session end
(defun term-handle-exit--close-buffer-on-cmd (&rest args)
  "Kill term buffer with 'q' after session exit."
  (when (null (get-buffer-process (current-buffer)))
    (use-local-map (let ((keymap (make-sparse-keymap)))
                     (define-key keymap (kbd "q")
                       (lambda ()
                         (interactive)
                         (kill-buffer (current-buffer))))
                     keymap))))
(advice-add 'term-handle-exit :after #'term-handle-exit--close-buffer-on-cmd)

11.3 tmux interaction convenenience functions

Define some convenience functions for interaction with the currently active tmux session.

  • tmux-send prompts for a command to send and sends it.
  • tmux-resend resends the previously sent command from the current buffer.
;; convenience functions for sent commands to an active tmux session
;; adapted from https://explog.in/notes/tmux.html

;; track previously sent tmux commands on per-buffer basis
(setq tmux-send--last-command nil)
(make-variable-buffer-local 'tmux-send--last-command)

(defun tmux-send (command)
  "Sends the specified COMMAND to the currently active tmux pane."
  (interactive "sCommand: ")
  (setq tmux-send--last-command command)
  (call-process "tmux" nil nil nil "send-keys" command "Enter"))

(defun tmux-resend ()
  "Resends previously sent command to currently active tmux pane."
  (interactive)
  (if tmux-send--last-command
      (call-process "tmux" nil nil nil "send-keys" tmux-send--last-command "Enter")
    (message "No previously sent command from the current buffer!")))

12 Comparison tools

12.1 Ediff

Ediff is a built-in tool that visualizes the standard Unix diff and patch programs.

Configuration:

  • Always set control window in the same frame as the diff’ed files.
;; always set up Ediff control window in the same frame as the diff,
;; open with horizontal window split instead of the default vertical
(setq ediff-split-window-function 'split-window-horizontally
      ediff-window-setup-function 'ediff-setup-windows-plain)

12.1.1 Ediff copy A and B diff regions to C in a 3-way diff job

Add an Ediff command for copying diff regions for a hunk from both buffers A and B to C when in a 3-way diff job, for example when resolving Git merge conflicts.

Adapted from here.

;; copy diff hunk from buffers A and B to C in 3-way Ediff
;; adapted from https://stackoverflow.com/a/29757750
(defun ediff-copy-A-and-B-to-C (arg)
  "Copies ARGth diff region from both buffers A and B to C.
ARG is a prefix argument.  If nil, copy the current difference region."
  (interactive "P")
  (ediff-barf-if-not-control-buffer)
  (if (eq arg '-) (setq arg -1)) ;; translate neg arg to -1
  (if (numberp arg) (ediff-jump-to-difference arg))
  (ediff-copy-diff ediff-current-difference nil 'C nil
                   (concat
                    (ediff-get-region-contents ediff-current-difference
                                               'A
                                               ediff-control-buffer)
                    (ediff-get-region-contents ediff-current-difference
                                               'B
                                               ediff-control-buffer)))
  ;; recenter with rehighlighting, but no messages
  (ediff-recenter))
(add-hook 'ediff-keymap-setup-hook
          (lambda ()
            (when ediff-3way-job
              (define-key ediff-mode-map "d" 'ediff-copy-A-and-B-to-C))))
(with-eval-after-load 'ediff-help
  (setq ediff-long-help-message-compare3
        (concat ediff-long-help-message-compare3
                "                                                 |"
                "  d -copy A + B regions to C
"
)))

13 Dired

Dired is a built-in directory editor for Emacs.

Additionally load some built-in extra Dired features, including a global binding C-x C-j to directly jump to a Dired buffer for the directory containing the current buffer.

(require 'dired-x) ; extra features
(require 'dired-aux) ; even more extra features
(setq dired-auto-revert-buffer 'dired-directory-changed-p ; when revisiting Dired buffers, refresh if dir has changed on disk
      dired-dwim-target t ; use neighboring dired buffer as default target dir
      dired-listing-switches "-alhvFG" ; more readable file listings
      dired-omit-files (concat dired-omit-files "\\|^\\..+$") ; omit dot files in dired-omit-mode
      dired-recursive-copies 'always ; always copy recursively
      dired-recursive-deletes 'always) ; always delete recursively
;; uncomment below to automatically update Dired buffers every
;; `auto-revert-interval' seconds, at cost of some slowdown
;; (add-hook 'dired-mode-hook #'auto-revert-mode) ; auto-refresh on file change
(add-hook 'dired-mode-hook #'dired-hide-details-mode) ; hide details initially

13.1 Open file at point in Dired using system file open dispatcher

Add binding for opening a file at point in Dired using the system file open dispatcher (typically xdg-open on Linux and open on Mac).

;; bind "z" in dired-mode to open file at point using system command
;; to open files by type
(with-eval-after-load 'dired
  (defun dired--open-file-at-pt ()
    "Opens file at point in Dired using system open command.
This opens the file using the preferred application by filetype."
    (interactive)
    (let ((filename (dired-get-file-for-visit)))
      (start-process "default-app"
                     nil
                     my-system-open-command
                     filename)))
  (define-key dired-mode-map (kbd "z") #'dired--open-file-at-pt))

14 Editing text

14.1 Indent with soft tabs

Use spaces (soft tabs) to indent by default instead of actual tab characters (hard tabs).

Use C-q TAB to input hard tabs if necessary.

;; indent with soft tabs; use C-q <TAB> for real tabs
(setq-default indent-tabs-mode nil)

14.2 Completion-enabled yanking from kill-ring

Add a convenience function for yanking (pasting) from the kill-ring with completion. Completion support is provided through completing-read, which is shadowed by completion frameworks like Icomplete, Ido, Ivy, etc.

Configuration:

  • Rebind yank to completion-enabled yank function.
(defun my-yank-from-kill-ring ()
  "Yank from the kill ring into buffer at point or region.
Uses `completing-read' for selection, which is set by Ido, Ivy, etc."
  (interactive)
  (let ((to-insert (completing-read
                    "Yank : " (cl-delete-duplicates kill-ring :test #'equal))))
    ;; delete selected buffer region if any
    (if (and to-insert (region-active-p))
        (delete-region (region-beginning) (region-end)))
    ;; insert the selected entry from the kill ring
    (insert to-insert)))

;; bind `my-yank-from-kill-ring'
(global-set-key (kbd "C-c y") #'my-yank-from-kill-ring)

14.3 Delete selected region on delete or character input

Use the built-in delsel package to support deleting the selected region on delete or some character input, which is the behavior in line with typical user interface conventions.

;; typing text replaces the active (i.e. selected) region, if any is selected
(delete-selection-mode)

14.4 Single spacing after sentences.

Single spacing after sentences. For abbreviations, use non-breaking spaces that can be input with \\{}nbsp in Org documents, or with C-x 8 SPC for the UTF-8 non-breaking space character.

;; use single spaces after sentences
(setq sentence-end-double-space nil)

14.5 Transparent editing of GPG files

EasyPG Assistant is a GnuPG interface for Emacs.

;; enable transparent editing of GPG files
(require 'epa-file)
(epa-file-enable)

14.6 Iedit mode for editing occurances of the same word simultaneously

Iedit mode enables editing multiple occurances of the same word in the buffer simultaneously.

Usage notes:

  • C-; to edit occurrences of the word under point within the buffer, or C-u 0 C-; to edit occurrences only within the current function. The rest of the list describes bindings when iedit-mode is active.
  • M-H restricts iedit to the current function.
  • Selecting a region while in iedit-mode and calling C-' (or calling M-x iedit-mode) again restricts iedit to that region.
  • M-I restricts iedit to current line
  • M-{ and M-} expands iedit region one-line at a time upwards and downwards (add a prefix argument to reverse instead). This is useful after restricting iedit to the current line, current function or a selected region.
  • M-p and M-n expands up and down to the next occurrence.
  • M-C toggles case sensitivity when searching for occurrences.
  • C-' while editing to toggle narrowing to occurrence lines.
  • TAB and S-TAB to cycle between occurrences.
  • C-; when editing is done to apply changes.
(use-package iedit
  :init (setq iedit-toggle-key-default (kbd "C-;"))
  :config
  ;; advise iedit functions that jump to new point locations to
  ;; perform context actions after they are run
  (dolist (jump-fun '(iedit-next-occurrence
                      iedit-prev-occurrence
                      iedit-goto-first-occurrence
                      iedit-goto-last-occurrence
                      iedit-expand-to-occurrence))
    (advice-add jump-fun :after #'my-after-jump-context-actions)))

14.7 Multiple cursors

multiple-cursors.el is package that enables the creation of multiple cursors in Emacs that all do the same thing simultaneously.

;; multiple cursors
;; using `set-rectangular-region-anchor' is probably the easiest
;; see https://emacs.stackexchange.com/a/773
(use-package multiple-cursors
  :bind (("M-C" . mc/edit-lines)
         ("M-V" . set-rectangular-region-anchor)
         ("C->" . mc/mark-next-like-this)
         ("C-<" . mc/mark-previous-like-this)
         ("C-S-<mouse-1>" . mc/toggle-cursor-on-click))
  :init (setq mc/always-run-for-all nil
              mc/always-repeat-command nil
              mc/insert-numbers-default 1)
  :config
  ;; decrease width of the multiple-cursors bar
  ;; setting a height of 1 ends up rendering a thick bar
  ;; probably because it is too small a value
  (set-face-attribute 'mc/cursor-bar-face nil :height 10))

14.8 Structured editing using Paredit

Paredit provides a minor mode for structured editing S-expression data. Enable it for editing Emacs Lisp buffers and the minibuffer. Also configure it so its commands integrate appropriately with delete-selection-mode.

;; structured editing of S-expressions with Paredit
(use-package paredit
  :commands paredit-mode
  :bind (:map paredit-mode-map
         ("{" . paredit-open-curly)
         ("}" . paredit-close-curly))
  :hook ((emacs-lisp-mode . paredit-mode)
         ;; when in minibuffer via `eval-expression`
         (eval-expression-minibuffer-setup . paredit-mode)
         ;; *scratch* default mode
         (lisp-interaction-mode . paredit-mode))
  :config
  ;; non-terminal bindings, see https://www.racket-mode.com/#paredit
  (unless terminal-frame
    (define-key paredit-mode-map (kbd "M-[") #'paredit-wrap-square)
    (define-key paredit-mode-map (kbd "M-{") #'paredit-wrap-curly))
  ;; make delete-selection-mode work within paredit-mode
  (with-eval-after-load 'delsel
    (put 'paredit-forward-delete 'delete-selection 'supersede)
    (put 'paredit-backward-delete 'delete-selection 'supersede)
    (put 'paredit-open-round 'delete-selection t)
    (put 'paredit-open-square 'delete-selection t)
    (put 'paredit-doublequote 'delete-selection t)
    (put 'paredit-newline 'delete-selection t)))

14.9 Traverse undo history as a tree

The undo-tree package allows the traversal of the undo history as a tree, which makes utilizing Emacs rather flexible undo/redo capabilities much easier.

Default bindings are C-/ to undo, C-S-/ to redo, and C-x u to open a new window whose buffer where the undo history is presented as a tree and can be navigated using the regular movement keys.

;; traverse undo history as a tree, default binding is "C-x u"
(use-package undo-tree
  :init (setq undo-tree-visualizer-relative-timestamps nil)
  :config
  ;; enable globally
  (global-undo-tree-mode))

14.10 Zap up to character

M-z is bound by default to zap-to-char that deletes from the point to (including) the next occurrence of a given character, which is like d f <char> in Vim.

There is also a more useful variant zap-up-to-char which deletes up to but not including the next occurrence of a given character, which is like d t <char> in Vim.

Configuration:

  • Rebind M-z to zap-up-to-char. Use C-u ARG M-z to delete up to the ARG-th next occurrence of a character.
;; bind over `zap-to-char' (defaults to "M-x") with `zap-up-to-char'
(global-set-key [remap zap-to-char] #'zap-up-to-char)

14.11 cycle-spacing

Bind cycle-spacing in place of just-one-space as it is more versatile, utilizing a single command to cycle between one space around point, no spaces around point and original spacing by calling it consecutively.

(global-set-key [remap just-one-space] #'cycle-spacing)

14.12 Join current and next line like “J” in Vim

Join current and next line, like J in Vim.

;; Join next line to end of current line, like "J" in Vim
(defun my-join-next-line ()
  "Join the next line to the end of the current line."
  (interactive)
  (let ((col (current-column)))
    (join-line -1)
    (move-to-column col)))

(global-set-key (kbd "C-S-j") #'my-join-next-line)

14.13 Open new line below/above like “o”/”O” in Vim

Create a new line below/above the current one and move point there, like o~/~O in Vim.

(defun my-open-line-below (n)
  "Open a new line below and go to it.
With arg N, insert N newlines."
  (interactive "*p")
  (end-of-line)
  (newline n)
  (indent-according-to-mode))

(defun my-open-line-above (n)
  "Open a new line above and go to it.
With arg N, insert N newlines."
  (interactive "*p")
  (beginning-of-line)
  (newline n)
  (forward-line (- n))
  (indent-according-to-mode))

;; bind over `open-line' ("C-o") with `my-open-line-below'
(global-set-key [remap open-line] #'my-open-line-below)
;; binding for `my-open-line-above
(global-set-key (kbd "C-S-o") #'my-open-line-above)

14.14 Use DWIM versions of default editing commands

;; use built-in DWIM versions of default editing commands
;; note that comment insertion command ("M-;") is already DWIM-ified
(global-set-key (kbd "M-u") #'upcase-dwim)
(global-set-key (kbd "M-l") #'downcase-dwim)

15 Emacs as an edit server

15.1 Server mode

Use server-mode (toggle) or server-start to start a server from the current Emacs session.

Clients for the server can be created using emacsclient command.

Note: Quitting the main Emacs session that initiated the server mode also quits the server and closes the attached clients. If the goal is to have a headless Emacs server always running, start it with one of the following.

# run headless as a daemon in the background
$ /Applications/Emacs.app/Contents/MacOS/bin/emacsclient --daemon
# run headless as a daemon in the foreground
$ /Applications/Emacs.app/Contents/MacOS/bin/emacsclient --fg-daemon

One option is also to have a headless Emacs server spawn on login, see link.

15.2 SIGUSR1 as a safety valve to restart server when its process is isolated

Send a SIGUSR1 signal to the Emacs process to start or restart the server process.

See link for more info.

;; server mode restart safety valve
(defun restart-emacs-server ()
  "Restarts an Emacs server."
  (interactive)
  (server-force-delete)
  (server-mode 1)
  (message "Restarted Emacs server."))

;; bind SIGUSR1 signal to call `server-restart'
(define-key special-event-map [sigusr1] #'restart-emacs-server)

To test the signal handler, have Emacs send a signal to itself:

(signal-process (emacs-pid) 'sigusr1)

To call the signal handler from the command line, run:

$ pkill -SIGUSR1 -i emacs

16 Non-programming files

16.1 markdown-mode for editing Markdown files

markdown-mode provides a major mode for editing Markdown files.

Pandoc (GitHub) is used for exporting to other formats, so it should be installed (e.g. using conda or by using a release binary).

Configuration:

  • Enable markdown-mode automatically for the common suffixes, except for README.md where gfm-mode is enabled instead for editing Github-flavored Markdown.
  • Pandoc is configured to generate standalone (not self-contained) HTML files and to render TeX expressions using KaTeX (GitHub).
;; major mode for editing Markdown files
(use-package markdown-mode
  :commands (markdown-mode gfm-mode)
  :mode (("README\\.md\\'" . gfm-mode)
         ("\\.md\\'" . markdown-mode)
         ("\\.markdown\\'" . markdown-mode))
  :init
  ;; place header markup only at the start of a line
  ;; syntax highlighting in fenced code blocks
  ;; use underscores for italics instead of asterisks
  (setq markdown-asymmetric-header t
        markdown-fontify-code-blocks-natively t
        markdown-italic-underscore t)
  ;; if available, use pandoc for converting markdown files
  (when (executable-find "pandoc")
    (setq markdown-command (concat "pandoc --from markdown --to html"
                                   " --standalone"
                                   " --katex"
                                   " --highlight-style=pygments"
                                   " --quiet") ; suppress warnings
          markdown-command-needs-filename t)))

16.1.1 markdown-toc for creating tables of content in Markdown buffers

(use-package markdown-toc
  :after markdown-mode)

16.2 YAML

yaml-mode provides a major mode for editing YAML files.

;; provides a major mode for editing YAML files
(use-package yaml-mode
  :commands yaml-mode
  :mode ("\\.ya?ml\\'" . yaml-mode))

17 Org-mode

Org-mode is a major mode for document editing, formatting and organizing, designed to help with taking notes, planning and authoring in Emacs. Org files typically have filenames with the .org suffix.

Note: The version of Org packaged with a given Emacs version is typically adequate, but it is often better to install the newest version of org from ELPA or the Org repository through list-packages.

Examples of good configurations include this.

17.1 Use ELPA version of Org

Use the ELPA version of Org that more closely tracks master.

;; install ELPA version of Org
(my-install-elpa-package 'org)

17.2 Rebind org-force-cycle-archived in older Org versions

org-force-cycle-archived in Org versions <9.4 is bound to C-TAB which shadows the default tab-bar-mode bindings for tab-next.

For these older versions, unbind C-TAB in org-mode-map and bind org-force-cycle-archived to C-c C-TAB which matches the bindings in newer Org versions.

;; rebind `org-force-cycle-archived' in older Org versions to not
;; conflict with the `tab-next' default binding
(with-eval-after-load 'org
  (when (version< (org-version) "9.4")
    (define-key org-mode-map (kbd "<C-tab>") nil)
    (org-defkey org-mode-map (kbd "C-c C-<tab>") #'org-force-cycle-archived)))

17.3 Base Org settings

Customizations:

  • Use ~/org/ as the main Org directory.
  • Set inbox.org in org-directory as the default filing location to be utilized later.
  • Enforce task dependencies
;; set Org directory and inbox file
(setq org-directory (file-truename (file-name-as-directory (expand-file-name "~/org"))))
(defvar my-org-agenda-inbox (concat org-directory "agenda/inbox.org")
  "Path to Org agenda inbox.")
(defvar my-org-someday-inbox (concat org-directory "agenda/someday.org")
  "Path to Org someday inbox.")
(defvar my-org-journal-file (concat org-directory "agenda/journal.org")
  "Path to Org journal file.")
(defvar my-org-scratch-file (concat org-directory "agenda/scratch.org")
  "Path to Org scratch file.")
(defvar my-org-websnippet-file (concat org-directory "agenda/websnippets.org")
  "Path to Org websnippet file.")

;; basic Org-mode settings
(setq org-adapt-indentation nil ; don't auto-indent when promoting/demoting
      org-attach-dir-relative t ; use relative directories when setting DIR property using `org-attach-set-directory'
      ;; org-blank-before-new-entry '((heading . nil) ; don't auto-add new lines
      ;;                              (plain-list-item . nil)) ; same as above
      org-catch-invisible-edits 'show-and-error
      org-confirm-babel-evaluate nil ; don't confirm before evaluating code blocks in Org documents
      org-cycle-separator-lines 2 ; collapse single item separator lines when cycling
      org-deadline-warning-days 3 ; warn starting 3 days before deadline
      org-edit-src-content-indentation 2
      org-enforce-todo-checkbox-dependencies t
      org-enforce-todo-dependencies t
      org-fontify-done-headline t
      org-fontify-quote-and-verse-blocks t
      org-fontify-whole-heading-line t
      org-goto-interface 'outline-path-completion
      org-hide-emphasis-markers nil
      org-hide-leading-stars nil
      org-highlight-latex-and-related '(latex script entities) ; highlight LaTeX fragments with the `org-highlight-latex-and-related' face
      org-image-actual-width (list (/ (display-pixel-width) 3)) ; auto-resize displayed images to one-third of display width
      org-link-file-path-type 'adaptive ; use relative paths for links to files in Org file dir or subdirs, absolute otherwise
      org-log-done 'time ; log time that task was marked DONE
      org-log-into-drawer t
      org-outline-path-complete-in-steps nil
      org-pretty-entities t
      org-pretty-entities-include-sub-superscripts nil ; don't render sub/superscripts in-buffer
      org-return-follows-link t
      org-src-fontify-natively nil ; don't syntax color org source blocks
      org-src-preserve-indentation t ; preserve src code block indentation on export and when switching btw org buffer and edit buffer
      org-src-strip-leading-and-trailing-blank-lines t
      org-src-tab-acts-natively t
      org-src-window-setup 'current-window ; reuse Org file window for editing source blocks when using "C-c '"
      org-startup-folded t
      org-startup-indented nil
      org-treat-S-cursor-todo-selection-as-state-change nil
      org-use-fast-todo-selection t
      org-use-speed-commands t)

;; make sure UUIDs generated for Org usage are alway upcased, which
;; solves issues with synced directories, for example Linux generates
;; lower case UUIDs while Mac generates upper case UUIDs.
(with-eval-after-load 'org-id
  (defun org-id-uuid--around-upcase (orig-fun &rest args)
    "Advice for `org-id-uuid' to upcase the uuids it outputs.
ORIG-FUN is the original function.
ARGS are the arguments provided to ORIG-FUN."
    (let ((uuid (apply orig-fun args)))
      (upcase uuid)))
  (advice-add 'org-id-uuid :around
              'org-id-uuid--around-upcase))

17.4 Bind over org-open-line with version calling my-org-open-line-below

Bind over org-open-line with a variant that calls my-open-line-below instead.

(defun my-org-open-line-below (n)
  "Insert a new row in tables, call `my-open-line-below' elsewhere.
If `org-special-ctrl-o' is nil, call `my-open-line-below' everywhere.
As a special case, when a document starts with a table, allow to
call `open-line' on the very first character."
  (interactive "*p")
  (if (and org-special-ctrl-o (/= (point) 1) (org-at-table-p))
      (org-table-insert-row)
    (my-open-line-below n)))

;; bind over `org-open-line' to call `my-org-open-line-below' instead
;; making it consistent with customized global-mode-map "C-o"
(with-eval-after-load 'org-keys
  (define-key org-mode-map (kbd "C-o") #'my-org-open-line-below))

17.5 Org TODO keywords and task states

Possible Org task states:

  • TODO: Inactive task.
  • NEXT: Active task. Keep to just a few (less open loops).
  • DONE: Completed task.
  • HOLD: Paused inactive task.
  • WAIT: Paused active task, waiting for external action.
  • CANX: Canceled task.
;; Set possible Org task states
;; Diagram of possible task state transitions
;;      ---------------------
;;      |                   |
;;      |                   V
;; --> TODO.. -> NEXT... -> DONE ----->
;;     | Λ  |    |   | Λ    Λ      |
;;     V |  |    |   V |    |      |
;;     HOLD |    |   WAIT ---      |
;;      |   |    |   |             |
;;      V   V    V   V             |
;;     CANX........... -------------
;;      (note records why it was cancelled)
(setq org-todo-keywords '((sequence "TODO(t)" "NEXT(n)" "|" "DONE(d!)")
                          (sequence "HOLD(h@/!)" "WAIT(w@/!)" "|" "CANX(c@/!)")))

17.6 Org basic capture templates

;; Org capture templates
(defun my-org-goto-end-of-org-file ()
  "Goto end of selected user file starting from `org-directory'."
  (let ((path (read-file-name
               "File: " org-directory nil nil nil
               (lambda (x) (string-suffix-p ".org" x)))))
    (find-file path)
    (goto-char (point-max))))

(setq org-capture-templates '(("t" "New Task" entry (file my-org-agenda-inbox)
                               "* TODO %i%?\n%U")
                              ("l" "Linked Task" entry (file my-org-agenda-inbox)
                               "* TODO %a%?\n%U")
                              ("s" "Someday Task" entry (file my-org-someday-inbox)
                               "* TODO %i%?\n%U")
                              ("i" "Interrupt Task" entry (function my-org-goto-end-of-org-file)
                               "* NEXT %i%?\n%U"
                               :jump-to-captured t :clock-in t :clock-resume t)
                              ("j" "Journal Entry" entry
                               (file+olp+datetree my-org-journal-file)
                               "**** %?\n%T"
                               :tree-type week :clock-in t :clock-resume t)
                              ("J" "Schedule Journal Entry" entry
                               (file+olp+datetree my-org-journal-file)
                               "**** %?\n%T"
                               :tree-type week :time-prompt t)))

17.7 Maximize Org capture buffers

Have Org capture buffers always be maximized. Restore the window configuration after capturing the task.

(with-eval-after-load 'org
  ;; maximize org-capture buffer
  (defun my-org-capture-setup (&rest args)
    "Save window configuration prior to `org-capture'."
    (set-frame-parameter
     nil
     'my-org-capture-prior-config
     (current-window-configuration)))
  (defun my-org-capture-teardown ()
    "Restore window configuration prior to `org-capture'."
    (let ((prior-window-configuration (frame-parameter
                                       nil
                                       'my-org-capture-prior-config)))
      (when prior-window-configuration
        (set-window-configuration prior-window-configuration))))
  (advice-add 'org-capture :before 'my-org-capture-setup)
  (add-hook 'org-capture-mode-hook 'delete-other-windows)
  (add-hook 'org-capture-after-finalize-hook 'my-org-capture-teardown))

17.8 Org tags

;; tags (note that tags within the same group are mutually exclusive)
(setq org-tag-alist '((:startgroup) ;; export
                      ("export" . ?9)
                      ("noexport" . ?0)
                      (:endgroup)
                      ;; prioritization (e.g. Eisenhower matrix)
                      ("important" . ?1)
                      ("urgent" . ?2)
                      (:newline)
                      ;; entry context, in addition to category
                      ("@work" . ?w)
                      ("@life" . ?l)
                      ("@learn" . ?e)
                      (:startgroup) ;; special meeting types
                      ("hiring" . ?h)
                      ("managing" . ?m)
                      ("vendor" . ?v)
                      ("sales" . ?s)
                      ("strategy" . ?t)
                      (:endgroup)))

17.9 Org export global macros

Useful Org export macros that are enabled globally.

To color text (works with LaTeX and HTML exports):

{{{color(colorname, text)}}}

To insert filler text:

{{{loremipsum}}}

To export different text for LaTeX and for other formats:

{{{if-latex-else(latex-specific text,other text)}}}
;; `org-export' macros
(with-eval-after-load 'ox
  ;; color macro, {{{color(colorname, text)}}} to use
  (push `("color"
          .
          ,(concat "@@latex:\\textcolor{$1}{$2}@@"
                   "@@html:<span style=\"color:$1\">$2</span>@@"))
        org-export-global-macros)
  ;; placeholder text, {{{loremipsum}}} to use
  (push `("loremipsum"
          .
          ,(mapconcat 'identity
                      '("Lorem ipsum dolor sit amet, consectetur"
                        "adipisicing elit, sed do eiusmod tempor"
                        "incididunt ut labore et dolore magna"
                        "aliqua. Ut enim ad minim veniam, quis"
                        "nostrud exercitation ullamco laboris nisi"
                        "ut aliquip ex ea commodo consequat. Duis"
                        "aute irure dolor in reprehenderit in"
                        "voluptate velit esse cillum dolore eu"
                        "fugiat nulla pariatur. Excepteur sint"
                        "occaecat cupidatat non proident, sunt in"
                        "culpa qui officia deserunt mollit anim id"
                        "est laborum."
                        "\n\n"
                        "Curabitur pretium tincidunt lacus. Nulla"
                        "gravida orci a odio. Nullam varius, turpis"
                        "et commodo pharetra, est eros bibendum elit,"
                        "nec luctus magna felis sollicitudin mauris."
                        "Integer in mauris eu nibh euismod gravida."
                        "Duis ac tellus et risus vulputate vehicula."
                        "Donec lobortis risus a elit. Etiam tempor."
                        "Ut ullamcorper, ligula eu tempor congue,"
                        "eros est euismod turpis, id tincidunt sapien"
                        "risus a quam. Maecenas fermentum consequat"
                        "mi. Donec fermentum. Pellentesque malesuada"
                        "nulla a mi. Duis sapien sem, aliquet nec,"
                        "commodo eget, consequat quis, neque. Aliquam"
                        "faucibus, elit ut dictum aliquet, felis nisl"
                        "adipiscing sapien, sed malesuada diam lacus"
                        "eget erat. Cras mollis scelerisque nunc."
                        "Nullam arcu. Aliquam consequat. Curabitur"
                        "augue lorem, dapibus quis, laoreet et,"
                        "pretium ac, nisi. Aenean magna nisl, mollis"
                        "quis, molestie eu, feugiat in, orci. In hac"
                        "habitasse platea dictumst.")
                      " "))
        org-export-global-macros)
  ;; flow control for latex-specific text and otherwise
  ;; {{{if-latex-else(latex text, other text)}}} to use
  (push '("if-latex-else"
          .
          "(eval (if (org-export-derived-backend-p
                     org-export-current-backend
                     'latex)
                    $1
                  $2))")
        org-export-global-macros))

17.10 Set applications used by Org for opening different file types

By default, Org mode uses the system defaults for several file types. Have Org mode open PDF files within Emacs instead of using the default (which is using the system viewer).

;; have Org mode open PDF files within Emacs
(with-eval-after-load 'org
  (push '("\\.pdf\\'" . emacs) org-file-apps))

17.11 Org Agenda

Org-mode provides agenda views that give an overview of open action items or events with specific scheduled or deadline dates in Org files specified by org-agenda-files.

A number of customizations are done here, including defining a view that displays 3-day agenda and undated TODO entries.

Important: Don’t add scheduled dates or deadlines unless necessary, it clutters up the agenda view.

Note: Archiving DONE and CANX items every so often helps to keep Org agenda parsing and operations speedy (link):

  • M-x org-agenda to bring up the agenda menu.
  • t to list TODO items.
  • N r (where N is the number corresponding to a task state) to select the DONE (N=3 in this configuration) or the CANX (N=6 in this configuration) task state.
  • * to select all listed items.
  • B $ to bulk archive the selected items to their respective <filename>.org_archive files.

Workflow:

  • Add new items to inbox.
  • Periodically refile inbox items into appropriate agenda project file.
  • Process the refiled items as per normal TODO task workflow.

Additional notes:

  • inbox.org and someday.org are not part of the Agenda files, but are accessed via specific agenda views.
;; org-agenda settings:
;; - narrow to subtree in org-agenda-follow-mode ("F" in agenda)
;; - full-frame Agenda view
;; - use ~/ORG-DIRECTORY/*.org files as Org agenda files
;; - de-duplicate entries with both a scheduled and deadline date
;; - don't show entries with scheduled or deadline dates that are done
(setq org-agenda-follow-indirect t
      org-agenda-restore-windows-after-quit t
      org-agenda-skip-deadline-if-done t
      org-agenda-skip-deadline-prewarning-if-scheduled 'pre-scheduled
      org-agenda-skip-scheduled-delay-if-deadline nil
      org-agenda-skip-scheduled-if-deadline-is-shown t
      org-agenda-skip-scheduled-if-done t
      org-agenda-start-on-weekday nil
      org-agenda-window-setup 'only-window
      org-agenda-files (seq-filter
                        (lambda (x)
                          (and
                           (not (string-suffix-p my-org-agenda-inbox x))
                           (not (string-suffix-p my-org-someday-inbox x))
                           (not (string-suffix-p my-org-scratch-file x))
                           (not (string-suffix-p my-org-websnippet-file x))))
                        (file-expand-wildcards (concat org-directory "agenda/*.org"))))

;; add separator between each day in agenda view
(setq org-agenda-format-date
      (lambda (date)
        (let* ((datestr (org-agenda-format-date-aligned date))
               (separator-width (- (window-width)
                                   (string-width datestr)
                                   1)))
          (concat "\n" datestr " " (make-string separator-width ?_)))))

;; helper functions for custom agenda commands
(defun my-org-agenda-to-deadline-prefix-str ()
  "Descriptor string for days to deadline for Org entry at point."
  (let ((deadline (org-get-deadline-time (point))))
    (when deadline
      (let ((days-left (org-time-stamp-to-now (format-time-string "%F" deadline))))
        (cond ((< days-left (- 1)) (format "%3d d. ago" (- days-left)))
              ((= days-left (- 1)) (format " Yesterday" days-left))
              ((= days-left 0)     (format "     Today" days-left))
              ((= days-left 1)     (format "  Tomorrow" days-left))
              ((> days-left 1)     (format " In %3d d." days-left)))))))

;; custom agenda commands
(setq org-agenda-custom-commands
      `(("." "Today's agenda and all TODO entries"
         ((agenda "" ((org-agenda-span 1)))
          (todo "NEXT" ((org-agenda-todo-ignore-with-date nil)
                        (org-agenda-sorting-strategy '(ts-up priority-down effort-up category-keep alpha-up))))
          (todo "WAIT" ((org-agenda-todo-ignore-with-date nil)
                        (org-agenda-sorting-strategy '(ts-up priority-down effort-up category-keep alpha-up))))
          (todo "TODO" ((org-agenda-todo-ignore-with-date nil)
                        (org-agenda-sorting-strategy '(ts-up priority-down effort-up category-keep alpha-up))))
          (todo "HOLD" ((org-agenda-todo-ignore-with-date nil)
                        (org-agenda-sorting-strategy '(ts-up priority-down effort-up category-keep alpha-up))))))
        ("u" "Undated TODO entries"
         ((todo "NEXT" ((org-agenda-todo-ignore-with-date t)
                        (org-agenda-sorting-strategy '(priority-down effort-up category-keep alpha-up))))
          (todo "WAIT" ((org-agenda-todo-ignore-with-date t)
                        (org-agenda-sorting-strategy '(priority-down effort-up category-keep alpha-up))))
          (todo "TODO" ((org-agenda-todo-ignore-with-date t)
                        (org-agenda-sorting-strategy '(priority-down effort-up category-keep alpha-up))))
          (todo "HOLD" ((org-agenda-todo-ignore-with-date t)
                        (org-agenda-sorting-strategy '(priority-down effort-up category-keep alpha-up))))))
        ("d" "Deadlines"
         ((tags-todo "DEADLINE<\"<today>\"" ; tasks that are past deadline
                     ((org-agenda-prefix-format " %(my-org-agenda-to-deadline-prefix-str) %i %-12:c%?-12t% s")
                      (org-agenda-sorting-strategy '(deadline-up priority-down scheduled-up effort-up category-keep alpha-up))))
          (tags-todo "DEADLINE>=\"<today>\"" ; tasks with upcoming deadlines
                     ((org-agenda-prefix-format " %(my-org-agenda-to-deadline-prefix-str) %i %-12:c%?-12t% s")
                      (org-agenda-sorting-strategy '(deadline-up priority-down scheduled-up effort-up category-keep alpha-up))))))
        ("i" "Inbox entries"
         ((alltodo "" ((org-agenda-files '(,my-org-agenda-inbox))
                       (org-agenda-sorting-strategy '(priority-down deadline-up scheduled-up effort-up category-keep alpha-up))))))
        ("o" "Someday entries"
         ((alltodo "" ((org-agenda-files '(,my-org-someday-inbox))
                       (org-agenda-sorting-strategy '(priority-down deadline-up scheduled-up effort-up category-keep alpha-up))))))))

17.12 Org refiling of tasks and subtrees

Set up Org refile targets:

  • Current buffer up to a max depth of 9 levels.
  • Org Agenda files corresponding to non-directory entries of org-agenda-files at the top-level of each file.
;; allow refiling up to 9 levels deep in the current buffer
;; and 3 levels deep in Org agenda files
;; allow refiling to the top level
(setq org-refile-targets `((nil . (:maxlevel . 9)) ;; current buffer
                           ;; top-level of regular `org-agenda-files' files
                           (,(seq-filter
                              'file-regular-p
                              org-agenda-files) . (:level . 0)))
      org-refile-use-outline-path 'file
      org-refile-allow-creating-parent-nodes 'confirm)

17.13 Automatic text wrapping in Org-mode documents

(add-hook 'org-mode-hook #'visual-line-mode)

17.14 Compile Org documents to PDF with LaTeX

Org-mode supports an Org → LaTeX → PDF build chain for compiling Org documents to PDF files.

By default, this build process utilizes the base latex compiler and does not handle BibTeX bibliography database files (.bib files).

It is better change to this to a more modern compiler like LuaTeX for better font and unicode support, and add a BibTeX compiler like Biber to the build chain.

;; compile Org documents to PDF with LuaTeX and Biber
(when (executable-find "lualatex")
  (with-eval-after-load 'org
    (setq org-latex-pdf-process
          '("lualatex -interaction nonstopmode -output-directory %o %f"
            "lualatex -interaction nonstopmode -output-directory %o %f"))
    (if (executable-find "biber")
        (push "biber %b" org-latex-pdf-process))
    (push "lualatex -interaction nonstopmode -output-directory %o %f"
          org-latex-pdf-process)))

17.15 ox-md for Org export backend to Markdown

Load the built-in Org backend for exporting Org documents to Markdown.

;; load Org backend for exporting to Markdown
(with-eval-after-load 'org
  (require 'ox-md))

17.16 Generating an Org agenda clock table by tag

Copy and evaluate (using C-c C-c) the following block to an Org mode buffer, modify the :match option parameters to include/exclude tags of interest and add/remove/change the other options as appropriate. See the Org docs for using the clock table and the tag match syntax.

#+end

17.17 Old-style structure expansions using Org Tempo

Enable Org pre-9.2 structure expansions, e.g. <s followed by TAB. See Org documentation for more info.

;; Enable Org pre-9.2 structure expansions, e.g. ~<s~ followed by TAB
(with-eval-after-load 'org
  (require 'org-tempo nil :noerror))

18 Programming

18.1 Flymake syntax checker

Flymake is a built-in on-the-fly syntax checker, with updated versions available from the GNU ELPA repository.

To display the error message at point in the minibuffer, do C-h . while the point is over an error.

To define new Flymake backend, refer to the docstring of flymake-diagnostic-functions, the Flymake manual or the code of existing backends.

If using this, it is typical to not enable Flycheck.

;; basic Flymake customizations
(setq flymake-no-changes-timeout 0.5 ;; auto check buffer change wait time
      flymake-start-on-save-buffer nil) ;; don't run checks when saving

;; deferred Flymake customizations
(with-eval-after-load 'flymake
  ;; don't use legacy Flymake checker
  (remove-hook 'flymake-diagnostic-functions #'flymake-proc-legacy-flymake)
  ;; function for toggling Flymake diagnostics window
  (defun my-toggle-flymake-diagnostics ()
    "Toggles flymake diagnostics window for current buffer."
    (interactive)
    (if flymake-mode
        (let* ((buf-name (buffer-name (current-buffer)))
               (flymake-winds (condition-case nil
                                  (get-buffer-window-list
                                   (concat "*Flymake diagnostics for " buf-name "*"))
                                (error nil))))
          (if flymake-winds
              (dolist (wind flymake-winds) (quit-window nil wind))
            (flymake-show-diagnostics-buffer)))))
  ;; shorten Flymake mode line symbol
  (defun my-flymake-modeline-filter (ret)
    "Filter function for `flymake--mode-line-format`."
    (setf (seq-elt (car ret) 1) " FlyM")
    ret)
  (advice-add #'flymake--mode-line-format :filter-return
              #'my-flymake-modeline-filter)
  ;; convenience bindings
  (define-key flymake-mode-map (kbd "C-c ! n") #'flymake-goto-next-error)
  (define-key flymake-mode-map (kbd "C-c ! p") #'flymake-goto-prev-error)
  (define-key flymake-mode-map (kbd "C-c ! l") #'my-toggle-flymake-diagnostics))

;; enable Flymake when editing Emacs Lisp buffers
(add-hook 'emacs-lisp-mode-hook #'flymake-mode)

18.1.1 Flymake and TRAMP

Flymake sometimes has issues with remote files edited over TRAMP. For some problematic file, the easiest solution is to add an Emacs file variable to the first line of the file to turn off Flymake (see following code for a Python file example).

# -*- eval:(flymake-mode-off) -*-
;; TODO add :around advice to flymake-mode to not enable mode
;; when editing a remote file

18.2 Emacs Lisp

18.2.1 el-patch

el-patch provides a way to customize Emacs Lisp functions.

(use-package el-patch
  :demand t)

18.2.1.1 Modify lisp-indent-function to handle property list indentation

Modify lisp-indent-function so that it indents property lists in Emacs Lisp (and other Lisps) in the expected manner (like in Clojure).

Uses functions from the el-patch package, so make sure that package is loaded before this code block.

;; Modifies lisp indentation to handle property lists used as
;; data structures
;; --- DEFAULT BEHAVIOR
;;  `(:token ,token
;;           :token-quality ,quality)
;; ---
;; --- DESIRED BEHAVIOR
;;  `(:token ,token
;;    :token-quality ,quality)
;; ---
;; Copied from https://emacs.stackexchange.com/questions/10230/
(with-eval-after-load 'lisp-mode
  (el-patch-defun lisp-indent-function (indent-point state)
    "This function is the normal value of the variable `lisp-indent-function'.
The function `calculate-lisp-indent' calls this to determine
if the arguments of a Lisp function call should be indented specially.
INDENT-POINT is the position at which the line being indented begins.
Point is located at the point to indent under (for default indentation);
STATE is the `parse-partial-sexp' state for that position.
If the current line is in a call to a Lisp function that has a non-nil
property `lisp-indent-function' (or the deprecated `lisp-indent-hook'),
it specifies how to indent.  The property value can be:
* `defun', meaning indent `defun'-style
  (this is also the case if there is no property and the function
  has a name that begins with \"def\", and three or more arguments);
* an integer N, meaning indent the first N arguments specially
  (like ordinary function arguments), and then indent any further
  arguments like a body;
* a function to call that returns the indentation (or nil).
  `lisp-indent-function' calls this function with the same two arguments
  that it itself received.
This function returns either the indentation to use, or nil if the
Lisp function does not specify a special indentation."
    (el-patch-let (($cond (and (elt state 2)
                               (el-patch-wrap 1 1
                                 (or (not (looking-at "\\sw\\|\\s_"))
                                     (looking-at ":")))))
                   ($then (progn
                            (if (not (> (save-excursion (forward-line 1) (point))
                                        calculate-lisp-indent-last-sexp))
                                (progn (goto-char calculate-lisp-indent-last-sexp)
                                       (beginning-of-line)
                                       (parse-partial-sexp (point)
                                                           calculate-lisp-indent-last-sexp 0 t)))
                            ;; Indent under the list or under the first sexp on the same
                            ;; line as calculate-lisp-indent-last-sexp.  Note that first
                            ;; thing on that line has to be complete sexp since we are
                            ;; inside the innermost containing sexp.
                            (backward-prefix-chars)
                            (current-column)))
                   ($else (let ((function (buffer-substring (point)
                                                            (progn (forward-sexp 1) (point))))
                                method)
                            (setq method (or (function-get (intern-soft function)
                                                           'lisp-indent-function)
                                             (get (intern-soft function) 'lisp-indent-hook)))
                            (cond ((or (eq method 'defun)
                                       (and (null method)
                                            (> (length function) 3)
                                            (string-match "\\`def" function)))
                                   (lisp-indent-defform state indent-point))
                                  ((integerp method)
                                   (lisp-indent-specform method state
                                                         indent-point normal-indent))
                                  (method
                                   (funcall method indent-point state))))))
      (let ((normal-indent (current-column))
            (el-patch-add
              (orig-point (point))))
        (goto-char (1+ (elt state 1)))
        (parse-partial-sexp (point) calculate-lisp-indent-last-sexp 0 t)
        (el-patch-swap
          (if $cond
              ;; car of form doesn't seem to be a symbol
              $then
            $else)
          (cond
           ;; car of form doesn't seem to be a symbol, or is a keyword
           ($cond $then)
           ((and (save-excursion
                   (goto-char indent-point)
                   (skip-syntax-forward " ")
                   (not (looking-at ":")))
                 (save-excursion
                   (goto-char orig-point)
                   (looking-at ":")))
            (save-excursion
              (goto-char (+ 2 (elt state 1)))
              (current-column)))
           (t $else)))))))

18.3 fish shell scripts

18.3.1 fish-mode

fish-mode is a major-mode for working with fish shell scripts.

(use-package fish-mode
  :init (setq fish-enable-auto-indent t
              fish-indent-offset 4))

19 Search

19.1 wgrep for writable grep buffers with changes pushed to files

wgrep.el makes grep buffers writable with changes pushed to files. Enable with C-c C-p when in a *grep* buffer.

Automatically installed by rg.el.

;; "C-c C-p" in grep bufs allow writing with changes pushed to files
(use-package wgrep
  :config (setq wgrep-auto-save-buffer nil
                wgrep-too-many-file-length 10))

19.2 Imenu

Imenu allows jumping to major definitions in a file by name. Have Imenu automatically rescan whenever the content changes.

(setq imenu-auto-rescan t)

19.2.1 Imenu extension to list headings in a side buffer

imenu-list is an Imenu extension that opens up a side buffer listing the major definitions and headings in the current buffer. This configuration customizes it such that on jumping to a definition or heading, it pulses the destination line once and automatically closes the imenu-list.

;; show imenu as a list in a side buffer
(use-package imenu-list
  :defer t
  :after imenu
  :bind ("C-c I" . imenu-list-smart-toggle)
  :config
  (setq imenu-list-focus-after-activation t)
  ;; pulse target after selecting
  (add-hook 'imenu-list-after-jump-hook
            (lambda () (pulse-momentary-highlight-one-line (point))))
  ;; close imenu list after going to entry
  (advice-add 'imenu-list-goto-entry :after 'imenu-list-quit-window))

19.3 Automatically fold characters in Isearch

Configure Isearch to automatically fold characters when searching, for instance when searching for a quote " the search will also match round quotes and , or the left- and right-pointing double angle quotation marks « and ».

References:

;; fold characters when searching with Isearch
(setq search-default-mode #'char-fold-to-regexp)

20 Visual

20.1 Change default cursor type

Change cursor type to a bar. By default, the cursor blinks 10 times and stops blinking after when idling. Modify blink-cursor-blinks to change this number.

;; set cursor after initialization
(setq-default cursor-type 'bar)

20.2 Hide cursor in non-selected windows

Don’t show the cursor in non-selected windows. This is useful as an additional visual indicator for the selected window.

;; hide cursor in non-selected windows
(setq-default cursor-in-non-selected-windows nil)

20.3 Decorations

Remove toolbar and menu to maximize space usage.

;; remove unused UI elements
(if (fboundp 'scroll-bar-mode)
    (scroll-bar-mode -1))
(if (fboundp 'tool-bar-mode)
    (tool-bar-mode -1))
;; (if (and (not (display-graphic-p))
;;          (fboundp 'menu-bar-mode))
;;     (menu-bar-mode -1))

20.4 Display line numbers when editing code

Display line numbers by default when editing code.

;; display line numbers by default when editing code
(add-hook 'prog-mode-hook
          (lambda ()
            (display-line-numbers-mode 1)))

20.5 Show trailing whitespace when editing code

Show trailing whitespace by default when editing code.

;; show trailing whitespace when editing code
(add-hook 'prog-mode-hook
          (lambda ()
            (setq show-trailing-whitespace t)))

20.6 Show point location column number in the mode line

Show the column number of the point location in the mode line.

;; show point location column number in mode line
(setq column-number-mode t)

20.7 Show matching parentheses immediately

Show matching parentheses while editing without delay.

;; show matching parentheses with no delay
(setq show-paren-delay 0)
(show-paren-mode 1)

20.8 Add frame internal border

Adding a default frame internal border creates nicer typography at the cost of displaying a few less characters on screen.

;; add internal frame border
(add-to-list 'default-frame-alist
             `(internal-border-width . 12))
(defun my-default-frame-border-teardown ()
  "Removes internal-border-width entries from `default-frame-alist'."
  (setq default-frame-alist
        (assq-delete-all 'internal-border-width default-frame-alist)))
;; add teardown function to be run before closing Emacs, which needs
;; to run early when closing so add it to the end of `after-init-hook'
(add-hook 'after-init-hook
          (lambda ()
            (add-hook 'kill-emacs-hook #'my-default-frame-border-teardown))
          t)

20.9 Censor text in specific buffers

Custom package providing a minor mode for censoring text in a buffer. One scenario where this is useful is to hide sensitive content during a screen share in case the buffer is opened accidentally.

censor-mode is buffer-local and will always apply to the current buffer when toggled. global-censor-mode is global and will apply to all buffers that either satisfy a predicate function or whose name matches a regexp in censor-include.

By default, censor-include matches all buffers with names that have the .gpg suffix (the default for GnuPG-encrypted files).

(require 'censor)

20.10 Hide lines that are too long

too-long-lines-mode provides a global minor mode that uses overlays to truncate very long lines, which can otherwise slow down Emacs. This package is not in MELPA.

An alternative way to reduce slowdown from long lines is using the built-in so-long package, which instead tries to disable major and minor modes that may contribute to the slowdown.

(require 'too-long-lines-mode)
(too-long-lines-mode 1)

21 Web

21.1 Browsing

21.1.1 Emacs Web Wowser

Emacs Web Wowser (eww) is a built-in Emacs web browser. It supports text and images, but not webfonts or Javascript.

Customizations:

  • Use the lightweight version of DuckDuckGo for web searches by default.
  • Don’t render images by default.
;; built-in Emacs text web browser
(use-package eww
  :ensure nil ;; built-in
  :commands (eww eww-follow-link)
  :init (setq eww-search-prefix "https://duckduckgo.com/lite?q=")
  :config
  ;; don't render images in HTML pages by default
  (setq-default shr-inhibit-images t)
  ;; toggle for enabling and disabling images
  (defun eww--toggle-images ()
    "Toggle displaying of images when rendering HTML."
    (interactive)
    (setq-local shr-inhibit-images (not shr-inhibit-images))
    (eww-reload)
    (message "Images are now %s" (if shr-inhibit-images "off" "on")))
  (define-key eww-mode-map "I" #'eww--toggle-images))

21.2 Hacky workaround for GnuTLS timing issues

The Emacs usage of GnuTLS has some timing-related issues, especially with url-retrieve-synchronously and on GUI Emacs. This appears to be a sort of race condition, and a bug reporter thinks that GnuTLS is not properly waiting for the server to write the initial greeting.

See link for more information and a workaround.

(defun open-gnutls-stream--after-sleep-250ms (&rest args)
  "Workaround for race condition bug in `open-gnutls-stream'.

Adds 250ms to the opening of GnuTLS connections.

ARGS is a list of the original arguments passed to
`open-gnutls-stream' and is ignored.

See https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=930573#10
for more information."
  (sleep-for 0 250))

;; add advice to `open-gnutls-stream' to have it sleep for 250ms
;; as a workaround for GnuTLS race condition bug
(advice-add #'open-gnutls-stream :after #'open-gnutls-stream--after-sleep-250ms)

21.3 Network security

Increased network security settings.

;; increase network security settings
(setq gnutls-verify-error t)
(setq gnutls-min-prime-bits 1024)
(setq network-security-level 'high)
(setq nsm-save-host-names t)

21.4 HTTP requests privacy

More private HTTP requests.

;; HTTP requests privacy settings
(setq url-cookie-untrusted-urls '(".*")) ;; no cookies
(setq url-privacy-level 'paranoid) ;; more private HTTP requests
(url-setup-privacy-info) ;; apply `url-privacy-level'

22 Writing

22.1 Spell checking

22.1.1 Flyspell for spell checking in Emacs

Flyspell is a built-in spell checker that comes in two flavors:

  • flyspell-mode checks spelling anywhere in the buffer.
  • flyspell-prog-mode checks spelling only in comments and strings.

When one of the modes is active, unrecognized words are highlighted. To maintain performance, it only checks words that are newly typed or that the point moves across.

Activating the mode also enables a few bindings that act on unrecognized words:

  • C-c $ pops up menu of corrections for the word before the point.
  • C-. corrects the current word. Pressing it repeatedly will propose successive correction candidates.
  • C-; corrects the previous word. Pressing it repeated will propose successively correction candidates.
  • C-, jumps to the next unrecognized word.

Note: Flyspell uses as its backend a spell checker like Aspell, so one needs to be installed on system. An appropriate dictionary or dictionaries for the spell checker should also be installed.

22.1.1.1 Advise flyspell-goto-next-error to run after-jump context actions

Run custom after-jump context actions post-~flyspell-goto-next-error~ calls. In particular, when jumping to a position within a folded region that region is automatically expanded after.

;; advise flyspell jump functions to perform context actions after
;; they are run
(with-eval-after-load 'flyspell
  (advice-add 'flyspell-goto-next-error :after #'my-after-jump-context-actions))

22.1.1.2 Unbind conflicting Flyspell bindings

Unbind some default Flyspell bindings:

  • C-; which conflicts with the iedit bindings
;; unbind some Flyspell default bindings that conflict with other more
;; useful bindings
(with-eval-after-load 'flyspell
  (define-key flyspell-mode-map (kbd "C-;") nil)) ; `iedit-mode' binding

22.1.2 Aspell command-line spell checker

Aspell is a spell checker. There are a number of alternatives but they are either deprecated (Ispell), similar (Hunspell), or language-specific (Voikko for Finnish).

This or another supported spell checker has to be installed in order for Flyspell to work. Additionally, a dictionary for the spell checker should also be installed.

If using MacPorts, installation instructions follow for Aspell and its English dictionary.

$ sudo port install aspell
$ sudo port install aspell-dict-en

22.2 dictionary.el for looking up word definitions

dictionary.el provides a frontend for accessing a local or online dictionary server that implements RFC 2229.

;; provides word lookups from a dictionary server
;; `dictionary-server' can be set to "localhost" to use a local
;; dictionary server like dictd or GNU Dico that implements RFC 2229
(use-package dictionary
  :init (setq dictionary-server "dict.org"
              dictionary-default-dictionary "*"))

22.2.1 GNU Collaborative International Dictionary of English

The GNU Collaborative International Dictionary of English (GCIDE) is a free dictionary derived from the Webster’s 1913 dictionary (which provides definitions that are fuller and more descriptive than more modern dictionaries) supplemented with newer definitions from Wordnet and other sources.

It is available as a source on dict.org which can be accessed by dictionary.el functions.

Downloads are available for use with a local dictionary server. One local dictionary server that has good GCIDE support is GNU Dico, which has a bundled interface module for the dictionary.

22.3 typo.el for typographical editing

typo.el is a minor mode that makes insertion of typographically useful unicode characters easier.

Enabling the buffer-local typo-mode does the following:

  • Automatically convert ' to and , and " to and .
  • Pressing ', " and - cycles among their different variants. For example, repeatedly pressing - cycles between en-dash, em-dash, and other dash-like glyphs.
  • Three periods in a row get automatically replaced by an ellipsis.

Enabling typo-global-mode does the following:

  • C-c 8 brings up a unicode insertion dispatcher that is like the standard C-x 8 unicode insertion dispatcher but focused on a different selection of commonly used typographical characters.
(use-package typo)

23 Other

23.1 real-auto-save for auto-saving files at regular intervals

The real-auto-save package supports per-file auto-saving at regular time intervals, i.e. a buffer-local auto-save-visited-mode.

When the buffer-local minor mode real-auto-save-mode is active, the buffer is automatically saved every real-auto-save-interval seconds.

;; buffer-local `auto-save-visited-mode'
(use-package real-auto-save
  :defer t
  :config (setq real-auto-save-interval 10)) ;; save interval, in seconds

Example use case: If some directory (say where files in org-agenda-files are stored) is a cloud-enabled shared folder and external changes should be reflected with files auto-saved on changes so that the buffer, local and remote files are kept in sync, create a .dir-locals.el file in the folder with the following contents.

;; Directory-local settings
((nil . ((eval . (auto-revert-mode 1)) ;; auto-revert files
         (eval . (real-auto-save-mode 1))))) ;; buffer-local auto-save

23.2 Scratch buffer default mode set to fundamental-mode

Set the initial mode of the scratch buffer to fundamental-mode, as it can used for anything and not just Emacs Lisp interaction.

S-expressions in the buffer can still be evaluated as Emacs Lisp by using C-x C-e if desired.

;; set *scratch* buffer major-mode to fundamental-mode
(setq initial-major-mode 'fundamental-mode)

23.3 Scroll conservatively at window edge

The default behavior in Emacs when scrolling past the window edge is to recenter the viewport on the point. Modify this to scroll conservatively, moving the viewport one column or line at a time instead of recentering.

;; scroll a line at a time at window edge
(setq scroll-conservatively 101)

23.4 Scrolling one line up or down without moving cursor

Convenience bindings for scroll-up-line and scroll-down-line that scrolls the viewport a line at a time like C-e and C-y in Vim.

;; bindings for scrolling line-at-a-time like "C-e" and "C-y" in Vim
(global-set-key (kbd "C-S-e") #'scroll-up-line)
(global-set-key (kbd "C-S-y") #'scroll-down-line)

23.5 Silence audio and visual bells

Supress audio and visual bells. These are super distracting.

;; turn off audio and visual bells
(setq ring-bell-function 'ignore)

23.6 Suppress auto-revert messages

Suppress the minibuffer messages that appear whenever a file is auto-reverted.

;; suppress auto-revert minibuffer messages
(setq auto-revert-verbose nil)

23.7 Suppress startup splash screen

Suppress the startup splash screen. Don’t need it.

;; suppress splash screen that appears on startup by default
(setq inhibit-startup-message t)

23.8 Very large file support

Very Large Files provides a minor mode vlf-mode that allows loading of large file in chunks, trading increased processing time for reduced memory usage.

When vlf-mode is active, it exposes a number of commands that are prefixed by C-c C-v:

  • C-c C-v n and C-c C-v p moves forward and back through the file by chunk.
  • C-c C-v SPC shows the chunk starting from the current point.
  • C-c C-v [ and C-c C-v ] goes to the front and end of the file.
  • C-c C-v l and C-c C-v j jumps to a given line in the file and a given chunk number respectively.
  • C-c C-v s, C-c C-v r, C-c C-v o and C-c C-v % are the forward search, backward search, occur and replace equivalents that act across all the file chunks. Note the C-c C-v % auto-saves the file when each chunk is processed.
  • C-c C-v f toggles continuous chunk around the point.
  • C-c C-v + and C-c C-v - control chunk size.

The package also provides two commands vlf-ediff-files and vlf-ediff-buffers that compare files and buffers in chunks.

;; visit large files without loading it entirely
(use-package vlf
  :config (require 'vlf-setup))

23.9 Auto-disable modes that slow down Emacs in files with long lines

Many Emacs modes cause slowdown when working with buffers containing extremely long lines.

Emacs 27+ includes a package so-long that if enabled will automatically disable major and minor modes that can cause extreme slowdown when visiting files with excessively long lines.

C-c C-c will revert so-long-mode after it is activated back to the original major mode.

See M-x so-long-commentary for more information.

;; automatically disable major and minor modes that can slow down
;; Emacs when visiting files with long lines, Emacs 27+ only
(when (require 'so-long nil :noerror)
  (global-so-long-mode 1)
  ;; leave major modes alone, only disable minor modes
  ;; increase threshold before so-long action is invoked
  (setq so-long-action 'so-long-minor-mode
        so-long-max-lines 10
        so-long-threshold 500))

23.10 Uniquify file buffer names

Disambiguate open file buffers with the same name but from different directories.

(require 'uniquify)
(setq uniquify-buffer-name-style 'post-forward-angle-brackets)

23.11 Calc built-in calculator

Calc is a built-in calculator that uses Reverse Polish notation (RPN). Use C-x * to start a Calc dispatch on the expression under the point, for example:

  • C-x * c to call calc and open a Calc instance.
  • C-x * e to enter Embedded mode when the point is on a formula.
  • C-x * q to run a quick calculation.

See the help buffer surfaced by C-h f calc-dispatch-help for more information.

(setq calc-multiplication-has-precedence nil
      calc-ensure-consistent-units t
      calc-context-sensitive-enter t
      calc-undo-length 100
      calc-highlight-selections-with-faces nil)

23.12 Enable commands that are disabled by default

Some commands are disabled by default and show a warning when they are called for the first time.

Enable some of them:

  • Horizontal scrolling, C-x < and C-x >
  • Narrow to region and page, C-x n n and C-x n p
  • Downcasing and upcasing a text region, C-x C-l and C-x C-u
;; Enable some functions disabled by default.
(put 'scroll-left 'disabled nil)
(put 'scroll-right 'disabled nil)
(put 'narrow-to-region 'disabled nil)
(put 'narrow-to-page 'disabled nil)
(put 'downcase-region 'disabled nil)
(put 'upcase-region 'disabled nil)

23.13 Revert buffer using F5

Buffers can be reverted using s-u but since the Super key is not on all keyboards it is a good idea to bind it to a function key, which is <f5> here.

(global-set-key (kbd "<f5>") #'revert-buffer)

23.14 Reuse path settings of remote account when using TRAMP

By default, the paths on the remote machine used by TRAMP for executables is limited to the default remote system paths. The remote user’s paths (whatever $PATH is set to when a login shell is created, usually this is whatever the default is together with any modifications in the ~/.profile file) are not included by default.

The TRAMP remote search paths are specified in in the tramp-remote-path variable. Adding the tramp-own-remote-path symbol to this list makes TRAMP also searches the remote user’s paths.

;; add remote user paths to the TRAMP remote search paths
(with-eval-after-load 'tramp-sh
  (add-to-list 'tramp-remote-path 'tramp-own-remote-path))

23.15 Repeatable mark popping

Setting set-mark-command-repeat-pop to through allows popping of the mark multiple times by repeating C-SPC after having run C-u C-SPC (i.e. C-u C-SPC ... C-SPC) instead of having to retype C-u C-SPC (i.e. C-u C-SPC C-u C-SPC ... C-u C-SPC).

;; repeating "C-SPC" after popping mark with "C-u C-SPC" pops it again
(setq set-mark-command-repeat-pop t)

23.16 Command for clearing registers

Define a command for clearing registers.

(defun my-clear-register (register)
  "Clear the contents in register REGISTER."
  (interactive (list (register-read-with-preview "Clear register: ")))
  (set-register register nil)
  (message "Cleared register %c" register))

;; global binding for clearing a register's contents
(global-set-key (kbd "C-x r DEL") #'my-clear-register)

23.17 xr for converting Emacs regexps to rx notation

xr provides functionality for converting Emacs regexps to rx notation which is easier understand and debug.

Note that these rx forms can be used in place of regular regexps in Emacs, for example "/\\*\\(?:[^*]\\|\\*[^/]\\)*\\*+/" is the same as (rx "/*" (* (| (not "*") (: "*" (not "/")))) (+ "*") "/").

Specifically, this package provides these non-interactive functions:

  • xr which converts regexp to rx.
  • xr-pp which converts regexp to rx and pretty prints it.
  • xr-lint which finds mistakes in regexp.
  • xr-skip-set, xr-skip-set-pp and xr-skip-set-lint which are skip set versions of the above.
  • xr-pp-rx-to-str which pretty prints an rx expression to a string.
;; convert regexp to rx notation
(use-package xr)

23.18 Binding for opening menu bar

Alternate binding for opening the menu bar using the keyboard when the keyboard does not have an F10 key.

;; alternative binding for opening the menu bar
(global-set-key (kbd "C-c e m") #'menu-bar-open)

24 Transient commands

Temporary bindings that are shown in a popup window.

24.1 Transient

Transient provides functionality for defining transient keymaps.

Transient bindings and definitions in this configuration conform to the following conventions:

  • The config calls (transient-bind-q-to-quit) which binds q to quit any transient like magit. Doing this will also rotate any q, Q and M-q bindings defined in any transient to Q, M-q and unbound respectively.
  • Globally-accessible transients are always bound to a key sequence prefixed by C-c- and followed by one or more characters (for example C-c B for the basic buffer management hydra or C-c e p for the Emacs profiler). These should be chosen to not conflict with common packages with bindings that don’t conform to the convention of reserving C-c <letter> for user bindings (like xcscope.el with its C-c s bindings).
  • Transients for toggling settings should be prefixed with C-c l where l is a mnemonic for layer (for example, C-c l v for the visual layer or C-c l w for the writing layer).
  • Transients for accessing global Emacs functionality should be prefixed with C-c e where e is a mnemonic for Emacs (for example C-c e f for Emacs frames and C-c e p for Emacs profiler).
  • Major mode-specific transients are always bound to C-c m.
  • Transients are always defined in their own section after all other user configuration, such that all dependencies in transients are loaded before any are defined. This simplifies transient definitions (the alternative is to modify already defined transients), but separates package loading code from where their functions are used in transients. Accordingly, take extra care to check all downstream transient definitions are adjusted when removing packages.

References:

(use-package transient
  :init
  ;; convenience function for specifying transient toggle descriptions
  (defun transient--make-description (desc is-enabled)
    "Return a string description for transient descriptions.
The returned string has format \"DESC [ ]\" if IS-ENABLED is nil
or \"DESC [X]\" if is-enabled is non-nil.

Examples:

  (transient--make-description symbol-overlay-mode \"highlight\")
  => \"highlight [x]\"

Example of use with transient suffix definitions in a
`transient-define-prefix' macro:

  ...
  (\"m\" (lambda () (transient--describe-toggle
                       \"highlight-at-pt\"
                       symbol-overlay-mode))
   symbol-overlay-mode)
  ...

  => \"m highlight-at-pt [ ]\"
"
    (concat desc " " (if is-enabled "[X]" "[ ]")))
  :config
  ;; bind "q" to quit transient popups by default, transient defns
  ;; binding "q" will attempt to bind "Q" or "M-q" instead
  (transient-bind-q-to-quit))

24.2 Global transients

Global transients have global bindings other than C-c m (that is reserved for major mode transients) and are always active.

24.2.1 Bookmarks transient

Transient for easier bookmark manipulation and usage.

;; add transient popup for bookmark commands
(transient-define-prefix transient/bookmarks ()
  "Various bookmark commands."
  ["Bookmarks"
   ["Navigate"
    ("j" "Jump" bookmark-jump)
    ("l" "List" list-bookmarks)
    ]
   ["Add/Remove"
    ("s" "Set" bookmark-set)
    ("d" "Delete" bookmark-delete)
    ("i" "Insert" bookmark-insert)
    ]
   ["File"
    ("L" "Load" bookmark-load)
    ("W" "Write" bookmark-write)
    ]
   ]
  )
(global-set-key (kbd "C-c e B") #'transient/bookmarks)

24.2.2 Buffer transient

Transient for buffer management.

(require 'buffer-expose)

(defun transient/buffer--tramp-cleanup-buffers ()
  "Clean up all TRAMP buffers and connections with confirm prompt."
  (interactive)
  (when (y-or-n-p "Cleanup all TRAMP buffers and connections? ")
    (tramp-cleanup-all-buffers)))

(defun transient/buffer--kill-other-buffers ()
  "Kill all file buffers except the current one."
  (interactive)
  (when (y-or-n-p "Kill all file buffers except the current one? ")
    (seq-each
     #'kill-buffer
     (delete (current-buffer)
             (seq-filter #'buffer-file-name (buffer-list))))))

(defun transient/buffer--indent-region-or-buffer ()
  "Indent a selected region, or the buffer otherwise."
  (interactive)
  (cond
   ((use-region-p) (indent-region (region-beginning) (region-end)))
   (t (indent-region (point-min) (point-max)))))

(defun transient/buffer--untabify-region-or-buffer ()
  "Convert tabs to spaces in a selected region, or the buffer otherwise."
  (interactive)
  (cond
   ((use-region-p) (untabify (region-beginning) (region-end)))
   (t (untabify (point-min) (point-max)))))

(defun transient/buffer--apply-all-hygiene-ops-region-or-buffer ()
  "Apply standard hygiene operations for selected region, or buffer otherwise.
The standard hygiene operations include removing trailing
whitespace, indenting and untabifying."
  (interactive)
  (progn
    (whitespace-cleanup)
    (transient/buffer--indent-region-or-buffer)
    (transient/buffer--untabify-region-or-buffer)))

(defun transient/buffer--open-containing-dir-externally (&optional path)
  "Opens the directory containing PATH or the buffer if unspecified externally."
  (interactive)
  (let* ((my-path (cl-some 'identity (list path
                                           (buffer-file-name)
                                           default-directory)))
         (my-full-path (expand-file-name my-path))
         (my-dir-path (file-name-directory my-full-path))
         (my-process-args (list "my-open-dir" nil
                                my-system-open-command my-dir-path)))
    (apply 'start-process my-process-args)))

(defun transient/buffer--print-minor-modes ()
  "Print enabled minor modes for the current buffer."
  (interactive)
  (let* ((maybe-active-minor-modes (mapcar #'car minor-mode-alist))
         (active-minor-modes (seq-filter (lambda (mode)
                                           (and (boundp mode)
                                                (symbol-value mode)))
                                         maybe-active-minor-modes))
         ;; sort alphabetically
         (active-minor-modes-sorted (sort (mapcar #'symbol-name
                                                  active-minor-modes)
                                          'string<))
         ;; five minor modes to a line
         (active-minor-mode-tuples (seq-partition active-minor-modes-sorted 5))
         (active-minor-mode-tuples-strs (mapcar
                                         (lambda (tup)
                                           (mapconcat #'identity tup " "))
                                         active-minor-mode-tuples))
         (msg-str (mapconcat 'identity active-minor-mode-tuples-strs "\n")))
    (message msg-str)))

(defun transient/buffer--clone-indirect-buffer-other-window ()
  "Create an indirect buffer of the current one in another window.
This wraps `clone-indirect-buffer-other-window' but provides a
name for the cloned indirect buffer ending with \"-INDIRECT\"."
  (interactive)
  (let ((bufname (generate-new-buffer-name
                  (concat (buffer-name)
                          "-INDIRECT"))))
    (clone-indirect-buffer-other-window bufname t)))

;; add transient for buffer management commands
(transient-define-prefix transient/buffer ()
  "Buffer management commands."
  ["Buffer"
   ["Select"
    ("b" "Switch" switch-to-buffer)
    ("n" "Next" next-buffer :transient t)
    ("p" "Previous" previous-buffer :transient t)
    ("z" "Open external" transient/buffer--open-containing-dir-externally)
    ]
   ["Hygiene"
    ("cr" "Whitespace report" whitespace-report)
    ("cw" "Whitespace cleanup" whitespace-cleanup)
    ("ci" "Indent" transient/buffer--indent-region-or-buffer)
    ("ct" "Untabify" transient/buffer--untabify-region-or-buffer)
    ("ca" "All hygiene ops" transient/buffer--apply-all-hygiene-ops-region-or-buffer)
    ]
   ["File operations"
    ("r" "Revert" revert-buffer)
    ("B" "Bury" bury-buffer)
    ("U" "Unbury" unbury-buffer)
    ("s" "Save" save-buffer)
    ("S" "Save all" save-some-buffers)
    ("k" "Kill" kill-this-buffer)
    ("K" "Kill matching" kill-matching-buffers)
    ("o" "Kill others" transient/buffer--kill-other-buffers)
    ("T" "TRAMP cleanup" transient/buffer--tramp-cleanup-buffers)
    ("I" "Make indirect" transient/buffer--clone-indirect-buffer-other-window)
    ]
   ["Expose"
    ("ee" "All" buffer-expose)
    ("em" "Current mode" buffer-expose-current-mode)
    ("eM" "Major mode" buffer-expose-major-mode)
    ("ed" "Dired" buffer-expose-dired-buffers)
    ("e!" "Non-special" buffer-expose-no-stars)
    ("e*" "Special" buffer-expose-stars)
    ]
   ]
  [
   ["Other"
    ("M" "Minor modes" transient/buffer--print-minor-modes)
    ("R" (lambda ()
           (transient--make-description
            "Autorevert"
            (and (boundp 'auto-revert-mode) auto-revert-mode)))
     auto-revert-mode :transient t)
    ("M-r" (lambda ()
             (transient--make-description
              "Revert without query"
              revert-without-query-mode))
     revert-without-query-mode :transient t)
    ]
   ]
  )
(global-set-key (kbd "C-c e b") #'transient/buffer)

24.2.3 Debugger transient

Transient for modifying Emacs Lisp debugger settings.

(require 'debug)

(defun transient/debugger--list-variables ()
  "Print variables configured to invoke the debugger to the minibuffer."
  (interactive)
  (prin1 (debug--variable-list)))

;; add transient popup for debugger commands
(transient-define-prefix transient/debugger ()
  "Emacs debugger settings."
  ["Emacs debugger settings"
   ["Toggle"
    ("1" (lambda ()
           (transient--make-description "Debug on error" debug-on-error))
     toggle-debug-on-error :transient t)
    ("2" (lambda ()
           (transient--make-description "Debug on quit" debug-on-quit))
     toggle-debug-on-quit :transient t)
    ]
   ["Invoke on function entry"
    ("fl" "List" debugger-list-functions)
    ("fa" "Add" debug-on-entry)
    ("fc" "Cancel" cancel-debug-on-entry)
    ]
   ["Invoke on variable change"
    ("vl" "List" transient/debugger--list-variables)
    ("va" "Add" debug-on-variable-change)
    ("vc" "Cancel" cancel-debug-on-variable-change)
    ]
   ]
  )
(global-set-key (kbd "C-c e d") #'transient/debugger)

24.2.4 Ediff transient

Transient popup for launching Ediff commands.

;; add transient popup for Ediff commands
(transient-define-prefix transient/ediff ()
  "Various Ediff launch commands."
  ["Ediff"
   ["2 Way"
    ("b" "Buffers" ediff-buffers)
    ("f" "Files" ediff-files)
    ("d" "Directories" ediff-directories)
    ("c" "Buffer vs File" ediff-current-file)
    ("~" "File vs Backup" ediff-backup)
    ]
   ["3 Way"
    ("3b" "Buffers" ediff-buffers3)
    ("3f" "Files" ediff-files3)
    ("3d" "Directories" ediff-directories3)
    ]
   ["Patches"
    ("pb" "Buffer" ediff-patch-buffer)
    ("pf" "File" ediff-patch-file)
    ]
   ["Regions"
    ("rl" "Linewise" ediff-regions-linewise)
    ("rw" "Wordwise" ediff-regions-wordwise)
    ]
   ["Windows"
    ("wl" "Linewise" ediff-windows-linewise)
    ("ww" "Wordwise" ediff-windows-wordwise)
    ]
   ]
  )
(global-set-key (kbd "C-c d e") #'transient/ediff)

24.2.5 Editing transient

Transient for editing commands, mostly useful for using Emacs on the TTY where some key combinations like C-; don’t get interpreted by the terminal.

;; add transient popup for various editing commands
(transient-define-prefix transient/edit ()
  "Editing commands."
  ["Edit"
   ["Completion"
    ("/" "Dyn. abbrev" dabbrev-expand :transient t) ; built-in
    ("TAB" "Company" company-complete) ; autoloaded from company-mode.el
    ]
   ["Line"
    ("O" "New line above" my-open-line-above :transient t)
    ("o" "New line below" my-open-line-below :transient t)
    ("J" "Join lines" my-join-next-line :transient t)
    ]
   ["Multi-cursor" ; functions autoloaded from multiple-cursors.el
    ("C" "Edit lines" mc/edit-lines)
    ("V" "Rect select" set-rectangular-region-anchor)
    ("<" "Previous" mc/mark-previous-like-this :transient t)
    (">" "Next" mc/mark-next-like-this :transient t)
    ]
   ["Other"
    (";" "Iedit" iedit-mode) ; autoloaded from iedit.el
    ]
   ]
  )
(global-set-key (kbd "C-c e e") #'transient/edit)

24.2.6 Frame transient

Transient for frame management.

(defun transient/frame--previous-frame ()
  "Select previous frame."
  (interactive)
  (other-frame -1))

;; add transient popup for frame commands
(transient-define-prefix transient/frame ()
  "Frame management commands."
  ["Frame"
   ["Select"
    ("n" "Next" other-frame)
    ("p" "Previous" transient/frame--previous-frame)
    ("s" "By name" select-frame-by-name)
    ]
   ["Layout"
    ("0" "Delete frame" delete-frame)
    ("1" "Delete other frames" delete-other-frames)
    ("2" "Create new frame" make-frame-command)
    ]
   ["Resize"
    ("M" "Toggle maximized" toggle-frame-maximized :transient t)
    ("f" "Toggle fullscreen" toggle-frame-fullscreen :transient t)
    ]
   ]
  )
(global-set-key (kbd "C-c e f") #'transient/frame)

24.2.7 Help transient

Transient for accessing Emacs help.

;; add transient popup for help commands
(transient-define-prefix transient/help ()
  "Various help commands."
  ["Help"
   ["Apropos"
    ("aa" "Symbol" apropos)
    ("ac" "Command" apropos-command)
    ("ad" "Documentation" apropos-documentation)
    ("al" "Library" apropos-library)
    ("av" "Variable" apropos-variable)
    ("aV" "Value" apropos-value)
    ]
   ["Describe"
    ("db" "Bindings" describe-bindings)
    ("dc" "Char" describe-char)
    ("df" "Function" describe-function)
    ("dF" "Face" describe-face)
    ("dk" "Key" describe-key)
    ("dm" "Mode" describe-mode)
    ("dp" "Package" describe-package)
    ("ds" "Syntax" describe-syntax)
    ("dv" "Variable" describe-variable)
    ]
   ["Info"
    ("ia" "Apropos" info-apropos)
    ("ib" "Browse" info)
    ("if" "File" info-lookup-file)
    ("ik" "Keywords" info-finder)
    ("is" "Symbol" info-lookup-symbol)
    ]
   ["Other"
    ("lf" "List faces" list-faces-display)
    ("ve" "View messages" view-echo-area-messages)
    ("vl" "View lossage" view-lossage)
    ("w" "Where is" where-is)
    ]
   ]
  )
(global-set-key (kbd "C-c H h") #'transient/help)

24.2.8 Keyboard macros transient

Transient for manipulating and using Emacs keyboard macros. For an example, see the writeup on EmacsWiki.

;; add transient for keyboard macros
(transient-define-prefix transient/keyboard-macros ()
  "Keyboard macro commands. Tries to adhere to \"C-x C-k\" bindings."
  ["Keyboard Macros"
   ["Actions"
    ("C-s" "Start" kmacro-start-macro)           ; also "C-x ("
    ("C-k" "Call last" kmacro-end-or-call-macro) ; also "C-x )"
    ("C-r" "Call last on region" apply-macro-to-region-lines)
    ]
   ["Ring"
    ("C-n" "Cycle next" kmacro-cycle-ring-next :transient t)
    ("C-p" "Cycle prev" kmacro-cycle-ring-previous :transient t)
    ("C-v" "View last" kmacro-view-macro :transient t)
    ("C-d" "Delete head" kmacro-delete-ring-head :transient t)
    ]
   ["Edit"
    ("e" "Named" edit-kbd-macro)
    ("RET" "Last" kmacro-edit-macro)
    ("l" "Lossage" kmacro-edit-lossage)
    ("SPC" "Step" kmacro-step-edit-macro)
    ]
   ]
  [
   ["Bind/Name"
    ("b" "Bind to key" kmacro-bind-to-key)
    ("n" "Name last" kmacro-name-last-macro)
    ("x" "To register" kmacro-to-register)
    ]
   ["Other"
    ("i" "Insert named" insert-kbd-macro)
    ]
   ]
  )
(global-set-key (kbd "C-c k") #'transient/keyboard-macros)

24.2.9 Marks and markers transient

Transient for manipulating and managing marks and markers.

(defun transient/marks-and-markers--xref-pop-marker-stack-all ()
  "Pop back to where `xref-find-definitions' was first invoked.
\\[xref-find-definitions] is the current binding for `xref-find-definitions'."
  (interactive)
  (let ((ring xref--marker-ring))
    (when (ring-empty-p ring)
      (user-error "Marker stack is empty"))
    (let ((marker (ring-remove ring nil))) ;; oldest marker
      (switch-to-buffer (or (marker-buffer marker)
                            (user-error "The marked buffer has been deleted")))
      (goto-char (marker-position marker))
      (set-marker marker nil nil)
      (run-hooks 'xref-after-return-hook)
      (xref-clear-marker-stack)))) ;; clear the rest of the marker stack

(defun transient/marks-and-markers--push-mark ()
  "Push location of point into the mark ring."
  (interactive)
  (push-mark))
(defun transient/marks-and-markers--pop-mark ()
  "Pop the top location the mark ring and jump to it."
  (interactive)
  (set-mark-command t))
(defun transient/marks-and-markers--push-marker ()
  "Push location of point onto the marker stack."
  (interactive)
  (xref-push-marker-stack))
(defun transient/marks-and-markers--clear-marker-stack ()
  "Clear the marker stack."
  (interactive)
  (xref-clear-marker-stack)
  (message "Cleared `xref--marker-ring'"))

(transient-define-prefix transient/marks-and-markers ()
  "Commands for manipulating and managing marks and markers."
  ["Marks/Markers"
   ["Mark"
    ("SPC" "Push" transient/marks-and-markers--push-mark)
    ("RET" "Pop" transient/marks-and-markers--pop-mark :transient t)
    ("DEL" "Pop global" pop-global-mark :transient t)
    ]
   ["Marker"
    ("." "Push" transient/marks-and-markers--push-marker)
    ("," "Pop" xref-pop-marker-stack :transient t)
    ("<" "Pop all" transient/marks-and-markers--xref-pop-marker-stack-all)
    ("c" "Clear stack" transient/marks-and-markers--clear-marker-stack)
    ]
   ]
  )
(global-set-key (kbd "C-c M") #'transient/marks-and-markers)

24.2.10 Org launcher transient

Transient for accessing Org entry points.

;; add transient for accessing Org entry points
(with-eval-after-load 'org
  ;; file launchers
  (defun transient/org-launcher--find-my-org-agenda-inbox ()
    "Open a file buffer for `my-org-agenda-inbox'."
    (interactive)
    (find-file my-org-agenda-inbox))
  (defun transient/org-launcher--find-my-org-someday-inbox ()
    "Open a file buffer for `my-org-someday-inbox'."
    (interactive)
    (find-file my-org-someday-inbox))
  (defun transient/org-launcher--find-my-org-journal-file ()
    "Open a file buffer for `my-org-journal-file'."
    (interactive)
    (find-file my-org-journal-file))
  (defun transient/org-launcher--find-org-websnippet-capture-file ()
    "Open a file buffer for `org-websnippet-capture-file'."
    (interactive)
    (find-file org-websnippet-capture-file))
  (defun transient/org-launcher--find-my-org-scratch-file ()
    "Open a file buffer for `my-org-scratch-file'."
    (interactive)
    (find-file my-org-scratch-file))

  (transient-define-prefix transient/org-launcher ()
    "Launcher for Org entry points."
    ["Org launcher"
     ["Main"
      ("a" "Agenda" org-agenda)
      ("c" "Capture" org-capture)
      ("b" "Switchb" org-switchb)
      ("l" "Store link" org-store-link)
      ]
     ["Find-file"
      ("fi" "Inbox" transient/org-launcher--find-my-org-agenda-inbox)
      ("fs" "Someday" transient/org-launcher--find-my-org-someday-inbox)
      ("fj" "Journal" transient/org-launcher--find-my-org-journal-file)
      ("fw" "Websnippets" transient/org-launcher--find-org-websnippet-capture-file)
      ("fx" "Scratch" transient/org-launcher--find-my-org-scratch-file)
      ]
     ]
    )
  (global-set-key (kbd "C-c o") #'transient/org-launcher))

24.2.11 Package management transient

Transient for accessing Emacs package management.

;; add transient popup for Emacs package management
(transient-define-prefix transient/package ()
  "Emacs package management commands."
  ["Package management"
   ("l" "List packages" list-packages)
   ("i" "Install package" package-install)
   ("k" "Delete package" package-delete)
   ("r" "Reinstall package" my-package-reinstall)
   ("R" "Use-package report" use-package-report) ; requires use-package-compute-statistics set to non-nil before use-package declarations
   ]
  )
(global-set-key (kbd "C-c e P") #'transient/package)

24.2.12 Profiler transient

Transient for built in Emacs profiler.

;; add transient for Emacs profiler
(transient-define-prefix transient/profiler ()
  "Emacs profiler commands."
  [:description (lambda ()
                  (concat "Profiler | "
                          (transient--make-description
                           "CPU"
                           (profiler-running-p))
                          " "
                          (transient--make-description
                           "Memory"
                           (profiler-memory-running-p))))
   ("s" "Start/Reset" profiler-start :transient t)
   ("p" "Report" profiler-report)
   ("e" "Stop" profiler-stop :transient t)
   ]
  )
(global-set-key (kbd "C-c e p") #'transient/profiler)

24.2.13 Registers transient

Transient for easier access to register commands.

;; add transient popup for register commands
(transient-define-prefix transient/registers ()
  "Register commands."
  ["Registers"
   [("SPC" "Save point" point-to-register)
    ("w" "Save windows" window-configuration-to-register)
    ("f" "Save frames" frameset-to-register)
    ("j" "Jump" jump-to-register)
    ]
   [("s" "Copy region" copy-to-register)
    ("a" "Append region" append-to-register)
    ("p" "Prepend region" prepend-to-register)
    ("r" "Copy rectangle" copy-rectangle-to-register)
    ]
   [("DEL" "Clear" my-clear-register)
    ("i" "Insert" insert-register)
    ("l" "List" list-registers)
    ("v" "View" view-register)
    ]
   ]
  )
(global-set-key (kbd "C-c R") #'transient/registers)

24.2.14 Search transient

Transient for accessing Emacs search functionality.

;; add transient popup for search tools
(transient-define-prefix transient/search ()
  "Search commands."
  ["Search"
   ["Grep"
    ("gr" "Recursive" rgrep)
    ("gz" "Recursive (*.gz)" rzgrep)
    ("gg" "With user args" grep)
    ("gf" "Via find" grep-find)
    ]
   ["Occur in buffers"
    ("oo" "Current" occur)
    ("ob" "Matching" multi-occur-in-matching-buffers)
    ("om" "All" multi-occur)
    ]
   ["Query/Replace"
    ("rs" "String" query-replace)
    ("rr" "Regexp" query-replace-regexp)
    ]
   ["Other"
    ("." "Find definition" xref-find-definitions)
    ("w" "EWW web search" eww)
    ]
   ]
  )
(global-set-key (kbd "C-c S") #'transient/search)

24.2.15 Server transient

Transient for Emacs server management.

;; add transient popup for Emacs server management
(transient-define-prefix transient/server ()
  "Emacs server-related commands."
  ;; suffix actions don't exit the transient popup by default
  :transient-suffix 'transient--do-stay
  [:description (lambda ()
                  (transient--make-description
                   "Emacs server-mode"
                   server-mode))
   ("s" "Toggle server-mode" server-mode)
   ("r" "Restart server" restart-emacs-server)
   ("e" "Next server editing buffer" server-edit)
   ("k" "Stop server and delete connection file" server-force-delete)
   ]
  )
(global-set-key (kbd "C-c e s") #'transient/server)

24.2.16 Shell entry transient

Transient providing entry points into Eshell and Ansi-Term.

;; add transient popup for shell tools
(transient-define-prefix transient/shell ()
  "Various shell tools."
  ["Shell tools"
   ["Shell"
    ("e" "Eshell" my-eshell-with-name)
    ("a" "ANSI Term" ansi-term)
    ]
   ["Tmux"
    ("ts" "Send" tmux-send)
    ("tr" "Resend" tmux-resend)
    ]
   ]
  )
(global-set-key (kbd "C-c T") #'transient/shell)

24.2.17 System information, process and Emacs runtime/server transient

Transient for accessing system, process and Emacs runtime commands. The Emacs runtime commands also include those for toggling and restarting Emacs as a server.

(defun transient/system--display-current-datetime ()
  "Display the current time in the minibuffer."
  (interactive)
  (message (format-time-string "%Y-%b-%d %l:%M:%S%p %Z %A")))

(defun transient/system--display-emacs-pid ()
  "Display the process id of current Emacs process in the minibuffer."
  (interactive)
  (message (format "%d" (emacs-pid))))

(defun transient/system--display-emacs-build-config ()
  "Display the Emacs build configuration in the minibuffer."
  (interactive)
  (message (mapconcat 'identity
                      `("Emacs build configuration"
                        ,(format "  Build target:     %s"
                                 system-configuration)
                        ,(format "  Enabled features: %s"
                                 system-configuration-features))
                      "\n")))

;; add transient popup for system process management and info, and
;; Emacs build and runtime info
(transient-define-prefix transient/system ()
  "System process managment, general info and Emacs runtime commands."
  ["System, process and Emacs runtime"
   ["Emacs"
    ("eb" "Build config" transient/system--display-emacs-build-config)
    ("ei" "Init time" emacs-init-time)
    ("ep" "Emacs PID" transient/system--display-emacs-pid)
    ("eu" "Uptime" emacs-uptime)
    ("ev" "Version" emacs-version)
    ]
   ["System"
    ("sp" "Proced" proced)
    ("st" "Datetime" transient/system--display-current-datetime :transient t)
    ("sw" "World time" display-time-world)
    ]
   ]
  )
(global-set-key (kbd "C-c e S") #'transient/system)

24.2.18 Visual transient

Transient for visual commands and toggles.

;; make sure functions used by visual transient are loaded
(require 'follow)
(require 'hilit-chg)
(require 'hl-line)
(require 'display-line-numbers)
(require 'face-remap)
(require 'whitespace)

(require 'censor)
(require 'too-long-lines-mode)

(defvar-local transient/visual--face-remap-cookies '()
  "Alist storing cookies for `face-remap-add-relative' calls.")

(defun transient/visual--toggle-lighten-face (face)
  "Toggle brightness of FACE color for emphasis or emphasis."
  (let ((face-remap-cookie-old (alist-get
                                face
                                transient/visual--face-remap-cookies)))
    (if face-remap-cookie-old
        (progn
          (face-remap-remove-relative face-remap-cookie-old)
          (setq transient/visual--face-remap-cookies
                (assq-delete-all
                 face
                 transient/visual--face-remap-cookies)))
      (let* ((light-color (color-lighten-name
                           (face-attribute face :foreground)
                           50)) ;; lighten color by 50 percent
             (face-remap-cookie-new (face-remap-add-relative
                                     face
                                     :foreground light-color)))
        (push `(,face . ,face-remap-cookie-new)
              transient/visual--face-remap-cookies)))))

(defun transient/visual--toggle-lighten-font-lock-comment-face ()
  "Toggle brightness of `font-lock-comment-face'."
  (interactive)
  (transient/visual--toggle-lighten-face
   'font-lock-comment-face))

(defun transient/visual--toggle-lighten-font-lock-comment-delimiter-face ()
  "Toggle brightness of `font-lock-comment-delimiter-face'."
  (interactive)
  (transient/visual--toggle-lighten-face
   'font-lock-comment-delimiter-face))

(defun transient/visual--toggle-lighten-font-lock-doc-face ()
  "Toggle brightness of `font-lock-doc-face'."
  (interactive)
  (transient/visual--toggle-lighten-face
   'font-lock-doc-face))

(defun transient/visual--display-toggle-trailing-whitespace ()
  "Toggle the display of trailing whitespace."
  (interactive)
  ;; `show-trailing-whitespace' is buffer-local by default
  (setq show-trailing-whitespace (not show-trailing-whitespace))
  (message "show-trailing-whitespace: %s"
           (if show-trailing-whitespace "yes" "no")))

(defun transient/visual--toggle-ligatures ()
  "Toggle ligatures.
Currently only works for Emacs Mac port."
  (interactive)
  (cond ((fboundp 'mac-auto-operator-composition-mode)
         (mac-auto-operator-composition-mode))
        (t (message "Not implemented for this Emacs build."))))

(defun transient/visual--text-scale-reset ()
  "Resets buffer `text-scale-mode-amount' to zero."
  (interactive)
  (text-scale-set 0))

;; add transient popup for visual commands
(transient-define-prefix transient/visual ()
  "Visual commands and toggles."
  :transient-suffix 'transient--do-stay
  ["Visual"
   ["Display"
    ("H" (lambda ()
           (transient--make-description
            "Highlight changes"
            highlight-changes-mode))
     highlight-changes-mode)
    ("l" (lambda ()
           (transient--make-description
            "Line numbers"
            display-line-numbers-mode))
     display-line-numbers-mode)
    ("t" (lambda ()
           (transient--make-description
            "Truncate lines"
            truncate-lines))
     toggle-truncate-lines)
    ("w" (lambda ()
           (transient--make-description
            "Trailing whitespace"
            show-trailing-whitespace))
     transient/visual--display-toggle-trailing-whitespace)
    ("W" (lambda ()
           (transient--make-description
            "Whitespace"
            whitespace-mode))
     whitespace-mode)
    ("L" (lambda ()
           (transient--make-description
            "Hide long lines"
            too-long-lines-mode))
     too-long-lines-mode)
    ("$" (lambda ()
           (concat "Selective display ["
                   (if selective-display
                       (number-to-string selective-display)
                     " ")
                   "]"))
     set-selective-display)
    ("P" (lambda ()
           (transient--make-description
            "Prettify symbols"
            prettify-symbols-mode))
     prettify-symbols-mode)
    ("B" (lambda ()
           (transient--make-description
            "Buffer-specific face"
            buffer-face-mode))
     buffer-face-mode)
    ("C-l" "Ligatures" transient/visual--toggle-ligatures)
    ]
   ["Cursor"
    ("b" (lambda ()
           (transient--make-description
            "Blink"
            blink-cursor-mode))
     blink-cursor-mode)
    ("h" (lambda ()
           (transient--make-description
            "Highlight line"
            hl-line-mode))
     hl-line-mode)
    ("p" (lambda ()
           (transient--make-description
            "Show paren"
            show-paren-mode))
     show-paren-mode)
    ("T" (lambda ()
           (transient--make-description
            "Transient mark"
            transient-mark-mode))
     transient-mark-mode)
    ]
   ["Color"
    ("cf" (lambda ()
            (transient--make-description
             "Font locking"
             font-lock-mode))
     font-lock-mode)
    ("cc" (lambda ()
            (transient--make-description
             "Comments"
             (null (assq 'font-lock-comment-face
                         transient/visual--face-remap-cookies))))
     transient/visual--toggle-lighten-font-lock-comment-face)
    ("cC" (lambda ()
            (transient--make-description
             "Comment delims"
             (null (assq 'font-lock-comment-delimiter-face
                         transient/visual--face-remap-cookies))))
     transient/visual--toggle-lighten-font-lock-comment-delimiter-face)
    ("cd" (lambda ()
            (transient--make-description
             "Docstrings"
             (null (assq 'font-lock-doc-face
                         transient/visual--face-remap-cookies))))
     transient/visual--toggle-lighten-font-lock-doc-face)
    ]
   ]
  [
   [:description (lambda ()
                   (transient--make-description
                    "Narrow"
                    (buffer-narrowed-p)))
    ("nd" "Defun" narrow-to-defun)
    ("nr" "Region" narrow-to-region)
    ("np" "Page" narrow-to-page)
    ("nw" "Widen" widen)
    ]
   [:description (lambda ()
                   (concat "Zoom ["
                           (if text-scale-mode
                               (number-to-string text-scale-mode-amount)
                             " ")
                           "]"))
    ("+" "Increase" text-scale-increase)
    ("-" "Decrease" text-scale-decrease)
    ("0" "Reset" transient/visual--text-scale-reset)
    ]
   ["Other"
    ("m" (lambda ()
           (transient--make-description
            "Menu bar"
            menu-bar-mode))
     menu-bar-mode)
    ("C" (lambda ()
           (transient--make-description
            "Scroll lock"
            (and (boundp 'scroll-lock-mode)
                 scroll-lock-mode)))
     scroll-lock-mode)
    ("v" (lambda ()
           (transient--make-description
            "Visual line"
            visual-line-mode))
     visual-line-mode)
    ("F" (lambda ()
           (transient--make-description
            "Follow"
            follow-mode))
     follow-mode)
    ("x" (lambda ()
           (transient--make-description
            "Censor"
            censor-mode))
     censor-mode)
    ("X" (lambda ()
           (transient--make-description
            "Censor (global)"
            global-censor-mode))
     global-censor-mode)
    ]
   ]
  )
(global-set-key (kbd "C-c l v") #'transient/visual)

24.2.19 Window transient

Transient for window management.

;; `next-multiframe-window' & `previous-multiframe-window' renamed to
;; `next-window-any-frame' & `previous-window-any-frame' in Emacs 27
(when (version< emacs-version "27")
  (defalias 'next-window-any-frame 'next-multiframe-window)
  (defalias 'previous-window-any-frame 'previous-multiframe-window))

(defun transient/window--transpose-windows (selector)
  "Call SELECTOR and transpose buffers between current and selected windows."
  (let ((from-win (selected-window))
        (from-buf (window-buffer)))
    (funcall selector)
    (set-window-buffer from-win (window-buffer))
    (set-window-buffer (selected-window) from-buf)))

(defun transient/window--transpose-window-up ()
  "Transpose buffers between current and the window above it."
  (interactive)
  (transient/window--transpose-windows 'windmove-up))
(defun transient/window--transpose-window-down ()
  "Transpose buffers between current and the window below it."
  (interactive)
  (transient/window--transpose-windows 'windmove-down))
(defun transient/window--transpose-window-left ()
  "Transpose buffers between current and the window to its left."
  (interactive)
  (transient/window--transpose-windows 'windmove-left))
(defun transient/window--transpose-window-right ()
  "Transpose buffers between current and the window to its right."
  (interactive)
  (transient/window--transpose-windows 'windmove-right))

;; add transient popup for window commands
(transient-define-prefix transient/window ()
  "Window management commands."
  :transient-suffix 'transient--do-stay
  ["Window"
   ["Navigate"
    ("n" "Next" next-window-any-frame)
    ("p" "Previous" previous-window-any-frame)
    ("o" "Other" other-window)
    ("<up>" "" windmove-up)
    ("<down>" "" windmove-down)
    ("<left>" "" windmove-left)
    ("<right>" "" windmove-right)
    ]
   ["Transpose"
    ("S-<up>" "" transient/window--transpose-window-up)
    ("S-<down>" "" transient/window--transpose-window-down)
    ("S-<left>" "" transient/window--transpose-window-left)
    ("S-<right>" "" transient/window--transpose-window-right)
    ("[" "Rotate bwd" my-rotate-buffers-backward)
    ("]" "Rotate fwd" my-rotate-buffers-forward)
    ]
   ["Layout"
    ("0" "Delete window" delete-window)
    ("1" "Delete other windows" delete-other-windows)
    ("2" "Split horiz" split-window-right)
    ("3" "Split vert" split-window-below)
    ("4" "Kill buffer and window" kill-buffer-and-window)
    ("u" "Winner undo" winner-undo)
    ("r" "Winner redo" winner-redo)
    ]
   ["Resize"
    ("-" "Shrink vert" shrink-window)
    ("^" "Enlarge vert" enlarge-window)
    ("{" "Shrink horiz" shrink-window-horizontally)
    ("}" "Enlarge horiz" enlarge-window-horizontally)
    ("M" "Maximize" maximize-window)
    ("m" "Minimize" minimize-window)
    ("+" "Balance" balance-windows)
    ]
   ]
  )
(global-set-key (kbd "C-c e w") #'transient/window)

24.2.20 Workspace transient

Transient for workspace manipulation and usage.

;; add transient popup for workspace commands
(transient-define-prefix transient/workspace ()
  "Various workspace commands."
  ["Workspace"
   ["Desktop"
    ("dc" "Clear" desktop-clear)
    ("ds" "Save" desktop-save)
    ("dr" "Read" desktop-read)
    ("dR" "Revert" desktop-revert)
    ("dd" "Change Dir" desktop-change-dir)
    ]
   ["Tabs"
    ("<backtab>" "Previous" tab-bar-switch-to-prev-tab :transient t)
    ("<tab>" "Next" tab-bar-switch-to-next-tab :transient t)
    ("1" "Only" tab-bar-close-other-tabs :transient t)
    ("2" "New" tab-bar-new-tab :transient t)
    ("0" "Close" tab-bar-close-tab :transient t)
    ("u" "Undo close" tab-bar-undo-close-tab :transient t)
    ("r" "Rename" tab-bar-rename-tab :transient t)
    ("m" "Move" tab-bar-move-tab :transient t)
    ("M" "Move (frame)" tab-bar-move-tab-to-frame :transient t)
    ("<return>" "Switch" tab-bar-switch-to-tab)
    ]
   ]
  )
(global-set-key (kbd "C-c e W") #'transient/workspace)

24.2.21 Writing transient

Transient for accessing writing-related commands.

;; add transient popup for writing commands
(require 'dictionary)
(require 'typo)

(defun transient/writing--ispell-dwim ()
  "Dispatch to different Ispell spelling correction commands by major mode.

If the major mode derives from `prog-mode', call interactively
`ispell-comments-and-strings'.

If the major mode derives from `message-mode', call interactively
`ispell-message'.

Otherwise call interactively `ispell'.

Note that `ispell-comments-and-strings' and `ispell-message' do
not support restricting to a region."
  (interactive)
  (let ((fun (cond
              ((derived-mode-p 'prog-mode) #'ispell-comments-and-strings)
              ((derived-mode-p 'message-mode) #'ispell-message)
              (t #'ispell))))
    (call-interactively fun)))

(transient-define-prefix transient/writing ()
  "Writing commands."
  ["Writing"
   ["Spelling"
    ("F" (lambda ()
           (interactive)
           (transient--make-description "Flyspell mode"
                                        flyspell-mode))
     flyspell-mode :transient t)
    ("P" "Flyspell prog mode" flyspell-prog-mode :transient t)
    ("B" "Check buffer" flyspell-buffer :transient t)
    ("n" "Next error" flyspell-goto-next-error :transient t)
    ("c" "Correct word" ispell-word :transient t)
    ("C" "Correct buffer/region" transient/writing--ispell-dwim :transient t)
    ("D" "Change dictionary" ispell-change-dictionary :transient t)
    ]
   ["Dictionary"
    ("ds" "Search" dictionary-search)
    ("dm" "Match words" dictionary-match-words)
    ]
   ["Typography"
    ("y" (lambda ()
           (interactive)
           (transient--make-description "Typography mode"
                                        typo-mode))
     typo-mode :transient t)]
   ]
  )

(global-set-key (kbd "C-c l w") #'transient/writing)

24.3 Major mode transients

Major mode transients are always bound to C-c m and that binding is active only when their corresponding major mode is active.

24.3.1 debugger-mode transient

Debugger major mode transient.

;; major-mode specific transient for debugger-mode
(with-eval-after-load 'debug
  (transient-define-prefix transient/debugger-mode ()
    "`debugger-mode' commands."
    ["Emacs debugger"
     ["Movement"
      ("n" "Next line" next-line :transient t)
      ("p" "Previous line" previous-line :transient t)
      ]
     ["Breakpoints"
      ("b" "Set" debugger-frame)
      ("u" "Unset" debugger-frame-clear)
      ]
     ["Evaluate"
      ("e" "Sexp" debugger-eval-expression)
      ("R" "Sexp and record" debugger-record-expression)
      ]
     ]
    [
     [
      "Stepping"
      ("d" "Step through" debugger-step-through)
      ("c" "Continue" debugger-continue)
      ("j" "Jump" debugger-jump)
      ("q" "Exit" top-level)
      ]
     ["Other"
      ("RET" "Follow at point" backtrace-help-follow-symbol)
      ("r" "Specify return value" debugger-return-value)
      ("l" "List debug functions" debugger-list-functions)
      ("v" "Toggle locals" backtrace-toggle-locals)
      ("h" "Help" describe-mode)
      ]
     ]
    )
  (define-key debugger-mode-map (kbd "C-c m") #'transient/debugger-mode))

24.3.2 dired-mode transient

Dired major mode transient.

;; major-mode specific transient for dired-mode
(with-eval-after-load 'dired
  (defun transient/dired-mode--dired-kill-and-next-subdir ()
    "Kill current subdir in dired, and jump back to its parent dir."
    (interactive)
    (let* ((subdir-name (directory-file-name (dired-current-directory)))
           (parent-dir  (file-name-directory subdir-name))
           (search-term (concat " "
                                (file-name-base subdir-name)
                                (file-name-extension subdir-name t))))
      (dired-kill-subdir)
      (dired-goto-subdir parent-dir)
      (search-forward search-term)))
  (transient-define-prefix transient/dired-mode ()
    "`dired-mode' commands."
    ["Dired"
     ["File open"
      ("RET" "Open" dired-find-file)
      ("o" "Open other" dired-find-file-other-window)
      ("F" "Open marked" dired-do-find-marked-files)
      ("z" "Open external" dired--open-file-at-pt)
      ("v" "View file" dired-view-file)
      ("+" "Create dir" dired-create-directory)
      ("=" "Diff" dired-diff)
      ]
     ["File operations"
      ("C" "Copy" dired-do-copy)
      ("D" "Delete" dired-do-delete)
      ("x" "Delete marked" dired-do-flagged-delete)
      ("S" "Symlink" dired-do-symlink)
      ("Y" "Symlink to" dired-do-relsymlink)
      ("c" "Compress to" dired-do-compress-to)
      ("Z" "Compress" dired-do-compress)
      ]
     ["File modification"
      ("R" "Rename" dired-do-rename)
      ("%R" "Rename by regexp" dired-do-rename-regexp)
      ("G" "chgrp" dired-do-chgrp)
      ("M" "chmod" dired-do-chmod)
      ("O" "chown" dired-do-chown)
      ("T" "Touch" dired-do-touch)
      ]
     ["Mark"
      ("m" "File at pt" dired-mark :transient t)
      ("E" "By extension" dired-mark-extension :transient t)
      ("t" "Toggle marks" dired-toggle-marks :transient t)
      ("u" "Unmark" dired-unmark :transient t)
      ("U" "Unmark all" dired-unmark-all-marks :transient t)
      ]
     ]
    [
     ["Search/Filter"
      ("A" "Query" dired-do-find-regexp)
      ("Q" "Query-replace" dired-do-find-regexp-and-replace)
      ("{" "Find by name" find-name-dired)
      ("}" "Find by query" find-grep-dired)
      ]
     ["View"
      ("(" "Toggle details" dired-hide-details-mode :transient t)
      (")" "Toggle omit" dired-omit-mode :transient t)
      ("i" "Insert subdir" dired-maybe-insert-subdir :transient t)
      ("K" "Kill subdir" transient/dired-mode--dired-kill-and-next-subdir :transient t)
      ("s" "Sort by date" dired-sort-toggle-or-edit :transient t)
      ]
     ["Other"
      ("y" "Show file type" dired-show-file-type :transient t)
      ("g" "Refresh" revert-buffer :transient t)
      ("l" "Redisplay" dired-do-redisplay :transient t)
      ("C-o" "Display other" dired-display-file)
      ("h" "Help" describe-mode)
      ]
     ]
    )
  (define-key dired-mode-map (kbd "C-c m") #'transient/dired-mode))

24.3.3 edebug-mode transient

Edebug major mode transient.

;; major-mode specific transient for edebug-mode
(with-eval-after-load 'edebug
  (transient-define-prefix transient/edebug-mode ()
    "`edebug-mode' commands."
    ["Edebug"
     ["Modes"
      ("SPC" "Step" edebug-step-mode)
      ("n" "Next" edebug-next-mode)
      ("g" "Go" edebug-go-mode)
      ("G" "Go (nonstop)" edebug-Go-nonstop-mode)
      ("t" "Trace" edebug-Trace-fast-mode)
      ("c" "Continue" edebug-continue-mode)
      ("C" "Continue (fast)" edebug-Continue-fast-mode)
      ]
     ["Stepping"
      ("f" "Forward sexp" edebug-forward-sexp)
      ("h" "Continue to here" edebug-goto-here)
      ("I" "Instrument callee" edebug-instrument-callee)
      ("i" "Step in" edebug-step-in)
      ("o" "Step out" edebug-step-out)
      ]
     ["Breakpoints"
      ("b" "Set" edebug-set-breakpoint)
      ("u" "Unset" edebug-unset-breakpoint)
      ("B" "Next" edebug-next-breakpoint)
      ("x" "Set (cond-at-pt)" edebug-set-conditional-breakpoint)
      ("X" "Set (global cond)" edebug-set-global-break-condition)
      ]
     ]
    [
     ["Evaluation"
      ("r" "Previous result" edebug-previous-result)
      ("e" "Sexp" edebug-eval-expression)
      ("C-e" "Last sexp" edebug-eval-last-sexp)
      ("E" "Visit eval list" edebug-visit-eval-list)
      ]
     ["Views"
      ("v" "Outside" edebug-view-outside)
      ("w" "Where" edebug-where)
      ("p" "Bounce point" edebug-bounce-point)
      ("W" "Toggle save windows" edebug-toggle-save-windows)
      ]
     ["Quitting/Stopping"
      ("q" "Top level" top-level)
      ("Q" "Top level (nonstop)" edebug-top-level-nonstop)
      ("a" "Abort recursive edit" abort-recursive-edit)
      ("S" "Stop" edebug-stop)
      ]
     ]
    [
     ["Other"
      ("d" "Backtrace" edebug-pop-to-backtrace)
      ("=" "Frequency count" edebug-temp-display-freq-count)
      ("?" "Help" edebug-help)
      ]
     ]
    )
  (define-key edebug-mode-map (kbd "C-c m") #'transient/edebug-mode))

24.3.4 eww-mode transient

Emacs Web Wowser major mode transient.

;; major-mode specific transient for eww-mode
(with-eval-after-load 'eww
  (transient-define-prefix transient/eww-mode ()
    "`eww-mode' commands."
    ["Emacs Web Wowser"
     ["Navigation"
      ("G" "Search" eww)
      ("o" "Open file" eww-open-file)
      ("l" "Back" eww-back-url)
      ("r" "Forward" eww-forward-url)
      ("g" "Reload" eww-reload)
      ("s" "Switchb" eww-switch-to-buffer)
      ("S" "Buffers" eww-list-buffers)
      ("H" "History" eww-list-histories)
      ]
     ["Bookmarks"
      ("b" "Add" eww-add-bookmark)
      ("B" "List" eww-list-bookmarks)
      ]
     ["Toggle"
      ("F" "Fonts" eww-toggle-fonts :transient t)
      ("I" "Images" eww--toggle-images :transient t)
      ("M-C" "Colors" eww-toggle-colors :transient t)
      ("D" "Text direction" eww-toggle-paragraph-direction :transient t)
      ]
     ["Other"
      ("d" "Downlink link" eww-download)
      ("w" "Copy url" eww-copy-page-url)
      ("R" "View readable" eww-readable)
      ("v" "View source" eww-view-source)
      ("C" "Cookies" url-cookie-list)
      ("&" "Open external" eww-browse-with-external-browser)
      ]
     ]
    )
  (define-key eww-mode-map (kbd "C-c m") #'transient/eww-mode))

24.3.5 ibuffer-mode transient

Ibuffer major mode transient.

(require 'ibuffer)

(transient-define-prefix transient/ibuffer-mode/mark ()
  "`ibuffer-mode' mark commands."
  :transient-suffix 'transient--do-stay
  ["Ibuffer → Mark"
   [("*" "Unmark all" ibuffer-unmark-all)
    ("M" "By mode" ibuffer-mark-by-mode)
    ("m" "Modified" ibuffer-mark-modified-buffers)
    ("u" "Unsaved" ibuffer-mark-unsaved-buffers)
    ]
   [("s" "Special" ibuffer-mark-special-buffers)
    ("r" "Read-only" ibuffer-mark-read-only-buffers)
    ("/" "Dired" ibuffer-mark-dired-buffers)
    ("e" "Disassociated" ibuffer-mark-dissociated-buffers)
    ]
   [("h" "Help" ibuffer-mark-help-buffers)
    ("z" "Compressed" ibuffer-mark-compressed-file-buffers)
    ]
   ]
  )

(transient-define-prefix transient/ibuffer-mode/action ()
  "`ibuffer-mode' action commands."
  ["Ibuffer → Action"
   ["Run"
    ("E" "Eval in buffers" ibuffer-do-eval)
    ("W" "View buffers and eval" ibuffer-do-view-and-eval)
    ("F" "Command on files" ibuffer-do-shell-command-file)
    ("X" "Pipe to command" ibuffer-do-shell-command-pipe)
    ("N" "Pipe to command and replace" ibuffer-do-shell-command-pipe-replace)
    ]
   ["Search"
    ("O" "Occur" ibuffer-do-occur)
    ("U" "Replace regexp" ibuffer-do-replace-regexp)
    ("Q" "Query/Replace" ibuffer-do-query-replace)
    ("I" "Query/Replace regexp" ibuffer-do-query-replace-regexp)
    ]
   ["Properties"
    ("R" "Rename uniquely" ibuffer-do-rename-uniquely)
    ("M" "Toggle modified" ibuffer-do-toggle-modified)
    ("T" "Toggle read-only" ibuffer-do-toggle-read-only)
    ]
   ]
  [
   ["Other"
    ("A" "View" ibuffer-do-view)
    ("H" "View (other)" ibuffer-do-view-other-frame)
    ("V" "Revert" ibuffer-do-revert)
    ("P" "Print" ibuffer-do-print)
    ]
   ]
  )

(transient-define-prefix transient/ibuffer-mode/sort ()
  "`ibuffer-mode' sort commands."
  :transient-suffix 'transient--do-stay
  ["Ibuffer → Sort"
   [("a" "Alphabetic" ibuffer-do-sort-by-alphabetic)
    ("f" "Filename/Process" ibuffer-do-sort-by-filename/process)
    ("m" "Mode" ibuffer-do-sort-by-major-mode)
    ]
   [("s" "Size" ibuffer-do-sort-by-size)
    ("v" "Recency" ibuffer-do-sort-by-recency)
    ("i" "Invert" ibuffer-invert-sorting)
    ]
   ]
  )

(transient-define-prefix transient/ibuffer-mode/filter ()
  "`ibuffer-mode' filter commands."
  :transient-suffix 'transient--do-stay
  ["Ibuffer → Filter"
   ["Predicates"
    ("a" "Add saved" ibuffer-add-saved-filters)
    ("c" "By content" ibuffer-filter-by-content)
    ("e" "By predicate" ibuffer-filter-by-predicate)
    ("f" "By filename" ibuffer-filter-by-filename)
    ("m" "By mode" ibuffer-filter-by-used-mode)
    ("M" "By derived mode" ibuffer-filter-by-derived-mode)
    ("n" "By name" ibuffer-filter-by-name)
    (">" "By size gt" ibuffer-filter-by-size-gt)
    ("<" "By size lt" ibuffer-filter-by-size-lt)
    ]
   ["Operators"
    ("&" "AND" ibuffer-and-filter)
    ("|" "OR" ibuffer-or-filter)]
   ["Stack"
    ("p" "Pop" ibuffer-pop-filter)
    ("\\" "Clear" ibuffer-clear-filter-groups)
    ]
   ["Presets"
    ("R" "Saved" ibuffer-switch-to-saved-filter-groups)
    ("/" "Disable" ibuffer-filter-disable)
    ]
   ]
  )

(defun transient/ibuffer-mode--activate-dwim ()
  "Toggle filter group or visit buffer under point in `ibuffer-mode'."
  (interactive)
  (condition-case nil
             (ibuffer-toggle-filter-group)
           (error (ibuffer-visit-buffer))))

;; major-mode specific transient for ibuffer-mode
(transient-define-prefix transient/ibuffer-mode ()
  "`ibuffer-mode' commands."
  :transient-suffix 'transient--do-stay
  ["Ibuffer"
   ["Navigation"
    ("n" "Next line" ibuffer-forward-line)
    ("p" "Previous line" ibuffer-backward-line)
    ("RET" "Open" transient/ibuffer-mode--activate-dwim :transient nil)
    ("o" "Open (other)" ibuffer-visit-buffer-other-window :transient nil)
    ]
   ["Actions"
    ("m" "Mark" ibuffer-mark-forward)
    ("u" "Unmark" ibuffer-unmark-forward)
    ("*" "→ Mark" transient/ibuffer-mode/mark)
    ("S" "Save" ibuffer-do-save)
    ("D" "Delete" ibuffer-do-delete)
    ("a" "→ Action" transient/ibuffer-mode/action)
    ]
   ["View"
    ("`" "Switch format" ibuffer-switch-format)
    ("g" "Refresh" ibuffer-update)
    ("s" "→ Sort" transient/ibuffer-mode/sort)
    ("/" "→ Filter" transient/ibuffer-mode/filter)
    ]
   ]
  )
(define-key ibuffer-mode-map (kbd "C-c m") #'transient/ibuffer-mode)

24.3.6 markdown-mode transient

Markdown major mode transient.

;; major-mode specific transient for markdown-mode
(with-eval-after-load 'markdown-mode
  (with-eval-after-load 'markdown-toc
    (transient-define-prefix transient/markdown-mode ()
      "`markdown-mode' commands."
      :transient-suffix 'transient--do-stay
      ["Markdown mode (other bindings: 'C-c C-c', 'C-c C-s', 'C-c C-x')"
       ["Navigate"
        ("n" "Next" markdown-outline-next)
        ("p" "Previous" markdown-outline-previous)
        ("f" "Next (same level)" markdown-outline-next-same-level)
        ("b" "Previous (same level)" markdown-outline-previous-same-level)
        ]
       ["Move outline"
        ("<left>" "Promote" markdown-promote)
        ("<right>" "Demote" markdown-demote)
        ("<up>" "Move up" markdown-move-up)
        ("<down>" "Move down" markdown-move-down)
        ]
       ["Shift region"
        ("<" "Outdent" markdown-outdent-region)
        (">" "Indent" markdown-indent-region)
        ]
       ]
      [
       ["User interface"
        ("E" "Toggle math" markdown-toggle-math)
        ("F" "Toggle code font" markdown-toggle-fontify-code-blocks-natively)
        ("I" "Toggle images" markdown-toggle-inline-images)
        ("L" "Toggle show URL" markdown-toggle-url-hiding)
        ("M" "Toggle show markup" markdown-toggle-markup-hiding)
        ]
       ["Table of contents"
        ("t" "Insert/Refresh" markdown-toc-generate-or-refresh-toc :transient nil)
        ("C-t" "Delete" markdown-toc-delete-toc)
        ]
       ["Other"
        ("d" "Do" markdown-do :transient nil)
        ("o" "Follow" markdown-follow-thing-at-point :transient nil)
        ("N" "Cleanup list nums" markdown-cleanup-list-numbers :transient nil)
        ("'" "Edit code block" markdown-edit-code-block :transient nil)
        ]
       ]
      )
    (define-key gfm-mode-map (kbd "C-c m") #'transient/markdown-mode)
    (define-key markdown-mode-map (kbd "C-c m") #'transient/markdown-mode)))

24.3.7 org-agenda-mode transient

Org-agenda major mode transient.

;; major-mode specific transient for org-agenda-mode
(with-eval-after-load 'org-agenda
  (defun transient/org-agenda-mode--hide-done ()
    "Hide items with DONE state in `org-agenda-mode' buffer."
    (interactive)
    (setq org-agenda-skip-scheduled-if-done
          (not org-agenda-skip-scheduled-if-done))
    (org-agenda-redo-all t))

  (transient-define-prefix transient/org-agenda-mode ()
    "`org-agenda-mode' commands."
    :transient-suffix 'transient--do-stay
    ["Org agenda"
     ["Agenda view"
      ("d" "Day" org-agenda-day-view)
      ("w" "Week" org-agenda-week-view)
      ("f" "Later" org-agenda-later)
      ("b" "Earlier" org-agenda-earlier)
      ]
     ["Navigate"
      ("n" "Next line" org-agenda-next-line)
      ("p" "Prev line" org-agenda-previous-line)
      ("N" "Next item" org-agenda-next-item)
      ("P" "Prev item" org-agenda-previous-item)
      ]
     ["Visit"
      ("SPC" "Show" org-agenda-show-and-scroll-up :transient nil)
      ("TAB" "Goto" org-agenda-goto :transient nil)
      ("RET" "Switch to" org-agenda-switch-to :transient nil)
      ("C-c C-o" "Link" org-agenda-open-link :transient nil)
      ]
     ["Other"
      ("r" "Redisplay" org-agenda-redo)
      ("j" "Goto date" org-agenda-goto-date)
      ("." "Goto today" org-agenda-goto-today)
      ("(" (lambda ()
             (transient--make-description
              "Hide DONE"
              org-agenda-skip-scheduled-if-done))
       transient/org-agenda-mode--hide-done)
      ]
     ]
    [
     ["Filter"
      ("<" "By category" org-agenda-filter-by-category)
      ("_" "By effort" org-agenda-filter-by-effort)
      ("=" "By regexp" org-agenda-filter-by-regexp)
      ("\\" "By tag" org-agenda-filter-by-tag)
      ("^" "By top headline" org-agenda-filter-by-top-headline)
      ("|" "Remove all" org-agenda-filter-remove-all)
      ]
     ["Clock"
      ("I" "In" org-agenda-clock-in)
      ("O" "Out" org-agenda-clock-out)
      ("X" "Cancel" org-agenda-clock-cancel)
      ("J" "Current task" org-agenda-clock-goto)
      ("R" (lambda ()
             (transient--make-description
              "Clocktable"
              org-agenda-clockreport-mode))
       org-agenda-clockreport-mode)
      ]
     ["Modify"
      ("t" "Status" org-agenda-todo)
      (":" "Tags" org-agenda-set-tags)
      ("," "Priority" org-agenda-priority)
      ("z" "Add note" org-agenda-add-note)
      ("C-c C-x p" "Property" org-agenda-set-property)
      ]
     ]
    [
     ["Date"
      (">" "Prompt" org-agenda-date-prompt)
      ("C-c C-s" "Schedule" org-agenda-schedule)
      ("C-c C-d" "Deadline" org-agenda-deadline)
      ]
     ["Node ops"
      ("$" "Archive" org-agenda-archive)
      ("C-c C-w" "Refile" org-agenda-refile)
      ("C-k" "Kill" org-agenda-kill)
      ]
     ]
    )

  (define-key org-agenda-mode-map (kbd "C-c m") #'transient/org-agenda-mode))

24.3.8 org-mode transient

Org major mode transient.

;; major-mode specific transient for org-mode
(with-eval-after-load 'org
  (defun transient/org-mode--toggle-display-image-width ()
    "Toggle resizing of inline images in `org-mode' to one-third screen width."
    (interactive)
    (if org-image-actual-width
        (setq org-image-actual-width nil)
      (setq org-image-actual-width (list (/ (display-pixel-width) 3))))
    (org-redisplay-inline-images))

  (defun transient/org-mode--next-heading-dwim (n)
    "Go to N-th next occur highlight or visible heading otherwise."
    (interactive "p")
    (if org-occur-highlights
        (next-error n)
      (org-next-visible-heading n)))

  (defun transient/org-mode--previous-heading-dwim (n)
    "Go to N-th previous occur highlight or visible heading otherwise."
    (interactive "p")
    (if org-occur-highlights
        (previous-error n)
      (org-previous-visible-heading n)))

  (defun transient/org-mode--org-insert-structure-template ()
    "Wrapper for `org-insert-structure-template' so `transient/org-mode' can work with Org 9.3."
    (interactive)
    (if (fboundp 'org-insert-structure-template)
        (call-interactively #'org-insert-structure-template)
      (message (concat "Current version of Org does not define `org-insert-structure-template'."
                       "\n"
                       "Use the '<{char}' (e.g. '<s') structure expansions instead."))))

  (transient-define-prefix transient/org-mode ()
    "`org-mode' commands."
    ["Org"
     ["Toggle"
      ("i" (lambda ()
             (transient--make-description
              "Images"
              org-inline-image-overlays))
       org-toggle-inline-images :transient t)
      ("I" (lambda ()
             (transient--make-description
              "Indent"
              org-indent-mode))
       org-indent-mode :transient t)
      ("P" (lambda ()
             (transient--make-description
              "Prettify entities"
              org-pretty-entities))
       org-toggle-pretty-entities :transient t)
      ("M-l" (lambda ()
               (transient--make-description
                "Link display"
                (if (version< org-version "9.3")
                    (not org-descriptive-links)
                  (not org-link-descriptive))))
       org-toggle-link-display :transient t)
      ("M-i" (lambda ()
               (transient--make-description
                "Image resize"
                org-image-actual-width))
       transient/org-mode--toggle-display-image-width :transient t)
      ]
     ["Search"
      ("g" "Goto" org-goto)
      ("o" "Occur" org-occur :transient t)
      ("/" "Create sparse tree" org-sparse-tree :transient t)
      ("c" "Clear search results" org-remove-occur-highlights :transient t)
      ("n" "Next (sparse) node" transient/org-mode--next-heading-dwim :transient t)
      ("p" "Previous (sparse) node" transient/org-mode--previous-heading-dwim :transient t)
      ]
     ["Modify"
      ("t" "Todo state" org-todo)
      (":" "Tags" org-set-tags-command)
      ("," "Priority" org-priority)
      ("D" "Insert drawer" org-insert-drawer)
      ("P" "Set property" org-set-property)
      ("N" "Add note" org-add-note)
      ]
     ]
    [
     ["Node ops"
      ("a" "Archive" org-archive-subtree-default)
      ("r" "Refile" org-refile)
      ("s" "Sort" org-sort)
      ]
     ["Text ops"
      ("F" "Add footnote" org-footnote-action)
      ("<" "Insert structure" transient/org-mode--org-insert-structure-template)
      ("'" "Edit special" org-edit-special)
      ("e" "Emphasize" org-emphasize)
      ]
     [:description (lambda ()
                     (transient--make-description
                      "Narrow"
                      (buffer-narrowed-p)))
      ("M-s" "Subtree" org-narrow-to-subtree)
      ("M-b" "Block" org-narrow-to-block)
      ("M-w" "Widen" widen)
      ]
     ["Other"
      ("<tab>" "Cycle node" org-cycle :transient t)
      ("<S-tab>" "Cycle global" org-global-cycle :transient t)
      ]
     ]
    )
  (define-key org-mode-map (kbd "C-c m") #'transient/org-mode))

24.3.9 smerge-mode transient

Smerge is a lightweight alternative to Ediff, and is the default merge tool called by Magit to resolve merge conflicts.

This is a transient for the smerge-mode major mode that is adapted from here.

;; major-mode specific transient for smerge-mode
(with-eval-after-load 'smerge-mode
  (transient-define-prefix transient/smerge-mode ()
    "`smerge-mode' commands."
    ;; have suffixes not exit the transient by default
    :transient-suffix 'transient--do-stay
    ["Smerge"
     ["Move"
      ("n" "Next" smerge-next)
      ("p" "Previous" smerge-prev)]
     [
      "Keep"
      ("b" "Base" smerge-keep-base)
      ("u" "Upper" smerge-keep-upper)
      ("l" "Lower" smerge-keep-lower)
      ("a" "All" smerge-keep-all)
      ("RET" "Current" smerge-keep-current)
      ]
     ["Diff"
      ("<" "Upper/Base" smerge-diff-base-upper)
      ("=" "Upper/Lower" smerge-diff-upper-lower)
      (">" "Base/Lower" smerge-diff-base-lower)
      ("R" "Refine" smerge-refine)
      ("E" "Ediff" smerge-ediff)
      ]
     [
      "Other"
      ("C" "Combine" smerge-combine-with-next)
      ("r" "Resolve" smerge-resolve)
      ("k" "Kill current" smerge-kill-current)
      ;; emulate Vim's "ZZ" command to save and close current file
      ("ZZ" "Save and bury buffer" my-save-and-bury-buffer
       :transient nil)
      ]
     ]
    )
  (define-key smerge-mode-map (kbd "C-c m") #'transient/smerge-mode))

24.3.10 term-mode transient

Term major mode transient.

This is mainly to toggling between term-char-mode (which is similar to a regular terminal emulator) and term-line-mode (which is similar to Shell mode) when in term-mode.

;; major-mode specific transient for term-mode
(with-eval-after-load 'term
  (defun transient/term-mode--toggle-char-mode-line-mode ()
    "Toggle between `term-char-mode' and `term-line-mode' in `term-mode'."
    (interactive)
    (if (term-in-line-mode)
        (progn (term-char-mode) (message "line → char"))
      (progn (term-line-mode) (message "char → line"))))
  (transient-define-prefix transient/term-mode ()
    "`term-mode' commands."
    ["Term"
     ("m" "Toggle between `term-char-mode' and `term-line-mode'"
      transient/term-mode--toggle-char-mode-line-mode :transient t)
     ]
    )
  (define-key term-mode-map (kbd "C-c m") #'transient/term-mode)
  (define-key term-raw-map (kbd "C-c m") #'transient/term-mode))

24.4 Minor mode transients

Minor mode transients are bound to a binding other than C-c m (which is reserved for major mode transients). These typically have global bindings to allow toggling of the minor mode in buffers.

24.4.1 flymake-mode transient

Flymake minor mode transient.

;; add transient for Flymake
(with-eval-after-load 'flymake
  (transient-define-prefix transient/flymake-mode ()
    "`flymake-mode' commands."
    :transient-suffix 'transient--do-stay
    [:description (lambda ()
                    (transient--make-description
                     "Flymake"
                     flymake-mode))
     ["Error"
      ("n" "Next" flymake-goto-next-error)
      ("p" "Previous" flymake-goto-prev-error)
      ("l" "List" my-toggle-flymake-diagnostics)
      ("." "Describe" display-local-help)
      ]
     ["Check"
      ("c" "Start" flymake-start)
      ("k" "Stop" flymake-proc-stop-all-syntax-checks)
      ]
     ["Other"
      ("m" "Toggle mode" flymake-mode)
      ("r" "Reporting backends" flymake-reporting-backends)
      ("d" "Disabled backends" flymake-disabled-backends)
      ("l" "Log" flymake-switch-to-log-buffer)
      ("c" "Compile (no check)" flymake-proc-compile)
      ]
     ]
    )
  (global-set-key (kbd "C-c F") #'transient/flymake-mode))

25 Back matter

25.1 Footer

(concat "(provide '" feature ")\n"
        ";;; " feature ".el ends here")

26 Tangled files

26.1 init.el

The main configuration file.

<<generate-header(feature="init", summary="Emacs init file")>>

<<author-info>>
<<generate-timestamp()>>

;;; Commentary:

<<file-commentary-init>>

;;; Code:

;; Package management

<<load-prefer-newer>>

<<add-dirs-to-load-path>>

;; Visual user interface components

<<cursor>>

<<hide-cursor-in-non-selected-windows>>

<<remove-decorations>>

;; Customize file and local configuration

<<custom-file-and-init-local>>

;; Custom variables and utility functions / Custom variables

<<my-system-open-command>>

;; Custom variables and utility functions / Utility functions

<<my-after-jump-context-actions>>

<<my-save-and-bury-buffer>>

<<my-install-elpa-package>>

;; Package management

<<elpa-repositories>>

<<package-init>>

<<use-package>>

<<my-package-reinstall>>

;; Backend and frontend frameworks for building user interfaces

<<edit-indirect>>

<<company>>

;; Visual (part 1)

;; Backups

<<backup-files-directory>>

;; Bookmarks and history

<<recentf>>

<<saveplace>>

<<savehist>>

;; Buffers, windows, frames, workspaces / Buffer management

<<protect-buffers>>

<<ibuffer>>

<<ibuffer-filter-groups>>

<<buffer-expose>>

<<revert-without-query-mode>>

;; Buffers, windows, frames, workspaces / Window management

<<winner-mode>>

<<more-convenient-other-window-binding>>

<<my-rotate-window-buffers>>

<<help-window-select>>

;; Buffers, windows, frames, workspaces / Frame management

<<frame-resize-pixelwise>>

<<transpose-frame>>

<<more-convenient-other-frame-binding>>

;; Buffers, windows, frames, workspaces / Workspace management

<<desktop>>

<<tab-bar-mode>>

;; Command-line interaction

<<eshell>>

<<eshell-visual-commands>>

<<eshell-disable-git-pager>>

<<eshell-named-buffers>>

<<eshell-bookmark>>

<<comint-prompt-read-only>>

<<kill-term-buffers-with-q-after-end>>

<<tmux-send>>

;; Comparison tools

<<ediff>>

<<ediff-copy-a-and-b-to-c>>

;; Dired

<<dired>>

<<dired-open-file-at-pt>>

;; Editing text

<<indent-with-soft-tabs>>

<<completing-yank>>

<<delsel>>

<<sentence-end-single-space>>

<<epa-file>>

<<iedit>>

<<multiple-cursors>>

<<paredit>>

<<undo-tree>>

<<zap-up-to-char>>

<<cycle-spacing>>

<<my-join-next-line>>

<<my-open-line-below-and-above>>

<<bind-dwim-editing-commands>>

;; Emacs as an edit server

<<sigusr1-restart-emacs-server>>

;; Non-programming files

<<markdown-mode>>

<<markdown-toc>>

<<yaml-mode>>

;; Org-mode

;; <<org-elpa>>

<<rebind-org-force-cycle-archived-when-using-older-org-versions>>

<<org-base-settings>>

<<my-org-open-line-below>>

<<org-todo-keywords>>

<<org-capture-templates>>

<<org-maximize-capture-buffers>>

<<org-tags>>

<<org-export-global-macros>>

<<org-file-apps>>

<<org-agenda>>

<<org-refile>>

<<org-visual-line-mode>>

<<org-latex-pdf-process>>

<<ox-md>>

<<org-tempo>>

;; Programming / Flymake syntax checker

<<flymake>>

;; Programming / Emacs Lisp

<<el-patch>>

<<modify-lisp-indent-function>>

;; Programming / fish shell scripts

<<fish-mode>>

;; Search

<<wgrep>>

<<imenu-auto-rescan>>

<<imenu-list>>

<<search-default-mode>>

;; Visual (part 2)

<<display-line-numbers-when-editing-code>>

<<show-trailing-whitespace>>

<<show-column-number>>

<<show-matching-parentheses>>

<<add-frame-internal-border>>

<<censor>>

<<too-long-lines-mode>>

;; Web

<<eww>>

<<open-gnutls-stream-workaround>>

<<network-security>>

<<http-requests-privacy>>

;; Writing

<<flyspell-my-after-jump-context-actions-advice>>

<<flyspell-unbind-conflicting-bindings>>

<<dictionary>>

<<typo>>

;; Other

<<real-auto-save>>

<<scratch-buffer-initial-mode>>

<<scroll-conservatively>>

<<scroll-line-at-a-time>>

<<silence-audio-and-visual-bells>>

<<suppress-auto-revert-messages>>

<<suppress-startup-splash>>

<<vlf>>

<<so-long>>

<<uniquify>>

<<calc>>

<<enable-default-disabled-functions>>

<<revert-buffer>>

<<tramp-remote-search-paths>>

<<set-mark-command-repeat-pop>>

<<my-clear-register>>

<<xr>>

<<menu-bar-open-alternate-binding>>

;; Transient commands

<<transient>>

;; Transient commands / Global transients

<<bookmarks-transient>>

<<buffer-transient>>

<<debugger-transient>>

<<ediff-transient>>

<<edit-transient>>

<<frame-transient>>

<<help-transient>>

<<keyboard-macros-transient>>

<<marks-and-markers-transient>>

<<org-launcher-transient>>

<<package-transient>>

<<profiler-transient>>

<<registers-transient>>

<<search-transient>>

<<server-transient>>

<<shell-transient>>

<<system-transient>>

<<visual-transient>>

<<window-transient>>

<<workspace-transient>>

<<writing-transient>>

;; Transient commands / Major mode transients

<<debugger-mode-transient>>

<<dired-mode-transient>>

<<edebug-mode-transient>>

<<eww-mode-transient>>

<<ibuffer-mode-transient>>

<<markdown-mode-transient>>

<<org-agenda-mode-transient>>

<<org-mode-transient>>

<<restclient-mode-transient>>

<<smerge-mode-transient>>

<<term-mode-transient>>

;; Transient commands / Minor mode transients

<<flymake-mode-transient>>

<<generate-footer(feature="init")>>

27 Local Variables

Autocaption source blocks with their names when exporting so that noweb entries in the Tangled files section can be referenced against their source blocks.

Local Variables:
eval: (setq-local org-babel-exp-code-template (concat "\n#+caption:=%name=\n" org-babel-exp-code-template))
End: