emacs.d/README.org

256 KiB
Raw Blame History

Emacs setup for use with LaTeX, Lisp, Org, and Python

,#+latex_header: <<latex-header-1>>
,#+latex_header: <<latex-header-2>>
,#+latex_header: <<latex-header-3>>
,#+latex_header: <<latex-header-4>>
,#+latex_header: <<latex-header-5>>

Copying

This README contains my Emacs setup for use with LaTeX, Lisp, Org, and Python. Copyright © 2021-2024 Gerard Vermeulen.

All Org-mode source blocks with function definitions in this document are free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.

Permission is granted to copy, distribute and/or modify the other parts of this document under the terms of the GNU Free Documentation License, Version 1.3 or any later version published by the Free Software Foundation; with no Invariant Sections, with no Front-Cover Texts, and with no Back-Cover Texts. You should have received a copy of the GNU Free Documentation License with the source file of this document. If not, see <https://www.gnu.org/licenses/>.

Quick start

This setup requires at least Emacs-30. The installation procedure starts with:

  1. Backup the user-emacs-directory which defaults often to ~/.emacs.d.
  2. Run the listing prepare the user-emacs-directory without ssh access or the listing prepare the user-emacs-directory with ssh access commands.

After invoking Emacs interactively, Emacs will ask you to install a selected set of packages. Quit Emacs and invoke Emacs again.

cd ~
git clone https://forge.chapril.org/gav451/emacs.d.git .emacs.d
make --directory=.emacs.d init
emacs &
cd ~
git clone ssh://gitea@forge.chapril.org:222/gav451/emacs.d.git .emacs.d
make --directory=.emacs.d init
emacs &

Introduction

This Emacs setup aims to install automatically a minimal set of extension packages that allows to handle my reports and presentations. The file format of the reports is Org Mode plain text with Python source code blocks and the file format of the presentations is LaTeX.

This org file (more precisely the original org source file of this file) illustrates three methods in my work-flow:

  1. How to tangle (or export) source blocks from org files. This file contains source blocks to produce the files early-init.el, init.el, latexmkrc, org-store-link, and example.py by tangling.
  2. How to export org files to other formats such as HTML, LaTeX, and PDF.
  3. How org hyperlinks (info) allow to link inside and outside Org Mode: hover over or click on the links to experiment.

The AUCTeX - Aalborg University Center TeX extension package provides a powerful Text-based User Interface (TUI) environment to edit the LaTeX presentations.

The Citar extension package provides quick filtering and selecting of bibliographic entries, and the option to run different commands on those selections. Citar exploits the extension package Vertico. The CiteProc extension package provides CSL: citation style language processing capabilities to Citar and Org Mode. A curated repository of CSL styles is the citation style language repository.

The PDF-Tools extension package renders PDF file with the possibility to annotate the file or to click on anchors in the PDF file that link back to the original LaTeX file of the document. An example of my work-flow are the steps to convert this org file to PDF and to see the result with PDF-Tools in Emacs: execute the commands pdf-tools-install, org-babel-tangle, org-latex-export-latex-to-latex, and compile. This sets up an infinite LaTeX compilation loop to update and redisplay the PDF file after execution of the org-latex-export-latex-to-latex command in this buffer.

Here follows a list of links on how to use Emacs and Elisp:

  1. Mastering Emacs is a link to a blog with interesting posts that promotes a book on how to become a proficient Emacs user.
  2. Use GNU Emacs: The Plain Text Computing Environment explains the fundamentals of the Emacs abstractions before showing how to exploit those abstractions interactively. It targets a similar audience as the Mastering Emacs book.
  3. A guided tour of Emacs is a link to a video tour pointing how buffers (info), dired (info), documentation (info), elisp (info), elisp debugging (info), eshell (info), and keyboard macros (info) turn Emacs in an powerful coding and editing environment.
  4. Emergency Emacs is a link to a video on fundamental editing features and principles with focus on ediff (info), undo (info), moving point (info), erasing (info), and dynamic abbreviations (info).
  5. Endless Parentheses is a blog with mindblowing code snippets.
  6. How to open a file in Emacs looks into elisp (info) and elisp debugging (info) and then compares Emacs, Vim, Neovim, and Visual Studio Code with respect to values and technology.
  7. Learning Emacs and Elisp is a link to a video tutorial with a transcript on the best approach to learn Emacs and Elisp.

Early Init File (info)

Try to load no-littering as early as possible, since it helps to keep ~/.emacs.d clean.

;;; early-init.el --- user early-init file        -*- lexical-binding: t -*-
;;; Commentary:
;;; Code:
(setq load-prefer-newer t)

(require 'no-littering nil 'noerror)
;; Emacs looks for "Local variables:" after the last "form-feed".

;; Local Variables:
;; indent-tabs-mode: nil
;; End:
;;; early-init.el ends here

In order to get help in understanding the code block above in a buffer showing the original Org source file, type

(eval

after moving point (or cursor) to one of the items of the list:

  1. src_emacs-lisp[:results none]{(describe-variable 'load-prefer-newer)}
  2. src_emacs-lisp[:results none]{(apropos-library "no-littering")}
  3. src_emacs-lisp[:results none]{(info "(emacs) Pages")}
  4. src_emacs-lisp[:results none]{(info "(emacs) Specifying File Variables")}
  5. src_emacs-lisp{(find-function #'hack-local-variables)}

to execute the code between the curly braces for access to help. This shows that Emacs is a self-documenting editor. See also the form-feed extension package which this setup does not install.

Init File (info) header

The user-init-file header requires cl-lib and sets Emacs options. It consists of three parts in listing lst:1st-setopt-call, lst:2nd-setopt-call, and lst:2nd-setopt-call in order to limit the length of the listings for exporting to LaTeX.

The init file (info) does load the custom-file according to the recommendation of saving customizations (info).

;;; init.el --- user init file                    -*- lexical-binding: t -*-
;;; Commentary:
;;; Code:

(require 'cl-lib)

(when (< emacs-major-version 30)
  (error "This `init.el' requires at least Emacs-30"))

(setopt
 after-save-hook #'executable-make-buffer-file-executable-if-script-p
 column-number-mode t
 cursor-type 'box
 custom-file (locate-user-emacs-file "custom.el")
 ;; Known problem: EasyPG fails to save files encrypted with GnuPG v2.4.1.
 ;; Darwin: brew install gnupg@2.2
 epg-pinentry-mode 'loopback
 global-hl-line-mode t
 global-hl-line-sticky-flag t
 history-delete-duplicates t
 history-length 200
 indent-tabs-mode nil
 inhibit-startup-buffer-menu t
 inhibit-startup-screen t
 initial-scratch-message ""
 isearch-lazy-count t
 kill-ring-max 300
 lazy-count-prefix-format nil
 lazy-count-suffix-format " (%s/%s)"
 lazy-highlight-initial-delay 1.0
 mode-line-compact t
 next-error-message-highlight t
 recentf-max-saved-items 100
 recentf-mode t
 save-place-mode t
 savehist-mode t
 scroll-bar-mode nil
 tab-always-indent 'complete
 tool-bar-mode nil
 tooltip-mode nil
 url-cookie-trusted-urls nil
 url-cookie-untrusted-urls '(".*")
 use-dialog-box nil
 use-short-answers t
 view-read-only t
 winner-mode t)
(setopt
 ;; See https://github.com/melpa/melpa#mirrors for official Melpa mirrors.
 ;; ("melpa" . "https://www.mirrorservice.org/sites/melpa.org/packages/")
 package-archives '(("gnu" . "https://elpa.gnu.org/packages/")
                    ("gnu-devel" . "https://elpa.gnu.org/devel/")
                    ("melpa" . "https://melpa.org/packages/")
                    ("melpa-stable" . "https://stable.melpa.org/packages/")
                    ("nongnu" . "https://elpa.nongnu.org/nongnu/"))
 package-install-upgrade-built-in nil
 ;; Pin packages to GNU ELPA or to MELPA STABLE for info and/or stability.
 package-pinned-packages  '((auctex . "gnu")
                            (citar . "melpa-stable")
                            (compat . "gnu")
                            (consult . "gnu")
                            (dash . "melpa-stable")
                            (debbugs . "gnu")
                            (emms . "gnu")
                            (engrave-faces . "gnu-devel")
                            (f . "melpa-stable")
                            (forge . "melpa-stable")
                            (git-commit . "nongnu")
                            (keycast . "nongnu")
                            (magit . "nongnu")
                            (magit-section . "nongnu")
                            (marginalia . "gnu")
                            (osm . "gnu")
                            (parsebib . "melpa-stable")
                            (queue . "gnu")
                            (rainbow-mode . "gnu")
                            (realgud . "melpa")
                            (s . "melpa-stable")
                            (xr . "gnu")
                            (vertico . "gnu")
                            (with-editor . "nongnu")
                            (yasnippet . "gnu"))
 package-selected-packages '(no-littering))
;; To use different Org git repositories (org or gro):
;; (push (expand-file-name "~/VCS/org-mode/lisp") load-path)
;; Postpone loading Org after shadowing Org and sh-script faces below.

(when (eq system-type 'darwin)
  ;; Failed to initialize color list unarchiver: ...
  ;; BUG#32854 and BUG#70836: rm ~/Library/Colors/Emacs.clr
  ;; INSTALL file: '--without-dbus --without-gconf --without-gsettings'.
  (setopt ns-alternate-modifier nil
          ns-command-modifier 'meta
          ns-right-command-modifier 'super))
(when (eq window-system 'ns)
  (add-to-list 'initial-frame-alist '(height . 51))
  (add-to-list 'initial-frame-alist '(width . 180)))

(when (file-exists-p custom-file)
  (load custom-file))

Install the selected packages (info)

Emacs installs packages from archives on the internet. This setup uses five archives:

  1. The GNU Emacs Lisp Package Archive.
  2. The Development Emacs Lisp Package Archive.
  3. The NonGNU Emacs Lisp Package Archive.
  4. The Milkypostman's Emacs Lisp Package Archive (MELPA) with its official mirror in case is Melpa.org down? tells MELPA is down for everyone.
  5. The Stable Milkypostman's Emacs Lisp Package Archive.

The code in listing lst:install-selected-packages assumes that the package system is in a virgin state if the package no-littering is not present:

  1. It installs and loads no-littering after ensuring refreshing of the contents of available packages.
  2. It calls src_emacs-lisp[:results silent]{(package-install-selected-packages)} to check the installation status of all packages in src_emacs-lisp[:results silent]{package-selected-packages} and to install the missing packages after the user has agreed to its prompt.
  3. It defines a function to ensure the installation of packages in other source blocks.

In case of normal Emacs usage, src_emacs-lisp[:results silent]{(list-packages)} refreshes the contents of packages and allows to update packages to the latest version.

(defvar package-selected-packages)

(unless noninteractive
  (unless (require 'no-littering nil 'noerror)
    (unless (bound-and-true-p package-archive-contents)
      (package-refresh-contents))
    ;; Install and require `no-littering'.
    (package-install 'no-littering)
    (require 'no-littering)
    ;; Install the selected packages.
    (package-install-selected-packages)))

(defun ensure-package-selection (package)
  "Ensure selection of PACKAGE."
  (when (and (package-installed-p package)
             (bound-and-true-p package-selected-packages))
    (cl-pushnew package package-selected-packages)))

(defun ensure-package-installation (&rest packages)
  "Ensure installation of all packages in PACKAGES."
  (let ((ok t))
    (dolist (package packages)
      (unless (package-installed-p package)
        (package-install package))
      (unless (ensure-package-selection package)
        (setq ok nil)))
    ok))

Emacs Tree-sitter

Mickey Peterson's post How To Get Started with Tree-Sitter explains how to use tree-sitter in Emacs-29.1. Listing Setup treesit grammar sources configures where to find the different tree-sitter grammar sources for different languages. Listing Setup Go programming shows how to configure tab-width for go-ts-mod and go-mode-ts-mode.

(when (require 'treesit nil 'noerror)
  (setopt
   treesit-language-source-alist
   '((bash "https://github.com/tree-sitter/tree-sitter-bash" "master" "src")
     (c "https://github.com/tree-sitter/tree-sitter-c" "master" "src")
     (cmake "https://github.com/uyha/tree-sitter-cmake" "master" "src")
     (cpp "https://github.com/tree-sitter/tree-sitter-cpp" "master" "src")
     (css "https://github.com/tree-sitter/tree-sitter-css" "master" "src")
     (glsl "https://github.com/theHamsta/tree-sitter-glsl" "master" "src")
     ;; go-ts-mode requires libtree-sitter-go and libtree-sitter-gomode.
     (go "https://github.com/tree-sitter/tree-sitter-go" "master" "src")
     (gomod "https://github.com/camdencheek/tree-sitter-go-mod" "main" "src")
     (html "https://github.com/tree-sitter/tree-sitter-html" "master" "src")
     (java "https://github.com/tree-sitter/tree-sitter-java" "master" "src")
     (javascript "https://github.com/tree-sitter/tree-sitter-javascript"
                 "master" "src")
     (json "https://github.com/tree-sitter/tree-sitter-json" "master" "src")
     (julia "https://github.com/tree-sitter/tree-sitter-julia" "master" "src")
     (lua "https://github.com/MunifTanjim/tree-sitter-lua" "main" "src")
     (make "https://github.com/alemuller/tree-sitter-make" "main" "src")
     (perl "https://github.com/ganezdragon/tree-sitter-perl" "master" "src")
     (python "https://github.com/tree-sitter/tree-sitter-python" "master" "src")
     (ruby "https://github.com/tree-sitter/tree-sitter-ruby" "master" "src")
     (rust "https://github.com/tree-sitter/tree-sitter-rust" "master" "src")
     (scala "https://github.com/tree-sitter/tree-sitter-scala" "master" "src")
     (sql "https://github.com/m-novikov/tree-sitter-sql" "main" "src")
     (swift "https://github.com/tree-sitter/tree-sitter-swift" "master" "src")
     (toml "https://github.com/tree-sitter/tree-sitter-toml" "master" "src")
     (tsx "https://github.com/tree-sitter/tree-sitter-typescript"
          "master" "tsx/src")
     (typescript "https://github.com/tree-sitter/tree-sitter-typescript"
                 "master" "typescript/src")
     (yaml "https://github.com/ikatyang/tree-sitter-yaml" "master" "src"))))

Text faces or styles (info)

This setup does not use custom themes (info), but it does a minimal setup of fonts and faces. The note on mixed font heights in Emacs for how to setup fonts properly. It boils down to two rules:

  1. The height of the default face must be an integer number to make the height a physical quantity.
  2. The heights of all other faces must be real numbers to scale those heights with respect to the height of the face (those heights default to 1.0 for no scaling).

The code in listing lst:configure-face-attributes implements those rules. Listing lst:face-setting-functions shows that font scaling is easy in case of proper initialization of all face heights in a frame. To adjust the font size in windows, invoke src_emacs-lisp adjust the font size in the current buffer, type to invoke src_emacs-lisp[:results silent]{(text-scale-adjust +1)} before making further adjustments as displayed. Listing lst:face-setting-functions, lst:shadow-org-font-lock-faces, and lst:shadow-emacs-font-lock-faces show tweaks to improve visibility without theming:

  1. Toggling between a dark and light background by means of src_emacs-lisp{(invert-default-face)}.
  2. Shadowing the definition of faces before loading. The last item in the page on fontifying Org mode source code blocks describes this method.

Listing lst:use-buffer-face-mode-for-fixed-or-variable-pitch-face contains functions to set a fixed or variable pitch face in the current buffer and selects a fixed pitch face for magit-mode and progmode buffers. Type

(eval

to inspect faces at point. Listing lst:disable-mac-mouse-change-font-size tries to disable accidental changing of the font size with the mouse on Darwin. Listing lst:setup-visual-line-mode configures visual-line-mode.

(with-eval-after-load 'emacs
  ;; Set face attributes.
  (cond
   ((eq system-type 'darwin)
    (set-face-attribute 'default nil :family "Hack" :height 120)
    (set-face-attribute 'fixed-pitch nil :family "Hack")
    (set-face-attribute 'variable-pitch nil :family "FiraGo"))
   ((eq system-type 'gnu/linux)
    (set-face-attribute 'default nil :family "Hack" :height 110)
    (set-face-attribute 'fixed-pitch nil :family "Hack")
    (set-face-attribute 'variable-pitch nil :family "FiraGo"))
   (t
    (set-face-attribute 'default nil :family "Hack" :height 110)
    (set-face-attribute 'fixed-pitch nil :family "Hack")
    (set-face-attribute 'variable-pitch nil :family "DejaVu Sans"))))
(with-eval-after-load 'emacs
  ;; Neither `set-default-face-height' nor `text-scale-adjust' work well.
  (defun set-default-face-height ()
    "Set the default face height in all current and future frames.

Scale all other faces with a height that is a real number."
    (interactive)
    (let* ((prompt (format "face height (%s): "
                           (face-attribute 'default :height)))
           (choices (mapcar #'number-to-string
                            (number-sequence 50 200 10)))
           (height (string-to-number
                    (completing-read prompt choices nil 'require-match))))
      (message "Setting the height of the default face to %s" height)
      (set-face-attribute 'default nil :height height)))

  (defun invert-default-face ()
    "Invert the default face."
    (interactive)
    (invert-face 'default)))
;; Shadow two definitions in org-faces.el:
(defface org-block
  ;; https://emacs.stackexchange.com/a/9604 answers:
  ;; How to override a defined face for light and dark backgrounds?
  `((((background dark))
     :inherit (fixed-pitch)
     ,@(and (>= emacs-major-version 27) '(:extend t))
     :background "#444444")
    (t
     :inherit (fixed-pitch)
     ,@(and (>= emacs-major-version 27) '(:extend t))
     :background "#FFFFD0"))
  "My face used for text inside blocks.

It is always used for source blocks.  You can refine what face
should be used depending on the source block language by setting,
`org-src-block-faces', which takes precedence.

When `org-fontify-quote-and-verse-blocks' is not nil, text inside
verse and quote blocks are fontified using the `org-verse' and
`org-quote' faces, which inherit from `org-block'."
  :group 'org-faces)

(defface org-table
  ;; https://emacs.stackexchange.com/a/9604 answers:
  ;; How to override a defined face for light and dark backgrounds?
  `((((background dark))
     :inherit (fixed-pitch)
     ,@(and (>= emacs-major-version 27) '(:extend t))
     :background "#444444")
    (t
     :inherit (fixed-pitch)
     ,@(and (>= emacs-major-version 27) '(:extend t))
     :background "#FFFFD0"))
  "My face for tables."
  :group 'org-faces)
;; Shadow one definition in sh-script.el:
(defface sh-heredoc
  '((((class color) (background dark))
     (:foreground "yellow"))
    (((class color) (background light))
     (:foreground "magenta"))
    (t
     (:weight bold)))
  "My face to show a here-document."
  :group 'sh-indentation)
;; Use proportional font faces in current buffer
(defun set-buffer-variable-pitch-face ()
  "Set a variable width (proportional) font in current buffer."
  (interactive)
  (setq-local buffer-face-mode-face 'variable-pitch)
  (buffer-face-mode))

;; Use monospaced font faces in current buffer
(defun set-buffer-fixed-pitch-face ()
  "Set a fixed width (monospace) font in current buffer."
  (interactive)
  (setq-local buffer-face-mode-face 'fixed-pitch)
  (buffer-face-mode))

(add-hook 'magit-mode-hook #'set-buffer-fixed-pitch-face)
(add-hook 'prog-mode-hook #'set-buffer-fixed-pitch-face)
(when (eq system-type 'darwin)
  ;; https://lmno.lol/alvaro/hey-mouse-dont-mess-with-my-emacs-font-size
  ;; Hint: reset font size with "C-u 0 M-x text-scale-adjust".
  ;; GAV: how to explain undefined "C-<wheel-left>" and "C-<wheel-right>"?
  (global-set-key (kbd "<pinch>") 'ignore)
  (global-set-key (kbd "<C-wheel-up>") 'ignore)
  (global-set-key (kbd "<C-wheel-down>") 'ignore))
(when (fboundp 'visual-wrap-prefix-mode)
  (add-hook 'visual-line-mode-hook 'visual-wrap-prefix-mode))

Window management (info)

Mickey Peterson's post Demystifying Emacs's Window Manager invites to improve window placement. Listing lst:1st-window-management and lst:2nd-window-management implement a selection of his recommendations. See:

  1. The Zen of Buffer Display (info)
  2. Configuring the Emacs display system

for technical information.

(with-eval-after-load 'emacs
  ;; https://www.masteringemacs.org/article/demystifying-emacs-window-manager
  (defun split-root-below (arg)
    "Split window below from the root or from the parent with ARG."
    (interactive "P")
    (split-window (if arg
                      (window-parent (selected-window))
                    (frame-root-window))
                  nil 'below nil))

  (defun split-root-right (arg)
    "Split window right from the root or from the parent with ARG."
    (interactive "P")
    (split-window (if arg
                      (window-parent (selected-window))
                    (frame-root-window))
                  nil 'right nil))

   (defun toggle-window-dedication ()
    "Toggles window dedication in the selected window."
    (interactive)
    (set-window-dedicated-p
     (selected-window) (not (window-dedicated-p (selected-window)))))

   (defun make-display-buffer-matcher-function (major-modes)
    "Return a lambda function to match a list of MAJOR-MODES."
    (lambda (buf-name _action)
      (with-current-buffer buf-name (apply #'derived-mode-p major-modes))))

   (keymap-global-set "M-o" #'other-window))
;; (info "(elisp) Displaying Buffers")
;; (info "(emacs) Window Choice")
;; (describe-function 'display-buffer)
(with-eval-after-load 'emacs
  ;; https://www.masteringemacs.org/article/demystifying-emacs-window-manager
  (setopt switch-to-buffer-obey-display-actions t)
  (add-to-list 'display-buffer-alist
               `(,(rx (or "*Apropos*" "*Dictionary*"))
                 (display-buffer-reuse-window display-buffer-pop-up-window)
                 (inhibit-same-window . nil)))
  (add-to-list 'display-buffer-alist
               `(,(rx (or "*Help*" "*info*"))
                 display-buffer-pop-up-window
                 (inhibit-same-window . t)))
  (add-to-list 'display-buffer-alist
               `(,(rx (or "*Occur*" "*grep*" "*xref*"))
                 display-buffer-reuse-window
                 (inhibit-same-window . nil)))
  ;; BUG#70773: Eli tells `(setq delayed-warnings-hook nil)' is better.
  ;; BUG#70795: Inconsistent `warning-display-at-bottom' behavior.
  ;; https://lists.gnu.org/archive/html/bug-gnu-emacs/2024-05/msg00359.html
  ;; Works now always as after setting `warnings-display-at-bottom' to `nil'.
  (add-to-list 'display-buffer-alist
               `(,(rx (or "*Warnings*" "*compilation*"))
                 display-buffer-no-window
                 (allow-no-window . t))))

Bookmarks (info)

Listing bookmark setup preserves bookmark history in bookmark-default.el. Table tab:bookmark-commands-and-bindings lists all bookmark commands with working key bindings. Not all key bindings in src_emacs-lisp[:results silent]{(describe-variable 'bookmark-map)} are operational for different reasons.

BUG: The virtual buffers of the consult-buffer command do not seem to work. Therefore, consult-buffer is no means to access the bookmark history (contrary to claims in consult (info)).

(with-eval-after-load 'bookmark
  (setopt bookmark-save-flag 1)
  (setf bookmark-exit-hook 'bookmark-unload-function))
command key binding
bookmark-delete
bookmark-delete-all
bookmark-insert

(eval

bookmark-insert-location
bookmark-jump

(eval

bookmark-jump-other-frame
bookmark-jump-other-window
bookmark-load
bookmark-rename
bookmark-save
bookmark-set

(eval

bookmark-set-no-overwrite

(eval

bookmark-write
consult-bookmark

(eval

list-bookmarks

Registers (info)

Listing register separator usage example shows how to use a register

(eval

as register separator when appending regions to text registers. Listing setup register usage prepares my preferred register usage. Table tab:register-commands-and-bindings lists all register commands with working key bindings. The desktop library allows to preserve the register history between Emacs sessions.

When I set register ?+ to
(set-register ?+ "\n*REGISTER SEPARATOR*\n")
to test append-to-register on two regions like
Mark this line without new-line as region 1
Mark this line without new-line as region 2
The correct result of
"C-x r s T" with region 1 marked
"C-x r + T" with region 2 marked
"C-x r i T"
is:
Mark this line without new-line as region 1
*REGISTER SEPARATOR*
Mark this line without new-line as region 2
(with-eval-after-load 'register
  ;; Register `?+' serves as register separator when using `append-to-register':
  (set-register ?+ "\n")

  ;; https://emacs.stackexchange.com/a/52290
  (defun delete-all-registers ()
    "Delete all registers."
    (interactive)
    (setq register-alist nil)))
command key binding
append-to-register
copy-rectangle-to-register

(eval

copy-to-register

(eval

frameset-to-register

(eval

increment-register

(eval

insert-register

(eval

jump-to-register

(eval

kmacro-to-register

(eval

number-to-register

(eval

point-to-register

(eval

prepend-to-register
set-register
window-configuration-to-register

(eval

Dired: directory editor as file manager (info)

Dired (info) and the ranger file manager offer similar capabilities for copying, deleting, opening, renaming, and viewing files and directories, but the integration of dired (info) in Emacs is obviously much better than the integration of ranger in Emacs.

For instance, this setup allows to insert an org-mode link to a file containing a specific image into an org-mode buffer by means of the facilities of dired (info) as follows:

  1. Type

    (eval

    to open the directory containing the file with the specific image in a dired-mode buffer.
  2. Start searching for the specific image by navigating to the name of any image file.
  3. Type

    (eval

    to switch to view the file in an image-mode buffer.
  4. In the image-mode buffer, continue searching for the specific image by typing:

    • (eval

      to view the next image in the dired-mode buffer,
    • (eval

      to view the previous image in the dired-mode buffer, and
    • (eval

      to quit the image-mode buffer and to go back to the dired-mode buffer.
  5. After finding the specific image, use

    (eval

    in the image-mode buffer or the dired-mode buffer to store the org-link.
  6. Switch to the org-mode buffer and use

    (eval

    to insert the org-link into the buffer.

Listing lst:set-dired-options makes the directory editor sort entries alphabetically after grouping the directories before grouping the files by extension.

(with-eval-after-load 'dired
  (setopt dired-dwim-target t
          ;; | switch | action                                 |
          ;; |--------+----------------------------------------|
          ;; | -a     | also list hidden entries               |
          ;; | -l     | use a long listing format              |
          ;; | -X     | sort alphabetically by entry extension |
          ;; | -G     | skip long listing format group names   |
          ;; | -1     | list one entry per line                |
          dired-listing-switches "-alGX1 --group-directories-first"
          dired-recursive-copies 'always
          dired-recursive-deletes 'always))

(with-eval-after-load 'wdired
  (setopt wdired-allow-to-change-permissions t))

Listing lst:extra-dired-key-bindings adds a new key binding to dired-mode-map to open files with the Emacs Web Wowser inside Emacs.

(with-eval-after-load 'dired
  (defun dired-eww-open-file ()
    "In Dired, open the regular file named on this line with eww"
    (interactive)
    (let ((file (dired-get-file-for-visit)))
      (if (file-regular-p file)
          (eww-open-file file)
        (error "Eww rejects `%s', since it is not a regular file" file))))

  (keymap-set dired-mode-map "E" #'dired-eww-open-file))

Minibuffer (info)

Listing lst:touch-minibuffer-look implements sets minibuffer options and enables marginalia-mode (after ensuring the installation of marginalia) which adds extra information (for instance documentation strings to commands) to items in the minibuffer. See minibuffer completion styles (info) and Understanding minibuffer completion for more information.

(with-eval-after-load 'minibuffer
  ;; https://www.masteringemacs.org/article/understanding-minibuffer-completion
  (setopt
   completion-category-overrides '((file (styles basic substring)))
   completion-styles '(basic flex partial-completion substring)))

(when (ensure-package-installation 'marginalia)
  (marginalia-mode +1))

Processes (info)

Listing lst:process-utilities defines a function to run a (command-line) program with arguments and to obtain a cons of its numeric return code as well as its output to stdout.

;; https://gitlab.com/howardabrams/spacemacs.d/blob/master/layers/ha-org/funcs.el#L418
(defun get-program-code-output (program &rest args)
  "Run PROGRAM with ARGS and return the `cons' of return-code and output."
  (with-temp-buffer
    (cons (apply 'call-process program nil (current-buffer) nil args)
          (buffer-substring-no-properties (point-min) (point-max)))))

Help (info)

Listing lst:setup-help enriches the help buffer. Table tab:help-key-bindings lists a number of key bindings to start playing with the help facilities of Emacs. Try .

(with-eval-after-load 'help-fns
  ;; ChatGPT recommends to require `shortdoc' contrary to the
  ;; `shortdoc-help-fns-examples-function' documentation string.
  ;; BUG#71537: "emacs -Q" works with a simpler `add-hook' form. Note:
  ;; `add-hook' should append `shortdoc-help-fns-examples-function' to
  ;; `help-fns-describe-function-functions'.
  (add-hook 'help-fns-describe-function-functions
            #'shortdoc-help-fns-examples-function 'append)
  (setopt help-enable-symbol-autoload t))
command key map keys
Info-goto-emacs-command-node help-map

(eval

describe-function help-map

(eval

describe-key help-map

(eval

describe-symbol help-map

(eval

describe-variable help-map

(eval

info help-map

(eval

Shortdoc-display-group (info)

Listing lst:setup-shortdoc binds

(eval

to shortdoc-display-group.

(when (fboundp 'shortdoc-display-group)
  (keymap-set help-map "y" #'shortdoc-display-group))

Disabling Commands (info)

Execute src_emacs-lisp[:results none]{(find-library "novice")} to see how Emacs prevents new users from shooting themselves in the feet. Listing lst:configure-disabled-command-function enables disabled commands on the fly.

(with-eval-after-load 'emacs
  (setq disabled-command-function
        (defun enable-this-command (&rest _args)
          "Called when executing a disabled command.
Enable it and re-execute it."
          (put this-command 'disabled nil)
          (message "You typed %s.  Emacs enabled %s."
                   (key-description (this-command-keys)) this-command)
          (sit-for 0)
          (call-interactively this-command))))

Info (info)

Listing lst:configure-info adds the required paths to the places where info looks for files.

(with-eval-after-load 'info
  ;; Make Emacs find ALL "*.info" files in `package-user-dir' on Gentoo Linux.
  (when (eq system-type 'gnu/linux)
    (dolist (path
             (nreverse
              (mapcar
               (lambda (name)
                 (expand-file-name (file-name-directory name)))
               (directory-files-recursively package-user-dir "\\.info\\'"))))
      (add-to-list 'Info-directory-list path nil #'file-equal-p)))
  ;; Make Emacs find the git Org info files.
  ;; (push (expand-file-name "~/VCS/org-mode/doc") Info-directory-list)
  ;; Make Emacs find Python info files.
  (push (expand-file-name "~/.local/share/info") Info-directory-list))

Key bindings (info)

Table tab:basic-key-bindings lists basic key bindings. Enabling which-key-mode is another way of associating key bindings with commands. This setup assumes that which-key is a builtin package or an installed external package. Notes:

  1. This setup does not enable which-key-mode because of rare interference with for instance a process that the compile command launches.
  2. ChatGPT bullshits on "How to page in which-key-mode on Emacs". Anyhow, it is easier to bypass paging.
  3. The easiest solution to bypass paging in which-key-mode as listing lst:setup-which-key implements.
command key map keys
undo global-map

(eval

backward-kill-word global-map

(eval

*backward-char global-map

(eval

forward-char global-map

(eval

backward-word global-map

(eval

forward-word global-map

(eval

backward-sentence global-map

(eval

forward-sentence global-map

(eval

(when (package-installed-p 'which-key)
  (setopt max-mini-window-height 20)
  (which-key-setup-minibuffer))

Interaction-log

(when (and (ensure-package-installation 'interaction-log)
           (require 'interaction-log nil 'noerror))

  ;; https://www.skybert.net/emacs/show-shortcuts-when-giving-presentations/
  (defun ilog-ensure-ilog-buffer-window ()
    "Ensure the visibility of the `ilog' buffer, when it exists."
    (when (and (get-buffer ilog-buffer-name)
               (not (get-buffer-window ilog-buffer-name)))
      (display-buffer (get-buffer ilog-buffer-name))))

  (keymap-set help-map "C-l" #'interaction-log-mode)
  (advice-add 'ilog-timer-function :after #'ilog-ensure-ilog-buffer-window))

Keycast

Listing lst:setup-keycast sets up keycast so that keycast-log-mode uses a buffer similar to what calling ediff-setup-windows-plain returns.

;; Make `keycast-log-update-buffer' use a buffer similar to what
;; calling `ediff-setup-windows-plain' returns.
(when (ensure-package-installation 'keycast)
  (setopt keycast-log-newest-first t
          keycast-mode-line-window-predicate #'keycast-bottom-right-window-p)

  (defun keycast-log-update-buffer-plain ()
    (let ((buffer (get-buffer keycast-log-buffer-name)))
      (unless (buffer-live-p buffer)
        (setq buffer (get-buffer-create keycast-log-buffer-name))
        (with-current-buffer buffer
          (setq buffer-read-only t)))
      (unless (get-buffer-window buffer)
        (display-buffer buffer '(display-buffer-at-bottom
                                 (dedicated . t)
                                 (window-height . 10))))
      (when-let ((output (keycast--format keycast-log-format)))
        (with-current-buffer buffer
          (goto-char (if keycast-log-newest-first (point-min) (point-max)))
          (let ((inhibit-read-only t))
            (when (and (> keycast--command-repetitions 0)
                       (string-match-p "%[rR]" keycast-log-format))
              (unless keycast-log-newest-first
                (backward-char))
              (ignore-errors
                (delete-region (line-beginning-position)
                               (1+ (line-end-position)))))
            (insert output))
          (goto-char (if keycast-log-newest-first (point-min) (point-max)))))))

  (advice-add 'keycast-log-update-buffer
              :override #'keycast-log-update-buffer-plain))

Using Emacs as a server (info)

Emacs can act as a server that listens to a socket to share its state (for instance buffers and command history) with other programs by means of a shell command emacsclient. Section #sec:latexmk-save-compile-display-loop explains how to use emacsclient to install an asynchronous (or background) loop of saving a LaTeX file, compiling it, and redisplaying the output in Emacs.

(when window-system
  (unless (or noninteractive (daemonp))
    (add-hook 'after-init-hook #'server-start)))

Latexmk save-compile-display loop

The latexmk resource file in listing lst:latexmkrc shows how to use emacsclient to (re)display the PDF file in Emacs after each successful (re)compilation on condition that the local compile-command variable is correct. The local compile-command variable in the local variables section (only visible in org files, but not in html and pdf files) shows how to make use of the latexmkrc file.

# pdf creator
$pdf_mode = 4;  # 4 means lualatex
# pdf previewer and update pdf previewer
$pdf_previewer = "emacsclient -e '(find-file-other-window %S)'";
$pdf_update_method = 4;  # 4 runs $pdf_update_command to force the update
$pdf_update_command = "emacsclient -e '(with-current-buffer (find-buffer-visiting %S) (pdf-view-revert-buffer nil t))'";
# see for instance glossary.latexmkrc
add_cus_dep( 'acn', 'acr', 0, 'makeglossaries' );
add_cus_dep( 'glo', 'gls', 0, 'makeglossaries' );
# use ".=" to append to $clean_ext
$clean_ext .= " acr acn alg bbl dvi glo gls glg ist lol lst";
$clean_ext .= " nav run.xml snm synctex.gz";
sub makeglossaries {
    my ($name, $path) = fileparse( $$Psource );
    return system "makeglossaries -d '$path' '$name'";
}
# Emacs looks for "Local variables:" after the last "form-feed".

# Local Variables:
# mode: perl
# End:

Calendar, Diary, and Time (info)

(with-eval-after-load 'time
  (setopt world-clock-list
          '(("America/Los_Angeles" "Seattle")
            ("America/New_York" "New York")
            ("Europe/London" "London")
            ("Europe/Moscow" "Moscow")
            ("Europe/Paris" "Paris")
            ("Europe/Istanbul" "Istanbul")
            ("Africa/Cairo" "Cairo")
            ("Asia/Jerusalem" "Jerusalem")
            ("Asia/Singapore" "Singapore")
            ("Asia/Tokyo" "Tokyo"))
          world-clock-time-format "%a %d %b %R %z"))

Saving Emacs sessions (info)

Listing setup desktop makes Emacs save its state between closing its session to opening the next session. This setup relies on desktop-save-mode in case of overlapping functionality of desktop-save-mode and savehist-mode. Listing lst:prune-file-name-history prunes non-existing files from file-name-history, which is under Emacs control but neither desktop-save-mode nor savehist-mode.

NOTE: The src_emacs-lisp{(describe-function 'savehist-mode)} documentation explains why it is best to turn on savehist-mode in user-init-file.

BUG: Adding kill-ring to savehist-additional-variables does not save kill-ring from an Emacs session to the next session, contrary to the src_emacs-lisp{(describe-variable 'savehist-additional-variables)} documentation.

(with-eval-after-load 'emacs
  ;; GAV: I fail to implement the idea of ChatGPT on how to reload
  ;; `eww' pages automatically while restoring the desktop (idea is to
  ;; add save and restore handlers to `desktop-buffer-mode-handlers').
  ;; GAV: (setopt eww-restore-desktop t) fails and starts hesitantly.
  ;; GAV: I fail to code a function for setting the
  ;; `desktop-buffers-not-to-save-function' option.
  (setopt desktop-buffers-not-to-save
          (rx bos (or "*Apropos" "compilation*"))
          desktop-modes-not-to-save
          '(emacs-lisp-mode image-mode tags-table-mode))

  (desktop-save-mode t))
(defun prune-file-name-history ()
  "Prune non-existing files from `file-name-history'."
  (interactive)
  (let ((old (length file-name-history)) ok)
    (dolist (name file-name-history)
      (when (file-exists-p name)
        (push name ok)))
    (setq file-name-history (nreverse ok))
    (message "Pruned `file-name-history' from `%S' to `%S' files"
             old (length file-name-history))))

Completion

Completion YouTube links are Using Vertico, Marginalia, Consult, and Embark and Emacs Completion Explained. This setup installs three external Emacs packages:

  1. Vertico (info) for a performant and minimalistic vertical completion UI.
  2. Company (info) to complete anything anywhere in Emacs.
  3. Consult (info) for useful search and navigation commands.

Vertico (info)

See the Vertico extensions for Emacs video for an overview. Listing lst:setup-vertico-mode sets up vertico-mode without using the vertico-directory extension (less is better!). Listing lst:prune-file-name-history allows to prune non-existing files from the file name history.

NOTE: Play with vertico-buffer-mode, vertico-flat-mode, vertico-grid-mode, vertico-indexed-mode, vertico-mouse-mode (not easy to use with my trackpad), vertico-reverse-mode.

(when (ensure-package-installation 'vertico)
  ;; GAV: Builtin `fido-mode' may fix `find-file' completion when
  ;; using `vertico-directory' functions which confuse `find-file'.
  ;; GAV: Builtin `fido-mode' has more down than up sides: ChatGPT
  ;; bullshits on enhancing `vertico-mode' completion.
  (vertico-mode +1)
  ;; GAV: Darwin shadows the "C-<up>" and "C-<down>" key bindings.
  (keymap-set vertico-map "C-(" #'vertico-previous-group)
  (keymap-set vertico-map "C-)" #'vertical-next-group))
command remap keys
vertico-exit exit-minibuffer

(eval

vertico-first beginning-of-buffer

(eval

vertico-first minibuffer-beginning-of-buffer

(eval

vertico-insert

(eval

vertico-last end-of-buffer

(eval

vertico-next-group forward-paragraph

(eval

vertico-next next-line-or-history-element

(eval

vertico-next next-line

(eval

vertico-previous-group backward-paragraph
vertico-previous previous-line-or-history-element

(eval

vertico-previous previous-line

(eval

vertico-save kill-ring-save

(eval

vertico-scroll-down scroll-down-command

(eval

vertico-scroll-up scroll-up-command

(eval

Company: a modular in-buffer completion framework for Emacs

Company (info) completes anything anywhere in Emacs. Listing lst:setup-company sets it up after ensuring its installation.

(when (ensure-package-installation 'company)
  ;; GAV: `desktop' may enable `company-mode' for files it remembers.
  (add-hook 'emacs-lisp-mode-hook #'company-mode)
  (add-hook 'lisp-interaction-mode-hook #'company-mode)
  (add-hook 'lisp-mode-hook #'company-mode)
  (add-hook 'python-mode-hook #'company-mode) ; works with `eglot'.
  (add-hook 'ielm-mode-hook #'company-mode)
  (add-hook 'sly-mrepl-mode-hook #'company-mode))

Consult (info)

Consult (info) provides practical commands based on the Emacs minibuffer completion function completing-read. Listing bind consult commands binds consult commands to different key maps. Consult usage tips are:

  1. Add a buffer position to the mark ring by means of

    (eval

    and jump to one of the marks in the ring by means of

    (eval

    lt-mark)}}} or

    (eval

    . See Emacs: Mark Ring.
  2. is an alternative to .
(when (ensure-package-installation 'consult)
  ;; C-c bindings (current-global-map)
  (keymap-global-set "C-c h" #'consult-history)
  (keymap-global-set "C-c m" #'consult-mode-command)
  ;; C-x bindings (ctl-x-map)
  (keymap-set ctl-x-map "M-:" #'consult-complex-command)
  (keymap-set ctl-x-map "b" #'consult-buffer)
  (keymap-set ctl-x-4-map "b" #'consult-buffer-other-window)
  (keymap-set ctl-x-5-map "b" #'consult-buffer-other-frame)
  (keymap-set ctl-x-r-map "x" #'consult-register)
  (keymap-set ctl-x-r-map "b" #'consult-bookmark)
  ;; M-g bindings (goto-map)
  (keymap-set goto-map "g" #'consult-goto-line)
  (keymap-set goto-map "M-g" #'consult-goto-line)
  (keymap-set goto-map "o" #'consult-outline)
  (keymap-set goto-map "m" #'consult-mark)
  (keymap-set goto-map "k" #'consult-global-mark)
  (keymap-set goto-map "i" #'consult-imenu)
  (keymap-set goto-map "e" #'consult-compile-error)
  (with-eval-after-load 'org
    (keymap-set org-mode-map "C-c C-h" #'consult-org-heading))
  ;; M-s bindings (search-map)
  (keymap-set search-map "g" #'consult-git-grep)
  (keymap-set search-map "f" #'consult-find)
  (keymap-set search-map "k" #'consult-keep-lines)
  (keymap-set search-map "l" #'consult-line)
  (keymap-set search-map "m" #'consult-multi-occur)
  (keymap-set search-map "u" #'consult-focus-lines)
  ;; Other bindings (current-global-map)
  (keymap-global-set "M-y" #'consult-yank-pop))
command key map keys
consult-bookmark ctl-x-r-keymap

(eval

consult-buffer-other-frame ctl-x-5-keymap

(eval

consult-buffer-other-window ctl-x-4-keymap

(eval

consult-buffer ctl-x-keymap

(eval

consult-compile-error goto-map

(eval

consult-complex-command ctl-x-keymap

(eval

consult-find search-map

(eval

consult-focus-lines search-map

(eval

consult-git-grep search-map

(eval

consult-global-mark goto-map

(eval

consult-goto-line goto-map

(eval

consult-goto-line goto-map

(eval

consult-history global-map

(eval

consult-imenu goto-map

(eval

consult-keep-lines search-map

(eval

consult-line search-map

(eval

consult-mark goto-map

(eval

consult-mode-command global-map

(eval

consult-multi-occur search-map

(eval

consult-org-heading org-mode-map

(eval

consult-outline goto-map

(eval

consult-register ctl-x-r-keymap

(eval

consult-yank-pop global-map

(eval

elfeed global-map

(eval

iedit-mode global-map

(eval

narrow-or-widen-dwim ctl-x-keymap

(eval

org-agenda global-map

(eval

org-capture global-map

(eval

org-cite-insert org-mode-map

(eval

org-goto org-goto

(eval

org-insert-link global-map

(eval

org-narrow-to-table ctl-x-keymap

(eval

org-store-link global-map

(eval

Search and replace (info)

Regexp Replace (info)

Executing

(eval

prompts for a regular expression REGEXP and a substitution NEWSTRING to replace REGEXP with NEWSTRING. The tools in section Narrowing allow to limit the scope of {{{kbd(M-x replace-regexp)}}}. A list pointing to relevant documentation is:

  1. Regexp Replace (info) explains how the substitution handles references to regular expression groups and lisp expressions.
  2. Syntax of Regular Expressions (info) describes regular expression features for users.
  3. Emacs Lisp Regular Expressions (info) describes regular expression features for programmers.
  4. Emacs Wiki has an article on replacing regexp with lisp expressions.

Table tab:replace-regexp-tranform tabulates how to perform example tasks by means of

(eval

using (regular-expression transform) pairs.

task regular expression substitution
move dot two digits forward 0\.\([[:digit:]][[:digit:]]\) 0\1.
move dot two digits forward 0\.\([0-9][0-9]\) 0\1.
renumber grep/occur matches \(.+:\) \,(1+ \#)._

Incremental Search (info) and Other Search and Loop Commands (info)

;; https://www.masteringemacs.org/article/searching-buffers-occur-mode
(defun get-buffers-matching-mode (mode)
  "Return a list of buffers whose major-mode is equal to MODE."
  (let ((buffer-mode-matches '()))
    (dolist (buf (buffer-list))
      (with-current-buffer buf
        (when (eq mode major-mode)
          (push buf buffer-mode-matches))))
    buffer-mode-matches))

(defun multi-occur-in-this-mode ()
  "Show all lines matching REGEXP in buffers with this major mode."
  (interactive)
  (multi-occur
   (get-buffers-matching-mode major-mode)
   (car (occur-read-primary-args))))
;; https://blog.chmouel.com/posts/emacs-isearch/
(when (package-installed-p 'consult)
  (defun consult-line-from-isearch ()
    "Invoke `consult-line' from isearch."
    (interactive)
    (let ((query (if isearch-regexp
                     isearch-string
                   (regexp-quote isearch-string))))
      (isearch-update-ring isearch-string isearch-regexp)
      (let (search-nonincremental-instead)
        (ignore-errors (isearch-done t t)))
      (consult-line query)))

  (keymap-set isearch-mode-map "C-l" #'consult-line-from-isearch))
;; https://blog.chmouel.com/posts/emacs-isearch/
(defun occur-from-isearch ()
  "Invoke `occur' from isearch."
  (interactive)
  (let ((query (if isearch-regexp
                   isearch-string
                 (regexp-quote isearch-string))))
    (isearch-update-ring isearch-string isearch-regexp)
    (let (search-nonincremental-instead)
      (ignore-errors (isearch-done t t)))
    (occur query)))

(defun project-find-regexp-from-isearch ()
  "Invoke `project-find-regexp' from isearch."
  (interactive)
  (let ((query (if isearch-regexp
                   isearch-string
                 (regexp-quote isearch-string))))
    (isearch-update-ring isearch-string isearch-regexp)
    (let (search-nonincremental-instead)
      (ignore-errors (isearch-done t t)))
    (project-find-regexp query)))

(keymap-set isearch-mode-map "C-o" #'occur-from-isearch)
(keymap-set isearch-mode-map "C-f" #'project-find-regexp-from-isearch)

Ugrep

Ugrep is an ultra fast grep with interactive query UI and fuzzy search. The pages Using ugrep within Emacs and Ugrep in Emacs show how to make Emacs use it with lgrep and xref. Listing set grep and xref options shows my implementation of the suggestions on those pages. To do:

  1. None of the commands listed in Searching with Grep under Emacs (info) works on my system with the exception of lgrep when using ugrep.
  2. None of the grep, agrep, egrep, fgrep, and glimpse builtins listed in Eshell builtins (info) works.
(when (executable-find "ugrep")
  (with-eval-after-load 'grep
    (setopt grep-template (string-join '("ugrep"
                                         "--color=always"
                                         "--decompress"
                                         "--ignore-binary"
                                         "--ignore-case"
                                         "--include=\"<F>\""
                                         "--line-number"
                                         "--null"
                                         "--recursive"
                                         "--regexp=<R>")
                                       " ")
            grep-command "ugrep --color=always --null -nH -e "
            grep-use-null-device nil))
  (with-eval-after-load 'xref
    (setopt
     xref-search-program 'ugrep
     xref-search-program-alist
     `((grep . "xargs -0 grep <C> --null -snHE -e <R>")
       (ripgrep . ,(concat "xargs -0 rg <C> --null -nH"
                           " --no-heading --no-messages -g '!*/' -e <R>"))
       (ugrep . "xargs -0 ugrep <C> --null -ns -e <R>")))))

XR - Emacs regexp parser and analyzer

XR converts Emacs regular expressions to the structured rx form, thus being an inverse of rx. It can also find mistakes and questionable constructs inside regexp strings.

(ensure-package-installation 'xr)

Version Control (info)

Ediff (info)

Video links to complete the ediff (info) manual are:

  1. Emergency Emacs.
  2. Use smerge and ediff to resolve file conflicts.

Listing lst:setup-ediff configures ediff to display all its buffers in a single frame and to make all text visible prior to ediffing Org buffers.

(with-eval-after-load 'ediff-wind
  (when (fboundp 'ediff-setup-windows-plain)
    (setopt ediff-merge-split-window-function #'split-window-horizontally
            ediff-split-window-function #'split-window-horizontally
            ediff-window-setup-function #'ediff-setup-windows-plain)))
(with-eval-after-load 'org
  ;; https://github.com/oantolin/emacs-config#readme
  (add-hook 'org-mode-hook
            (defun ediff-with-org-show-all ()
              "Expand all headings prior to ediffing org buffers."
              (add-hook 'ediff-prepare-buffer-hook #'org-fold-show-all nil t))))

Git

Magit (info)

Magit (info) is an Emacs interface to the version control system Git and the snippet below ensures its installation. Magit usage tips are:

  1. Most upvoted Magit questions on Stack Overflow.
  2. Abort an active rebase by pressing

    (eval

    .

I still have to understand Magit pushing (info) and to figure out how to use src_emacs-lisp{(magit-push-current-to-pushremote)} or src_emacs-lisp{(magit-push-current-to-upstream)}.

(ensure-package-installation 'magit)

Github quick setup

echo "# oglot" >> README.md
git init
git add README.md
git commit -m "first commit"
git branch -M main
git remote add origin git@github.com:gav451/oglot.git
git push -u origin main
git remote add origin git@github.com:gav451/oglot.git
git branch -M main
git push -u origin main

List all Git branches

git -C ~/VCS/oglot branch -a
* main
  remotes/origin/main

Reading

Reading DjVu files

Document View (info) allows to read DjVu files on condition that it can use the command line DjVu decoder ddjvu from the DjVuLibre utilities as shown by the code of src_emacs-lispvu->tiff-converter-ddjvu)}. Use to enlarge or shrink what doc-view-mode displays and use to scroll down or up through when pages are larger than the doc-view-mode window.

Reading EPUB files

The package nov.el provides a major mode for reading EPUB files in Emacs. Listing lst:configure-nov configures nov.el.

(when (ensure-package-installation 'nov)
  (add-to-list 'auto-mode-alist `(,(rx ".epub" eos) . nov-mode)))

Reading PDF files

The pdf-tools package exploits the poppler library to render and to let you annotate PDF files. It also exploits the SyncTeX library to link anchors in PDF files produced with LaTeX to the original LaTeX sources. In order to use pdf-tools, you have to type

(eval

after installation of pdf-tools from MELPA or after each update of poppler to build or rebuild the epdfinfo executable that serves the PDF files to Emacs. Table tab:pdf-view-mode-commands-and-bindings lists important local map key bindings in pdf-view-mode. Note: image-save saves the page under point to a png file.

(when (ensure-package-installation 'pdf-tools)
  ;; Use `pdf-loader-install' for faster startup than with `pdf-tools-install'.
  (pdf-loader-install)

  (with-eval-after-load 'pdf-view
    (setopt pdf-view-display-size 'fit-page
            pdf-view-use-scaling (eq system-type 'darwin))))
command key binding
image-save

(eval

pdf-outline

(eval

pdf-view-fit-height-to-window

(eval

pdf-view-fit-width-to-window

(eval

Reading news and outline files

Listing lst:maybe-read-only implements heuristics to enable read-only-mode for all emacs-news-mode and selected outline-mode files.

(defun maybe-read-only-in-outline-mode ()
  "Check whether to open a file in `outline-mode' with `read-only-mode' enabled."
  ;; GAV: Should be a toplevel function to call `message' here.
  (let ((fnsd (buffer-file-name)))
    ;; GAV: `fnsd' is nil during Org export to LaTeX.
    (when fnsd
      (setq fnsd (file-name-nondirectory fnsd))
      ;; GAV: Handle files that do not open in `emacs-news-mode' from
      ;; the EMACS and ORG git repositories (`cl-member' works too).
      (when (or (string= fnsd "EGLOT-NEWS")
                (string= fnsd "ORG-NEWS")
                (string= fnsd "PROBLEMS")
                (string= fnsd "org.org")
                (string= fnsd "org-guide.org")
                (string= fnsd "org-manual.org"))
        (message "Visit `%s' in `read-only-mode'" fnsd)
        (read-only-mode +1)))))

(add-hook 'outline-mode-hook #'maybe-read-only-in-outline-mode)
(add-hook 'emacs-news-mode-hook (lambda () (read-only-mode +1)))

Writing

Writing LaTeX files

Loading tex.el immediately instead of lazily ensures proper initialization of AUCTeX. In particular, the TeX-master safe local variable in the tex.el elisp library file has no autoload cookie. Without prior loading of tex.el, Emacs will complain that TeX-master is no safe local variable in case it reads a LaTeX file that sets TeX-master. The list below summarizes the main AUCTeX configuration objectives:

  1. Listing lst:set-tex-options ensures installation of AUCTeX and initializes AUCTeX properly for Lua-TeX or Lua-LaTeX. Listing lst:set-tex-options also enables indenting between square brackets which is a recent feature.
  2. Listing lst:set-latex-related-options configures the Emacs bibtex library to use the LaTeX BibTeX dialect for backwards compatibility, disables font scaling of section titles, and configures LaTeX for a full featured LaTeX-section-command.
(when (ensure-package-installation 'auctex)
  ;; Use `require' to make `TeX-master' a safe local variable.
  (when (require 'tex nil 'noerror)
    (setopt
     TeX-auto-save t
     TeX-engine 'luatex
     TeX-indent-close-delimiters "]"
     TeX-indent-open-delimiters "["
     TeX-parse-self t
     ;; Disable `TeX-electric-math' to prevent collisions with `smartparens'.
     TeX-electric-math nil)))
(with-eval-after-load 'bibtex
  (setopt bibtex-dialect 'BibTeX))

(with-eval-after-load 'font-latex
  (setopt font-latex-fontify-sectioning 1.0))

(with-eval-after-load 'latex
  (setopt LaTeX-electric-left-right-brace t
          LaTeX-section-hook '(LaTeX-section-heading
                               LaTeX-section-title
                               LaTeX-section-toc
                               LaTeX-section-section
                               LaTeX-section-label)))

Writing Markdown files

(when (ensure-package-installation 'markdown-mode) t)

Writing Org (info) files and Org activation (info)

Listing lst:bind-org-commands activates Org (info) by means of global key bindings.

(with-eval-after-load 'emacs
  (global-set-key (kbd "C-c a") #'org-agenda)
  (global-set-key (kbd "C-c c") #'org-capture)
  (global-set-key (kbd "C-c l") #'org-store-link))

Links to Org-mode videos are:

  1. Analyze Your Time with Org Mode Clocktables
  2. Basic Task Management with Org: Checklists, TODOs, and Org-Agenda
  3. Emacs Org-mode: a system for note-taking and project planning
  4. Getting Started With Org Mode
  5. Org Mode Time and Task Tools
  6. Orgmode Workflow 3 (with Python Source Blocks)

Setup Org

I have divided the initial Org-mode setup into eleven listings. Here, follows a list detailing and motivating each listing:

  1. Listing lst:set-org-options handles basic Org-mode options.
  2. Listing lst:org-capture-templates initializes a simple capture template.
  3. Listing lst:setup-org-babel sets ob-core, ob-latex, and ob-lisp options. See working with source code (info) for Org Babel information.
  4. Listing lst:looking-at-org-id enables recent org-id features.
  5. Listing lst:setup-org-mode-map-1 and lst:setup-org-mode-map-2 extend the org-mode-map with useful key-bindings.
  6. Listing lst:setup-org-src facilitates editing source code blocks, and it provides functions to change the indentation of all org-src-mode blocks.
  7. Listing lst:setup-ob-python allows to pretty-print Python session source block values with black instead of pprint. This snippet may break in the future, because it sets org-babel-python--def-format-value which is a constant declared "private" by two dashes in its name!
  8. Listing lst:set-org-export-options selects the non-intrusive expert user interface for export dispatching.
  9. Listing lst:setup-org-for-lualatex-preview allows Org-mode to generate preview png images with Lua-LaTeX and ImageMagick.
  10. Listing lst:set-ox-latex-options-for-lualatex-export configures Org-mode to generate output for Lua-LaTeX.
(with-eval-after-load 'org
  (setopt
   org-babel-load-languages `((calc . t)
                              (dot . ,(fboundp 'grapviz-dot-mode))
                              (emacs-lisp . t)
                              (eshell . t)
                              (fortran . t)
                              (gnuplot . ,(fboundp 'gnuplot-mode))
                              (latex . t)
                              (lilypond . ,(fboundp 'lilypond-mode))
                              (lisp . t)
                              (lua . ,(fboundp 'lua-mode))
                              (org . t)
                              (perl . t)
                              ;; Beware of circular Python dependencies.
                              (python . t)
                              (ruby . ,(fboundp 'ruby-mode))
                              (shell . t))
   org-export-backends '(ascii beamer html latex texinfo)
   org-file-apps '((auto-mode . emacs)
                   (directory . emacs)
                   ("\\.mm\\'" . default)
                   ("\\.x?html?\\'" . default)
                   ("\\.pdf\\'" . emacs))
   org-latex-default-packages-alist '(("" "fontspec" t ("lualatex"))
                                      ("AUTO" "inputenc" t ("pdflatex"))
                                      ("T1" "fontenc" t ("pdflatex"))
                                      ("" "graphicx" t)
                                      ("" "longtable" nil)
                                      ("" "wrapfig" nil)
                                      ("" "rotating" nil)
                                      ("normalem" "ulem" t)
                                      ("" "amsmath" t)
                                      ("" "amssymb" t)
                                      ("" "capt-of" nil)
                                      ("" "hyperref" nil))
   org-modules (list 'ol-bibtex 'ol-doi 'ol-eww 'ol-info
                     'org-id 'org-protocol 'org-tempo)
   org-use-property-inheritance t))
(with-eval-after-load 'org-capture
  (setopt org-capture-templates
          '(("f" "Flat capture database" entry (file "flat.org")
             "* %^{Prompt}t %?
%a"))))
(with-eval-after-load 'ob-core
  (setopt org-confirm-babel-evaluate nil))

(with-eval-after-load 'ob-latex
  (setopt org-babel-latex-preamble
          (lambda (_)
            "\\documentclass[preview]{standalone}\n")))

(with-eval-after-load 'ob-lisp
  (when (package-installed-p 'sly)
    (setopt org-babel-lisp-eval-fn #'sly-eval)))
(with-eval-after-load 'org-id
  (setopt org-id-link-to-org-use-id t
          org-id-track-globally t)
  ;; https://stackoverflow.com/a/16247032 answers
  ;; "How to assign IDs to all entries in a buffer."
  (defun org-id-add-ids-to-headlines-in-buffer ()
    "Add slowly ID properties to all headlines without such an ID property.
Undo this by means of `org-delete-property-globally'."
    (interactive)
    (org-map-entries #'org-id-get-create)))
;; From: "Nicolas Richard" <theonewiththeevillook@yahoo.fr>
;; Date: Fri, 08 Mar 2013 16:23:02 +0100	[thread overview]
;; Message-ID: <87vc913oh5.fsf@yahoo.fr> (raw)
(defun org-electric-dollar ()
  "When called once, insert \\(\\) and leave point in between.
When called twice, replace the previously inserted \\(\\) by one $."
  (interactive)
  (if (and (looking-at "\\\\)") (looking-back "\\\\(" (- (point) 2)))
      (progn (delete-char 2) (delete-char -2) (insert "$"))
    (insert "\\(\\)")
    (backward-char 2)))

(with-eval-after-load 'org
  (setopt org-return-follows-link t)
  (keymap-set org-mode-map "$" #'org-electric-dollar)
  (keymap-set org-mode-map "M-q" #'org-fill-paragraph))
;; Stolen from `org-insert-structure-template'.
;; Note: `org-tempo' does not require `tempo' at all.
(defcustom org-insert-source-block-defaults
  '("calc -n :results silent"
    "emacs-lisp -n :results silent"
    "latex -n"
    "lisp -n :results silent :package :cs325-user"
    "org -n"
    "python -i -n :results silent")
  "Default values for `org-insert-source-block'."
  :group 'org
  :type '(repeat string))

(defun org-insert-source-block ()
  "Insert a source block #+begin_src/SPEC/BODY/#+end_src.
Prompt for the source block SPEC.  With an active region, the
region becomes the block BODY.  Otherwise, insert an empty block."
  (interactive)
  (let* ((spec (completing-read
                "Block spec: " org-insert-source-block-defaults nil 'confirm))
         (region? (use-region-p))
         (region-start (and region? (region-beginning)))
         (region-end (and region? (copy-marker (region-end)))))
    (when region? (goto-char region-start))
    (let ((column (current-indentation)))
      (if (save-excursion (skip-chars-backward " \t") (bolp))
          (forward-line 0)
        (insert "\n"))
      (save-excursion
        (indent-to column)
        (insert (format "#+begin_src %s\n" spec))
        (when region?
          (org-escape-code-in-region (point) region-end)
          (goto-char region-end)
          (skip-chars-backward " \n\t\r")
          (end-of-line))
        (unless (bolp) (insert "\n"))
        (indent-to column)
        (insert "#+end_src")
        (if (looking-at "[ \t]*$") (replace-match "") (insert "\n"))
        (when (and (eobp) (not (bolp))) (insert "\n")))
      (end-of-line))))

(with-eval-after-load 'org
  (keymap-set org-mode-map "C-c C-;" #'org-insert-source-block))
(with-eval-after-load 'org-src
  ;; https://list.orgmode.org/c3cc4ff1fcfb3bc741df89a3f45be30e@posteo.net/
  (setopt org-edit-src-content-indentation 0
          org-src-preserve-indentation nil
          org-src-window-setup 'current-window)

  (defun org-left-shift-block ()
    "Left-shift the block at point using `org-indent-block'."
    (interactive)
    (let ((org-adapt-indentation nil)
          (org-edit-src-content-indentation 0)
          (org-src-preserve-indentation nil))
      (org-indent-block)))

  (defun org-right-shift-block (&optional arg)
    "Right-shift the block at point 4 spaces.
With prefix argument ARG, prompt for the number of spaces."
    (interactive "P")
    (let ((spaces 4)
          (start (org-babel-where-is-src-block-head)))
      (if (not start) (message "Not at block")
        (when arg
          (setq spaces (read-number "Number of spaces: ")))
        ;; The `SUBEXP' of the block body is 5.
        (goto-char (match-end 5))
        (forward-line -1)
        (string-insert-rectangle
         (match-beginning 5) (point) (make-string spaces ?\s)))))

  (defun zero-all-org-src-blocks-indentation ()
    "Remove the indentation of all `org-src-mode' blocks without `-i' switch."
    (interactive)
    (when (derived-mode-p 'org-mode)
      (save-excursion
        (org-babel-map-src-blocks
            nil
          (unless (or (string-match "-i " switches)
                      (string-match (rx (or bos bol) graph) body))
            (when-let ((new (with-temp-buffer
                              (insert body)
                              (org-do-remove-indentation)
                              (buffer-string))))
              (goto-char beg-body)
              (delete-region beg-body end-body)
              (insert new))))))))
(with-eval-after-load 'ob-python
  (setq org-babel-python--def-format-value "\
def __org_babel_python_format_value(result, result_file, result_params):
    with open(result_file, 'w') as f:
        if 'graphics' in result_params:
            result.savefig(result_file)
        elif 'pp' in result_params:
            import black
            f.write(black.format_str(repr(result), mode=black.Mode()))
        elif 'list' in result_params and isinstance(result, dict):
            f.write(str(['{} :: {}'.format(k, v) for k, v in result.items()]))
        else:
            if not set(result_params).intersection(\
['scalar', 'verbatim', 'raw']):
                def dict2table(res):
                    if isinstance(res, dict):
                        return [(k, dict2table(v)) for k, v in res.items()]
                    elif isinstance(res, list) or isinstance(res, tuple):
                        return [dict2table(x) for x in res]
                    else:
                        return res
                if 'table' in result_params:
                    result = dict2table(result)
                try:
                    import pandas
                except ImportError:
                    pass
                else:
                    if isinstance(result, pandas.DataFrame) and 'table' in result_params:
                        result = [[result.index.name or ''] + list(result.columns)] + \
[None] + [[i] + list(row) for i, row in result.iterrows()]
                    elif isinstance(result, pandas.Series) and 'table' in result_params:
                        result = list(result.items())
                try:
                    import numpy
                except ImportError:
                    pass
                else:
                    if isinstance(result, numpy.ndarray):
                        if 'table' in result_params:
                            result = result.tolist()
                        else:
                            result = repr(result)
            f.write(str(result))"))
(with-eval-after-load 'ox
  (setopt org-export-dispatch-use-expert-ui t))
(with-eval-after-load 'org
  ;; https://list.orgmode.org/87o84fd4oi.fsf@posteo.net/
  (add-to-list
   'org-preview-latex-process-alist
   '(luamagick
     :programs ("lualatex" "convert")
     :description "pdf > png"
     :message "you need to install lualatex and imagemagick."
     :image-input-type "pdf"
     :image-output-type "png"
     :image-size-adjust (1.0 . 1.0)
     :post-clean nil
     :latex-header nil
     :latex-compiler
     ("lualatex -interaction nonstopmode -output-directory %o %f")
     :image-converter
     ("convert -density %D -trim -antialias %f -quality 100 %O")))
  (setopt org-preview-latex-default-process 'luamagick))
(when (require 'ox-latex nil 'noerror) ;; To use the safe file local variables!
  (setopt
   org-latex-compiler "lualatex"
   org-latex-compiler-file-string
   "%% -*- LaTeX -*-\n%% Intended LaTeX compiler: %s\n"
   org-latex-hyperref-template "\\hypersetup{
  pdfauthor={%a},
  pdftitle={%t},
  pdfkeywords={%k},
  pdfsubject={%d},
  pdfcreator={%c},
  pdflang={%L},
  citecolor=blue,
  colorlinks=true,
  filecolor=blue,
  hyperfootnotes=false,
  linkcolor=blue,
  unicode=true,
  urlcolor=blue,
}\n"
   org-latex-minted-langs '((cc "c++")
                            (conf "text")
                            (cperl "perl")
                            (diff "diff")
                            (shell-script "bash")
                            (json "json")
                            (org "text")
                            (toml "toml"))
   org-latex-minted-options '(("bgcolor" "LightGoldenrodYellow"))
   org-latex-logfiles-extensions
   (cl-union '("lof" "lot") org-latex-logfiles-extensions :test #'equal)
   ;; https://tecosaur.github.io/emacs-config/#compiling
   org-latex-pdf-process (list (concat "latexmk -f -pdf -%latex"
                                       " -interaction=nonstopmode"
                                       " -shell-escape -outdir=%o %f"))
   org-latex-prefer-user-labels t)

  (put 'org-latex-toc-command 'safe-local-variable 'stringp))

Buffer properties

(with-eval-after-load 'org
  (defun org-get-buffer-properties ()
    "Get all properties in buffer."
    (org-element-map (org-element-parse-buffer) 'keyword
      (lambda (el)
        (and ;; Use (upcase "property"), else it fails.
         (string= (org-element-property :key el) "PROPERTY")
         (let* ((strings (split-string (org-element-property :value el)))
                (value (string-join (cdr strings) " "))
                (name (car strings)))
           (cons name value)))))))

Org introspection

  1. Listing lst:org-babel-active-languages produces a list containing language names that have org-babel-execute:LANGUAGE functions.
  2. Listing lst:get-in-buffer-settings and lst:get-in-buffer-settings-result show how to access In-buffer Settings (info).
(defun org-babel-active-languages ()
  (let (result)
    (mapatoms
     (lambda (x)
       (when (and
              (string-prefix-p "org-babel-execute:" (symbol-name x))
              ;; Get rid of all sub-modes in `ob-shell':
              (not (eq 'org-babel-shell-initialize (get x 'definition-name)))
              ;; Get rid of aliases:
              (not (eq 'symbol (type-of (symbol-function x)))))
         (when (symbol-file x)
           (push (string-remove-prefix "org-babel-execute:" (symbol-name x))
                 result)))))
    result))

(mapcar #'list (sort (org-babel-active-languages)))
(("calc") ("emacs-lisp") ("eshell") ("fortran") ("latex") ("lisp")
 ("org") ("perl") ("python") ("ruby") ("shell"))
Active Org Babel languages.
(org-collect-keywords '("title" "author" "latex_class"))
(("TITLE" "Emacs setup for use with LaTeX, Lisp, Org, and Python")
 ("AUTHOR" "Gerard Vermeulen") ("LATEX_CLASS" "article"))

Citar: citing bibliography with Org Mode

Citar is a completing-read front-end to browse and act on BibTeX, BibLaTeX, as well as CSL JSON bibliographic data with LaTeX, markdown, and org-cite editing support. Citar in combination with Vertico provides quick filtering and selecting of bibliographic entries from the minibuffer.

The article Citations in org-mode: Org-cite and Citar tries to walk you from understanding the general context (bibliography producer, text processor, text to product converter) to an Emacs setup. Listing lst:set-oc+citar-options shows the oc and citar setup with binding of

(eval

to src_emacs-lisp{(call-interactively 'org-cite-insert)} in org-mode-map.

(when (ensure-package-installation 'citar)
  (with-eval-after-load 'oc
    (setopt org-cite-export-processors '((latex biblatex)
                                         (t csl))
            org-cite-global-bibliography '("~/VCS/research/refs.bib"))
    (if (not (require 'citar nil 'noerror))
        (user-error "Fails to require `citar' and to setup `citar' and `oc'")
      ;; Synchronize the two involved bibliographies.
      (setopt citar-bibliography org-cite-global-bibliography
              citar-library-file-extensions '("djvu" "pdf")
              ;; Here are the `.djvu' and `.pdf' files.
              citar-library-paths '("~/VCS/research/papers/"))
      (setopt org-cite-activate-processor 'citar
              org-cite-follow-processor 'citar
              org-cite-insert-processor 'citar)
      (with-eval-after-load 'org
        (keymap-set org-mode-map "C-c b" #'org-cite-insert)))))

Ref. [cite:@Schulte.MCSE.2011.41] shows that Org-mode is a simple, plain-text markup language for hierarchical documents allowing intermingling of data, code, and prose. It serves as an example link to show how to use Citar within this setup, provided that citar-bibliography and citar-library-paths point to valid directories and files. In an Org-mode buffer this setup allows to:

  1. Insert one or more Org-mode cite links:

    1. Type .
    2. Choose one or multiple citations from the bibliography:

      1. by navigating to an item and selecting it with

        (eval

      2. by repeatingly navigating to an item and preselecting it with .
  2. View an electronic copy or the DOI of an Org-mode cite link:

    1. Click on the Org-mode cite link.
    2. Navigate to the corresponding resource.
    3. Select it.
  3. The following citar functions may be useful too:

    1. src_emacs-lisp{(call-interactively 'citar-insert-citation)}.
    2. src_emacs-lisp[:results silent]{(call-interactively 'citar-open)}.

Engrave Faces

This package has a front-end that processes fontified buffers and pipes its output into one of three back-ends for export to LaTeX, HTML and text with ANSI escape codes. It is an alternative to minted in case of LaTeX export or to htmlized in case of HTML export.

The listings below work around one issue with spacing in the pdf output resulting from use of the engraved source block back-end during LaTeX export. They increase the quality of the export results of this README.org and the user-friendliness of this facility to a level acceptable for my workflow:

  1. The default Org export configuration for engraving listings produces pdf output with a non-equidistant interline spacing in floating listings (those with captions) and equidistant interline spacing in non-floating tcolorbox environments (those without captions). I have traced the non-equidistant interline spacing back to the breakable option of Code environments inside listing environments. However, tcolorbox requires breakable for listings that do not fit on a page. The filter org-latex-engraved-source-block-filter in listing lst:org-latex-engraved-source-block-filter and the org-latex-engraved-preamble customization in listing lst:smart-latex-engrave-org-source-blocks allow to engrave org-mode source blocks to "floating unbreakable with caption" or "non-floating breakable without caption" LaTeX environments. This is a partial work-around, but part of my workflow.
  2. Listing lst:ox-engraved-emacs-lisp-setup shows how to use this configuration.
(ensure-package-installation 'engrave-faces)

(defun org-latex-engraved-source-block-filter (data _backend _info)
  "Replace \"Code\" with \"Breakable\" in non-floating DATA environments.

Set `org-latex-engraved-preamble' to define a Breakable (non-floating)
environment and an unbreakable Code (floating) environment."
  (unless (string-match "^\\\\DeclareTColorBox\\[\\]{Breakable}"
                        org-latex-engraved-preamble)
    (user-error
     "`org-latex-engraved-preamble' defines no `Breakable' environment"))
  (when (eq org-latex-src-block-backend 'engraved)
    ;; Transform only blocks matching at position 0.  Therefore, do
    ;; not transform blocks that are listing environments.
    (when (string-match "\\`\\\\begin{Code}\n" data)
      (setq data (replace-match "\\begin{Breakable}\n" t 'literal data))
      (if (string-match "^\\\\end{Code}\n" data)
          (setq data (replace-match "\\end{Breakable}\n" t 'literal data))
        (error "Match `^\\\\end{Code}' failure")))))
(defun smart-latex-engrave-org-source-blocks ()
  "Enable smart LaTeX engraving of `org-src-mode' blocks.

Sets backend and preamble locally to support floating unbreakable LaTeX
environments and non-floating breakable LaTeX environments by means of
`org-latex-engraved-source-block-filter'."
  (interactive)
  (when (require 'ox-latex nil 'noerror)
    (setq-local org-latex-src-block-backend 'engraved
                org-latex-engraved-preamble "\\usepackage{fvextra}
[FVEXTRA-SETUP]

% Make code and line numbers normalsize. Make line numbers grey.
\\renewcommand\\theFancyVerbLine{
  \\normalsize\\color{black!40!white}\\arabic{FancyVerbLine}}
% Do not rely on an eventual call to `engrave-faces-latex-gen-preamble'.
\\usepackage{xcolor}
\\providecolor{EfD}{HTML}{f7f7f7}
\\providecolor{EFD}{HTML}{28292e}
% To use \\DeclareTColorBox from the tcolorbox package:
\\usepackage[breakable,xparse]{tcolorbox}
% Define a Breakable environment to fontify code outside floats.
\\DeclareTColorBox[]{Breakable}{o}{
  colback=EfD, colframe=EFD, colupper=EFD,
  fontupper=\\normalsize\\setlength{\\fboxsep}{0pt},
  IfNoValueTF={#1}{
    boxsep=1pt, arc=1pt, outer arc=1pt, boxrule=0.5pt,
  }{
    boxsep=1pt, arc=0pt, outer arc=0pt, boxrule=0pt, leftrule=1pt
  },
  left=2pt, right=2pt, top=1pt, bottom=1pt, breakable
}
% Define an unbreakable Code environment to fontify code inside floats.
\\DeclareTColorBox[]{Code}{o}{
  colback=EfD, colframe=EFD, colupper=EFD,
  fontupper=\\normalsize\\setlength{\\fboxsep}{0pt},
  IfNoValueTF={#1}{
    boxsep=1pt, arc=1pt, outer arc=1pt, boxrule=0.5pt
  }{
    boxsep=1pt, arc=0pt, outer arc=0pt, boxrule=0pt, leftrule=1pt
  },
  left=2pt, right=2pt, top=1pt, bottom=1pt
}

[LISTINGS-SETUP]")))
(with-eval-after-load 'ox-latex
  (make-local-variable 'org-export-filter-src-block-functions)
  (add-to-list 'org-export-filter-src-block-functions
               'org-latex-engraved-source-block-filter)
  (when (fboundp 'smart-latex-engrave-org-source-blocks)
    (smart-latex-engrave-org-source-blocks)))

Translate capital keywords (old) to lower case (new)

(with-eval-after-load 'emacs
  ;; https://tecosaur.github.io/emacs-config/#translate-capital-keywords
  (defun org-syntax-convert-keyword-case-to-lower ()
    "Convert all #+KEYWORDS to #+keywords."
    (interactive)
    (when (derived-mode-p 'org-mode)
      (save-excursion
        (goto-char (point-min))
        (let ((count 0)
              (case-fold-search nil))
          (while (re-search-forward "^[ \t]*#\\+[A-Z_]+" nil t)
            (unless (s-matches-p "RESULTS" (match-string 0))
              (replace-match (downcase (match-string 0)) t)
              (setq count (1+ count))))
          (message "Replaced %d keywords" count))))))

Evaluate specific source blocks at load-time

How to do load time source block evaluation

(with-eval-after-load 'emacs
  (defun org-eval-named-blocks-with-infix (infix)
    "Evaluate all source blocks having INFIX in their name."
    (when (eq major-mode 'org-mode)
      (let ((blocks
             (org-element-map
                 (org-element-parse-buffer 'greater-element nil) 'src-block
               (lambda (block)
                 (when-let ((name (org-element-property :name block)))
                   (when (string-match-p infix name) block))))))
        (dolist (block blocks)
          (goto-char (org-element-property :begin block))
          (org-babel-execute-src-block)))))

  (defun org-eval-emacs-lisp-setup-blocks ()
    "Evaluate all source blocks having \"emacs-lisp-setup\" in their name."
    (interactive)
    (org-eval-named-blocks-with-infix "emacs-lisp-setup"))

  (defun org-eval-python-setup-blocks ()
    "Evaluate all source blocks having \"python-setup\" in their name."
    (interactive)
    (org-eval-named-blocks-with-infix "python-setup"))

  ;; Emacs looks for "Local variables:" after the last "form-feed".
  (add-to-list 'safe-local-eval-forms '(org-eval-emacs-lisp-setup-blocks))
  (add-to-list 'safe-local-eval-forms '(org-eval-python-setup-blocks)))

Org-mode macro utilities

Listing lst:by-backend-kbd-org-macro defines the Emacs Lisp utilities to define the Org mode kbd macro in listing lst:source-file-export-keyword-settings.

(with-eval-after-load 'emacs
  (when (ensure-package-installation 'htmlize)
    (autoload 'htmlize-protect-string "htmlize" nil t))

  ;; https://orgmode.org/worg/org-contrib/babel/languages/ob-doc-LaTeX.html
  (defmacro by-backend (&rest body)
    "Help for org-export backend dependent execution."
    `(cl-case ',(bound-and-true-p org-export-current-backend) ,@body))

  (defun by-backend-kbd-org-macro (keys)
    "Help for an org-export backend dependent \"#+macro: kbd\"."
    (by-backend
     (ascii (format "`kbd(%s)'" keys))
     (html (format "@@html:<kbd>%s</kbd>@@" (htmlize-protect-string keys)))
     (latex (format "@@latex:\\colorbox{PowderBlue}{\\texttt{%s}}@@" keys)))))

Handle Python exceptions in ob-python

Python exceptions are never visible while executing Python org-src-mode blocks. Redirecting Python exceptions is a way to make them visible. Listing lst:redirect-python-exceptions redirects Python exceptions to the file stderr.arm, listing lst:open-python-exceptions opens the file stderr.arm in auto-revert-mode, and listing lst:demo-python-exceptions demonstrates the redirection of Python exceptions to the file stderr.arm.

import sys

file = open("stderr.arm", "w")
sys.stderr = file
(find-file "stderr.arm")
(auto-revert-mode)
variable

Testing Org

Listing lst:batch-org-testing shows the recommended way: use make test in a clean Org git repository. In addition, on my weird Darwin system, I do ln -sf VCS/org-mode/lisp and ln -sf VCS/org-mode/testing to fix the command make repro.

make test > out.txt 2>&1

LaTex preamble editing using Noweb (info)

Listing lst:source-file-export-keyword-settings shows the preamble lines of this /gav451/emacs.d/src/commit/93c59c7416e62b920fa664e1a43a692b71dc4938/README.org file. It lists the export keyword settings, the definition of the Org mode kbd macro and the source block that generates part of the LaTeX preamble. Listing lst:latex-header-1, lst:latex-header-2, lst:latex-header-3, lst:latex-header-4, and lst:latex-header-5 show the LaTeX source blocks that exporting adds to the LaTeX preamble.

Note: Noweb (info) handles all LaTeX preamble listings in this section. For this idea and more, see: LaTeX header blocks or managing lots of LaTeX headers.

<<latex-header-1>>
<<latex-header-2>>
<<latex-header-3>>
<<latex-header-4>>
<<latex-header-5>>

HTML export (info)

The code label and link feature in Literal Examples (info) does not integrate with LaTeX export.

(with-eval-after-load 'ox-html
  ;; (Info-find-node "Org" "Literal Examples")
  (setopt org-html-head-include-scripts t))

Advanced LaTeX export settings

Listing lst:ox-latex-emacs-lisp-setup initializes the buffer local variables org-latex-title-command, org-latex-toc-command, and org-latex-subtitle-format. Read the documentation strings to understand how those variables control exporting from Org-mode to LaTeX.

(when (require 'ox-latex nil 'noerror)
  ;; https://emacs.stackexchange.com/questions/47347/
  ;; customizing-org-latex-title-command-to-edit-title-page
  ;; https://tex.stackexchange.com/questions/506102/
  ;; adding-header-and-footer-to-custom-titlepage
  (setq-local org-latex-title-command "\\begin{titlepage}
  %% https://emacs.stackexchange.com/questions/47347/
  %% customizing-org-latex-title-command-to-edit-title-page
  %% https://tex.stackexchange.com/questions/506102/
  %% adding-header-and-footer-to-custom-titlepage
  \\thispagestyle{titlepage}
  \\begin{center}
    %% Title
    \\begin{Huge}
      {\\bf %t} \\\\
      \\vspace{1em}
    \\end{Huge}
    %% Author
    \\begin{Large}
      {\\bf %a} \\\\
      \\vspace{1em}
    \\end{Large}
  \\end{center}
\\end{titlepage}")
  (setq-local org-latex-toc-command "
\\tableofcontents\\label{toc}
\\listoflistings
\\listoftables
\\newpage
")
  (setq-local org-latex-subtitle-format ""))

Org Syntax

Two tools to grok how Org mode parsing works are the Org Syntax specification and the Org mode parser tutorial. The Org element parsing API boils down to three functions:

  1. The function org-element-parse-buffer implements a fully recursive buffer parser that returns an abstract syntax tree.
  2. The functions org-element-at-point and org-element-context return information on the document structure around point either at the element level or at the object level in case of org-element-context.

Listing lst:grok-org-element-tree improves the Org mode parser tutorial by defining interactive wrappers that pretty print the results of those non-interactive org-element functions to an Emacs-lisp buffer.

(with-eval-after-load 'org-element
  (defconst grok-org-output
    "*Grok Org Element Output*"
    "Grok Org output buffer name.")

  (defun grok-org-element-at-point ()
    "Call `org-element-at-point' interactively and pretty-print."
    (interactive)
    (pp-display-expression
     (org-element-at-point) grok-org-output))

  (defun grok-org-element-context ()
    "Call `org-element-context' interactively and pretty-print."
    (interactive)
    (pp-display-expression
     (org-element-context) grok-org-output))

  (defun grok-org-element-parse-buffer ()
    "Call `org-element-parse-buffer' interactively and pretty-print."
    (interactive)
    (let ((what (completing-read
                 "granularity: "
                 '(headline element greater-element object)
                 nil 'require-match)))
      (pp-display-expression
       (org-element-parse-buffer what) grok-org-output)))

  (defun grok-org-element-parse-whole-buffer ()
    "Like `grok-org-element-parse-buffer' from point 1 and sans granularity."
    (interactive)
    (org-with-point-at 1
      (pp-display-expression (org-element-parse-buffer) grok-org-output)))

  (defun grok-org-heading-components ()
    "Call `org-heading-components' interactively and pretty-print."
    (interactive)
    (pp-display-expression
     (org-heading-components) grok-org-output))

  (defun grok-org-element-lineage ()
    "Call `org-element-lineage' interactively and pretty-print."
    (interactive)
    (org-load-modules-maybe)
    (pp-display-expression
     (org-element-lineage (org-element-context) nil t) grok-org-output)))

Grammar, spelling, and style tools

Abbrevs (info)

Mickey Peterson has posted Correcting Typos and Misspellings with Abbrev showing how to use Keyboard Macros (info) to exploit Wikipedia's list of common misspellings for machines. Listing lst:misspellings-abbrev defines his keyboard macro under the name misspellings-abrev. I have used those directions to define src_emacs-lisp[:results silent]{(describe-variable 'global-abbrev-table)} and to store it in src_emacs-lisp[:results none]{(describe-variable 'abbrev-file-name)} to manage its changes with git. I can change the abbreviation definitions in this file by means of:

  1. Execute src_emacs-lisp[:results silent]{(edit-abbrevs)} to alter abbreviation definitions by editing an *Abbrevs* buffer.
  2. Add, edit, or remove definitions of the form "source" 1 "target" under the global or a mode-specific table.
  3. Execute src_emacs-lisp{(abbrev-edit-save-buffer)} to save all user abbreviation definitions in the current buffer.
(with-eval-after-load 'emacs
  (defun browse-common-misspellings ()
    "Open the Wikipedia page of common misspellings for machines in EWW."
    (interactive)
    (eww (concat "https://en.wikipedia.org/wiki/Wikipedia"
                 ":Lists_of_common_misspellings/For_machines")))

  (fset 'misspellings-abbrev
        (kmacro-lambda-form
         [?\C-s ?- ?> return backspace backspace ?\C-k ?\C-x ?a ?i ?g ?\C-y return]
         0 "%d"))

  (setq-default abbrev-mode t))

TODO DICT.org local server fails on Darwin

Evaluating src_emacs-lisp{(dictionary)} connects to a local or remote dictd server. The following links explain how to configure and use dictd:

  1. Looking up words with DICT
  2. RFC 2229: A Dictionary Server Protocol
  3. Wordsmithing in Emacs

I am using the following Debian Bullseye dictionaries on Darwin and Gentoo:

  1. dict-devil_1.0-13.1_all.deb
  2. dict-foldoc_20201018-1_all.deb
  3. dict-gcide_0.48.5+nmu1_all.deb
  4. dict-jargon_4.4.7-3.1_all.deb
  5. dict-vera_1.24-1_all.deb
  6. dict-wn_3.0-36_all.deb
# https://jpmens.net/2020/03/08/looking-up-words-with-dict/
server 127.0.0.1 {
   port 2628
}
# Local Variables:
# mode: conf-unix
# End:
cat > ~/.dictd.conf <<EOF
# https://www.masteringemacs.org/article/wordsmithing-in-emacs
database devil {
   data "${HOME}/.local/share/dictd/devil.dict.dz"
   index "${HOME}/.local/share/dictd/devil.index"
}
database foldoc {
   data "${HOME}/.local/share/dictd/foldoc.dict.dz"
   index "${HOME}/.local/share/dictd/foldoc.index"
}
database gcide {
   data "${HOME}/.local/share/dictd/gcide.dict.dz"
   index "${HOME}/.local/share/dictd/gcide.index"
}
database jargon {
   data "${HOME}/.local/share/dictd/jargon.dict.dz"
   index "${HOME}/.local/share/dictd/jargon.index"
}
database vera {
   data "${HOME}/.local/share/dictd/vera.dict.dz"
   index "${HOME}/.local/share/dictd/vera.index"
}
database wn {
   data "${HOME}/.local/share/dictd/wn.dict.dz"
   index "${HOME}/.local/share/dictd/wn.index"
}
EOF
# https://jpmens.net/2020/03/08/looking-up-words-with-dict/
/usr/local/sbin/dictd \
    --config .dictd.conf \
    --verbose \
    --logfile .dictd.log \
    -d nodetach

Writegood mode

Writegood mode is a minor mode to aid in finding common writing problems. The post Matt Might's "My Ph.D. advisor rewrote himself in bash" scripts inspired this mode. Listing lst:configure-writegood-mode configures writegood mode and enables writegood-mode for text-mode buffers. Therefore, the best way use it for this buffer is by typing

(eval

to export the it to a text-mode buffer where the writegood-mode faces are more visible.

(when (ensure-package-installation 'writegood-mode)
  (add-hook 'after-init-hook
            (defun on-after-change-mode-hook-enable-writegood-mode ()
              (add-hook 'after-change-major-mode-hook
                        (defun enable-writegood-mode ()
                          (when (derived-mode-p '(org-mode text-mode))
                            (writegood-mode +1)))))
            'depth)

  (global-set-key (kbd "C-c g") #'writegood-mode))

Which-function-mode (info)

Listing lst:setup-which-function-mode sets which-function-mode options. The post Emacs: which-function-mode claims that which-function-mode works in org-mode. This is true in case all document headlines are simple, but is not true in case document headlines contain links. The code in listing lst:define-for-which-func-functions has stolen ideas from the following links:

(with-eval-after-load 'which-func
  (setopt which-func-modes '(emacs-lisp-mode org-mode pdf-view-mode)))
;; https://emacs.stackexchange.com/questions/30894/
(declare-function pdf-info-outline "pdf-info" (&optional file-or-buffer))
(declare-function org-element-type-p "org-element-ast" (node types))
(defvar which-func-functions nil)

(defun which-func-org-function ()
  "Return level and title of the current headline.
Return the document title when point is above the first headline."
  (interactive) ;; Keep this function interactive for fixing.
  (when (eq major-mode 'org-mode)
    (let (text)
      ;; Handle point eventually above the first headline.
      (unless (org-at-heading-p)
        (save-excursion
          (org-previous-visible-heading 1)
          (let ((eap (org-element-at-point)))
            (when (org-element-type-p eap 'keyword)
              (setq text (format "0|%s" (org-element-property :value eap)))))))
      ;; Handle point anywhere else.
      (unless text
        (let* ((chain (org-get-outline-path t))
               (count (length chain)))
          (setq text (format "%s|%s" count (nth (1- count) chain)))))
      text)))
(add-to-list 'which-func-functions 'which-func-org-function)

(defun which-func-pdf-view-function ()
  "Return the title of the current headline.
Return \"Front Matter\" when current page is above the first headline."
  (interactive) ;; Keep this function interactive for fixing.
  (when (eq major-mode 'pdf-view-mode)
    (let* ((current-page (pdf-view-current-page))
           (outline (pdf-info-outline (current-buffer)))
           (hl-count (length outline))
           (hl-index 0)
           (hl-page 0)
           (old-title "Front Matter")
           (new-title old-title))
      ;; Return the first headline on a page which is better than nothing.
      ;; I can't do better, since `pdf-view-mode' has no notion of point.
      (while (and (< hl-index hl-count) (< hl-page current-page))
        (setq old-title new-title)
        (setq hl-page (alist-get 'page (nth hl-index outline)))
        (setq new-title (alist-get 'title (nth hl-index outline)))
        (cl-incf hl-index))
      (if (< current-page hl-page) old-title new-title))))
(add-to-list 'which-func-functions 'which-func-pdf-view-function)

Programming Tools

Eglot (info)

Emacs polyGLOT (Eglot) is an Emacs language-server-protocol client that stays out of your way. Eglot (info) is a builtin since Emacs-29.1. The following listings set up Eglot:

  1. Listing minimal Eglot setup adds key bindings to eglot-mode-keymap and ensures automatic Eglot startup in case of visiting files in python-mode.
  2. Listing lst:setup-oglot uses oglot to enable using Eglot in org-src-mode Python buffers.
(with-eval-after-load 'eglot
  (keymap-set eglot-mode-map "C-c n" 'flymake-goto-next-error)
  (keymap-set eglot-mode-map "C-c p" 'flymake-goto-prev-error)
  (keymap-set eglot-mode-map "C-c r" 'eglot-rename))

(add-hook 'python-mode-hook #'eglot-ensure)
(unless (package-installed-p 'oglot)
  (package-vc-install '(oglot :url "https://github.com/gav451/oglot.git")))
(when (require 'oglot nil 'noerror)
  (setopt oglot-maybe-ensure-modes '(python-mode)))

Format-all

Listing lst:configure-format-all:

  1. Configures format-all which is a package that provides an universal interface to code formatters of more than 60 computer languages.
  2. Adds format-all-org-babel-post-tangle to org-babel-post-tangle-hook to format tangled Python code.
;; https://github.com/lassik/emacs-format-all-the-code#readme
;; https://ianyepan.github.io/posts/format-all/
;; https://jamesaimonetti.com/posts/formatting-tangled-output-in-org-mode/
(when (ensure-package-installation 'format-all)
  ;; `format-all' defines `format-all-buffer' as an autoloaded command.
  (with-eval-after-load 'ob-tangle
    (add-hook
     'org-babel-post-tangle-hook
     (defun format-all-org-babel-post-tangle ()
       (when (derived-mode-p 'python-mode)
         (setq-local format-all-formatters '(("Python" black)))
         (format-all-buffer)
         (save-buffer)
         (message "Saved reformatted tangled buffer `%s'"
                  (buffer-file-name)))))))

Flymake (info)

Flymake is an universal on-the-fly syntax checker for Emacs. It is a requirement of eglot, but you can use it without eglot for instance in python-mode buffers that do not visit a file. Listing lst:configure-flymake aliases list-errors (new) to flymake-show-buffer-diagnostics (old).

(with-eval-after-load 'flymake
  (defalias 'list-errors #'flymake-show-buffer-diagnostics
    "Show a list of Flymake diagnostics for current buffer.")

  (keymap-set flymake-mode-map "M-n" 'flymake-goto-next-error)
  (keymap-set flymake-mode-map "M-p" 'flymake-goto-prev-error))

Programming Modes

Common Lisp programming

Links to Common Lisp books and posts are:

  1. Common Lisp
  2. Kvardek Du - Luis Oliveira
  3. Lisp Resources
  4. On Lisp - Paul Graham
  5. Traité de Programmation en Common Lisp

Sly

Listing lst:setup-sly sets up the Sly (info) Common Lisp IDE for Emacs for use with Steel Bank Common Lisp (sbcl):

  1. It sets the sly-default-lisp and sly-lisp-implementations options. It uses a core image file to apply the technique to load Slynk faster (info) and listing lst:sbcl-core-for-sly tangles to a script to dump such a SBCL core.
  2. It does not ensure Connecting to SLY automatically (info) when opening a Common Lisp file because that does not play well with ob-lisp.
  3. It configures searching documentation in the Common Lisp HyperSpec according to Basic customization (info).
(when (ensure-package-installation 'sly 'sly-macrostep 'sly-named-readtables)
  (with-eval-after-load 'sly
    ;; Set `sly-default-lisp' instead of `inferior-lisp-program',
    ;; because `sly' uses `inferior-lisp-program' only as a backwards
    ;; compatibility fallback option.
    ;; The warning "Value sbcl does not match type function" is due to
    ;; the buggy `defcustom' type of `sbcl-default-lisp' in `sly.el'.
    (setopt sly-default-lisp 'sbcl
            sly-lisp-implementations
            `((ccl (,(executable-find "ccl64")))
              (sbcl (,(executable-find "sbcl")
                     "--core"
                     ,(no-littering-expand-var-file-name "sbcl.core-for-sly")))))
    (cond
     ((eq system-type 'darwin)
      (setq common-lisp-hyperspec-root
            "file:///usr/local/share/doc/hyperspec/HyperSpec/")
      (setq common-lisp-hyperspec-symbol-table
            (concat common-lisp-hyperspec-root "Data/Map_Sym.txt")))
     ((eq system-type 'gnu/linux)
      (setq common-lisp-hyperspec-root
            "file:///usr/share/doc/hyperspec-7.0/HyperSpec/"))
     (t (message "Default Common Lisp HyperSpec access")))
    (keymap-set sly-prefix-map "M-h" #'sly-documentation-lookup)))
#!/bin/sh                                       # -*- shell-script -*-
sbcl <<EOF
(mapc 'require '(sb-bsd-sockets sb-posix sb-introspect sb-cltl2 asdf))
(save-lisp-and-die "sbcl.core-for-sly")
EOF
# sbcl.core-for-sly ends here

Slynk configurables (info)

Listing lst:slink-sel allows to set or get the Sly slynk:*string-elision-length*.

#+caption[Get/set Sly slynk:*string-elision-length* functionality]:

(with-eval-after-load 'sly
  (defconst slynk-command-get-sel
    "(cdr (assoc 'slynk:*string-elision-length* slynk:*slynk-pprint-bindings*))"
    "Slynk \"string elision length\" getter command.")

  (defun slynk-command-set-sel (length)
    "Slynk LENGTH \"string elision length\" setter command.
LENGTH values must be positive integers to enable or `nil' to disable elision."
    (format "(setf %s %s)" slynk-command-get-sel length))

  (defun slynk-get-sel ()
    "Get the Slynk \"string-elision-length\"."
    (cadr (sly-eval `(slynk:eval-and-grab-output ,slynk-command-get-sel))))

  ;; Most useful for Org source blocks.
  (defun slynk-set-sel (length)
    "Set the Slynk \"string-elision-length\" LENGTH.
LENGTH values must be positive integers to enable or `nil' to disable elision."
    (cadr
     (sly-eval `(slynk:eval-and-grab-output ,(slynk-command-set-sel length)))))

  ;; Most useful for interactive use.
  (defun slynk-eval-sel-command (string)
    "Evaluate the Slynk \"string-elision-length\" command STRING."
    (unless (sly-connected-p)
      (user-error "Slynk connection is not open: \"M-x sly\"?"))
    (message "Slynk string elision length is %S"
             (read (cadr (sly-eval `(slynk:eval-and-grab-output ,string))))))

  (defun slynk-get-string-elision-length ()
    "Get the Slynk \"string-elision-length\"."
    (interactive)
    (slynk-eval-sel-command slynk-command-get-sel))

  (defun slynk-set-string-elision-length (&optional length)
    "Set the Slynk \"string-elision-length\" LENGTH.
Valid LENGTH values are positive integers to set or `nil' to reset."
    (interactive "X\ Slink string elision length: ")
    (unless (or (null length) (and (integerp length) (> length 0)))
      (user-error
       "Slynk `length' must be `nil' or a positive integer (got `%S')" length))
    (slynk-eval-sel-command (slynk-command-set-sel length))))

CS 325 AI Programming

The CS 325 AI Programming course allows to learn Common Lisp by self-study and the page CS 325: Setting up Lisp gives instructions how to:

  1. Download and install common lisp.
  2. Install Quicklisp.
  3. Install the CS325 library.

Quicklisp

Quicklisp is a library manager for Common Lisp. Listing lst:download+verify-quicklisp downloads the Quicklisp installation file and verifies its signature to prevent tampering. Listing lst:bootstrap-quicklisp tangles to a shell script allowing to bootstrap Quicklisp with SBCL. Listing lst:quicklisp-sbclrc-file tangles to the a SBCL resource file with Quicklisp support. Listing lst:clone-local-projects shows how to make local projects out of unpackaged projects and listing lst:register-load-local-projects shows how to register local projects in order to load those unpackaged projects in the same way as packaged projects.

curl -sS -O https://beta.quicklisp.org/quicklisp.lisp
curl -sS -O https://beta.quicklisp.org/quicklisp.lisp.asc
curl -sS -O https://beta.quicklisp.org/release-key.txt
gpg --import release-key.txt
gpg --verify quicklisp.lisp.asc quicklisp.lisp
#!/bin/sh

sbcl --load ~/quicklisp.lisp <<EOF
(quicklisp-quickstart:install)
(quit)
EOF

# Local Variables:
# mode: shell-script
# sh-indentation: 2
# sh-basic-offset: 2
# End:
git clone https://gitlab.com/criesbeck/cs325.git
git clone git@github.com:ageldama/cl-state-machine.git
;; SBCL on Darwin fails to run cl-cffi-gtk:
;; https://lisp-journey.gitlab.io/blog/gui-programming-in-common-lisp-part-3-of-5-gtk3/
;; https://www.crategus.com/books/cl-cffi-gtk/
;; https://www.crategus.com/books/cl-gtk/gtk-tutorial.html
(defun probe--local-project-directory (name)
  (some #'identity (mapcar (lambda (x)
                             (let ((*default-pathname-defaults* x))
                               (probe-file name)))
                           ql:*local-project-directories*)))

(when (probe--local-project-directory "cs325")
  (ql:register-local-projects)
  (ql:quickload "cs325")
  (ql:quickload "meta")
  (ql:quickload '("named-readtables" "try")) ;; testing requires "try".
  #-:clozure
  (ql:quickload "nodgui") ;; requires https://www.tcl.tk/software/tklib/
  (ql:quickload '("rutils" "rutilsx"))
  (ql:quickload "ucons"))

(when (probe--local-project-directory "cl-state-machine")
  (ql:quickload '("cl-state-machine" "cl-state-machine-examples"
                  "cl-state-machine-graphing" "cl-state-machine-test")))
;;; Hey Emacs, this is my -*- lisp -*- .sbclrc file.

;;; The following lines added by ql:add-to-init-file:
#-quicklisp
(let ((quicklisp-init (merge-pathnames "quicklisp/setup.lisp"
                                       (user-homedir-pathname))))
  (when (probe-file quicklisp-init)
    (load quicklisp-init)))

;;; Load cs325.lisp to create the cs325 package.

;; (eval-when (:compile-toplevel :load-toplevel :execute)
;;   (ql:quickload "cs325")
;;   (setq *package* (find-package :cs325-user)))

Lisp mode custom font locking for Common Lisp

This section implements the code described in the Emacs Common Lisp Font Locking post.

Initialize Common Lisp custom font locking

;;; Common Lisp custom font lock configuration.
;;; https://www.n16f.net/blog/custom-font-lock-configuration-in-emacs/

(defface cl-character-face
  '((default :inherit font-lock-constant-face))
  "The face used to highlight Common Lisp character literals.")

(defface cl-standard-function-face
  '((default :inherit font-lock-keyword-face))
  "The face used to highlight standard Common Lisp function symbols.")

(defface cl-standard-value-face
  '((default :inherit font-lock-variable-name-face))
  "The face used to highlight standard Common Lisp value symbols.")
(defun standard-symbol-names (predicate)
  (let ((symbols nil))
    (do-external-symbols (symbol :common-lisp)
      (when (funcall predicate symbol)
        (push (string-downcase (symbol-name symbol)) symbols)))
    (sort symbols #'string<)))

#+caption[Set Sly ~slynk:*string-elision-length*~]:

(when (and (fboundp 'sly-connected-p) (sly-connected-p))
  (and (fboundp 'slynk-set-sel) (slynk-set-sel (ash 1 14))))

#+caption[Set Sly slynk:*string-elision-length* result]:

16384 (15 bits, #x4000)

Collect standard symbol function names

(format nil "(defvar~%  cl-function-names~%  '~S)~%"
        (standard-symbol-names #'fboundp))
(defvar
  cl-function-names
  '("*" "+" "-" "/" "/=" "1+" "1-" "<" "<=" "=" ">" ">=" "abort" "abs" "acons"
    "acos" "acosh" "add-method" "adjoin" "adjust-array" "adjustable-array-p"
    "allocate-instance" "alpha-char-p" "alphanumericp" "and" "append" "apply"
    "apropos" "apropos-list" "aref" "arithmetic-error-operands"
    "arithmetic-error-operation" "array-dimension" "array-dimensions"
    "array-displacement" "array-element-type" "array-has-fill-pointer-p"
    "array-in-bounds-p" "array-rank" "array-row-major-index" "array-total-size"
    "arrayp" "ash" "asin" "asinh" "assert" "assoc" "assoc-if" "assoc-if-not"
    "atan" "atanh" "atom" "bit" "bit-and" "bit-andc1" "bit-andc2" "bit-eqv"
    "bit-ior" "bit-nand" "bit-nor" "bit-not" "bit-orc1" "bit-orc2"
    "bit-vector-p" "bit-xor" "block" "boole" "both-case-p" "boundp" "break"
    "broadcast-stream-streams" "butlast" "byte" "byte-position" "byte-size"
    "caaaar" "caaadr" "caaar" "caadar" "caaddr" "caadr" "caar" "cadaar"
    "cadadr" "cadar" "caddar" "cadddr" "caddr" "cadr" "call-method" "car"
    "case" "catch" "ccase" "cdaaar" "cdaadr" "cdaar" "cdadar" "cdaddr" "cdadr"
    "cdar" "cddaar" "cddadr" "cddar" "cdddar" "cddddr" "cdddr" "cddr" "cdr"
    "ceiling" "cell-error-name" "cerror" "change-class" "char" "char-code"
    "char-downcase" "char-equal" "char-greaterp" "char-int" "char-lessp"
    "char-name" "char-not-equal" "char-not-greaterp" "char-not-lessp"
    "char-upcase" "char/=" "char<" "char<=" "char=" "char>" "char>="
    "character" "characterp" "check-type" "cis" "class-name" "class-of"
    "clear-input" "clear-output" "close" "clrhash" "code-char" "coerce"
    "compile" "compile-file" "compile-file-pathname" "compiled-function-p"
    "compiler-macro-function" "complement" "complex" "complexp"
    "compute-applicable-methods" "compute-restarts" "concatenate"
    "concatenated-stream-streams" "cond" "conjugate" "cons" "consp"
    "constantly" "constantp" "continue" "copy-alist" "copy-list"
    "copy-pprint-dispatch" "copy-readtable" "copy-seq" "copy-structure"
    "copy-symbol" "copy-tree" "cos" "cosh" "count" "count-if" "count-if-not"
    "ctypecase" "decf" "declaim" "decode-float" "decode-universal-time"
    "defclass" "defconstant" "defgeneric" "define-compiler-macro"
    "define-condition" "define-method-combination" "define-modify-macro"
    "define-setf-expander" "define-symbol-macro" "defmacro" "defmethod"
    "defpackage" "defparameter" "defsetf" "defstruct" "deftype" "defun"
    "defvar" "delete" "delete-duplicates" "delete-file" "delete-if"
    "delete-if-not" "delete-package" "denominator" "deposit-field" "describe"
    "describe-object" "destructuring-bind" "digit-char" "digit-char-p"
    "directory" "directory-namestring" "disassemble" "do" "do*"
    "do-all-symbols" "do-external-symbols" "do-symbols" "documentation"
    "dolist" "dotimes" "dpb" "dribble" "ecase" "echo-stream-input-stream"
    "echo-stream-output-stream" "ed" "eighth" "elt" "encode-universal-time"
    "endp" "enough-namestring" "ensure-directories-exist"
    "ensure-generic-function" "eq" "eql" "equal" "equalp" "error" "etypecase"
    "eval" "eval-when" "evenp" "every" "exp" "export" "expt" "fboundp"
    "fceiling" "fdefinition" "ffloor" "fifth" "file-author"
    "file-error-pathname" "file-length" "file-namestring" "file-position"
    "file-string-length" "file-write-date" "fill" "fill-pointer" "find"
    "find-all-symbols" "find-class" "find-if" "find-if-not" "find-method"
    "find-package" "find-restart" "find-symbol" "finish-output" "first" "flet"
    "float" "float-digits" "float-precision" "float-radix" "float-sign"
    "floatp" "floor" "fmakunbound" "force-output" "format" "formatter" "fourth"
    "fresh-line" "fround" "ftruncate" "funcall" "function" "function-keywords"
    "function-lambda-expression" "functionp" "gcd" "gensym" "gentemp" "get"
    "get-decoded-time" "get-dispatch-macro-character" "get-internal-real-time"
    "get-internal-run-time" "get-macro-character" "get-output-stream-string"
    "get-properties" "get-setf-expansion" "get-universal-time" "getf" "gethash"
    "go" "graphic-char-p" "handler-bind" "handler-case" "hash-table-count"
    "hash-table-p" "hash-table-rehash-size" "hash-table-rehash-threshold"
    "hash-table-size" "hash-table-test" "host-namestring" "identity" "if"
    "ignore-errors" "imagpart" "import" "in-package" "incf"
    "initialize-instance" "input-stream-p" "inspect" "integer-decode-float"
    "integer-length" "integerp" "interactive-stream-p" "intern" "intersection"
    "invalid-method-error" "invoke-debugger" "invoke-restart"
    "invoke-restart-interactively" "isqrt" "keywordp" "labels" "lambda" "last"
    "lcm" "ldb" "ldb-test" "ldiff" "length" "let" "let*"
    "lisp-implementation-type" "lisp-implementation-version" "list" "list*"
    "list-all-packages" "list-length" "listen" "listp" "load"
    "load-logical-pathname-translations" "load-time-value" "locally" "log"
    "logand" "logandc1" "logandc2" "logbitp" "logcount" "logeqv"
    "logical-pathname" "logical-pathname-translations" "logior" "lognand"
    "lognor" "lognot" "logorc1" "logorc2" "logtest" "logxor" "long-site-name"
    "loop" "loop-finish" "lower-case-p" "machine-instance" "machine-type"
    "machine-version" "macro-function" "macroexpand" "macroexpand-1" "macrolet"
    "make-array" "make-broadcast-stream" "make-concatenated-stream"
    "make-condition" "make-dispatch-macro-character" "make-echo-stream"
    "make-hash-table" "make-instance" "make-instances-obsolete" "make-list"
    "make-load-form" "make-load-form-saving-slots" "make-package"
    "make-pathname" "make-random-state" "make-sequence" "make-string"
    "make-string-input-stream" "make-string-output-stream" "make-symbol"
    "make-synonym-stream" "make-two-way-stream" "makunbound" "map" "map-into"
    "mapc" "mapcan" "mapcar" "mapcon" "maphash" "mapl" "maplist" "mask-field"
    "max" "member" "member-if" "member-if-not" "merge" "merge-pathnames"
    "method-combination-error" "method-qualifiers" "min" "minusp" "mismatch"
    "mod" "muffle-warning" "multiple-value-bind" "multiple-value-call"
    "multiple-value-list" "multiple-value-prog1" "multiple-value-setq"
    "name-char" "namestring" "nbutlast" "nconc" "nintersection" "ninth"
    "no-applicable-method" "no-next-method" "not" "notany" "notevery" "nreconc"
    "nreverse" "nset-difference" "nset-exclusive-or" "nstring-capitalize"
    "nstring-downcase" "nstring-upcase" "nsublis" "nsubst" "nsubst-if"
    "nsubst-if-not" "nsubstitute" "nsubstitute-if" "nsubstitute-if-not" "nth"
    "nth-value" "nthcdr" "null" "numberp" "numerator" "nunion" "oddp" "open"
    "open-stream-p" "or" "output-stream-p" "package-error-package"
    "package-name" "package-nicknames" "package-shadowing-symbols"
    "package-use-list" "package-used-by-list" "packagep" "pairlis"
    "parse-integer" "parse-namestring" "pathname" "pathname-device"
    "pathname-directory" "pathname-host" "pathname-match-p" "pathname-name"
    "pathname-type" "pathname-version" "pathnamep" "peek-char" "phase" "plusp"
    "pop" "position" "position-if" "position-if-not" "pprint" "pprint-dispatch"
    "pprint-exit-if-list-exhausted" "pprint-fill" "pprint-indent"
    "pprint-linear" "pprint-logical-block" "pprint-newline" "pprint-pop"
    "pprint-tab" "pprint-tabular" "prin1" "prin1-to-string" "princ"
    "princ-to-string" "print" "print-not-readable-object" "print-object"
    "print-unreadable-object" "probe-file" "proclaim" "prog" "prog*" "prog1"
    "prog2" "progn" "progv" "provide" "psetf" "psetq" "push" "pushnew" "quote"
    "random" "random-state-p" "rassoc" "rassoc-if" "rassoc-if-not" "rational"
    "rationalize" "rationalp" "read" "read-byte" "read-char"
    "read-char-no-hang" "read-delimited-list" "read-from-string" "read-line"
    "read-preserving-whitespace" "read-sequence" "readtable-case" "readtablep"
    "realp" "realpart" "reduce" "reinitialize-instance" "rem" "remf" "remhash"
    "remove" "remove-duplicates" "remove-if" "remove-if-not" "remove-method"
    "remprop" "rename-file" "rename-package" "replace" "require" "rest"
    "restart-bind" "restart-case" "restart-name" "return" "return-from"
    "revappend" "reverse" "room" "rotatef" "round" "row-major-aref" "rplaca"
    "rplacd" "sbit" "scale-float" "schar" "search" "second" "set"
    "set-difference" "set-dispatch-macro-character" "set-exclusive-or"
    "set-macro-character" "set-pprint-dispatch" "set-syntax-from-char" "setf"
    "setq" "seventh" "shadow" "shadowing-import" "shared-initialize" "shiftf"
    "short-site-name" "signal" "signum" "simple-bit-vector-p"
    "simple-condition-format-arguments" "simple-condition-format-control"
    "simple-string-p" "simple-vector-p" "sin" "sinh" "sixth" "sleep"
    "slot-boundp" "slot-exists-p" "slot-makunbound" "slot-missing"
    "slot-unbound" "slot-value" "software-type" "software-version" "some"
    "sort" "special-operator-p" "sqrt" "stable-sort" "standard-char-p" "step"
    "store-value" "stream-element-type" "stream-error-stream"
    "stream-external-format" "streamp" "string" "string-capitalize"
    "string-downcase" "string-equal" "string-greaterp" "string-left-trim"
    "string-lessp" "string-not-equal" "string-not-greaterp" "string-not-lessp"
    "string-right-trim" "string-trim" "string-upcase" "string/=" "string<"
    "string<=" "string=" "string>" "string>=" "stringp" "sublis" "subseq"
    "subsetp" "subst" "subst-if" "subst-if-not" "substitute" "substitute-if"
    "substitute-if-not" "subtypep" "svref" "sxhash" "symbol-function"
    "symbol-macrolet" "symbol-name" "symbol-package" "symbol-plist"
    "symbol-value" "symbolp" "synonym-stream-symbol" "tagbody" "tailp" "tan"
    "tanh" "tenth" "terpri" "the" "third" "throw" "time" "trace"
    "translate-logical-pathname" "translate-pathname" "tree-equal" "truename"
    "truncate" "two-way-stream-input-stream" "two-way-stream-output-stream"
    "type-error-datum" "type-error-expected-type" "type-of" "typecase" "typep"
    "unbound-slot-instance" "unexport" "unintern" "union" "unless"
    "unread-char" "untrace" "unuse-package" "unwind-protect"
    "update-instance-for-different-class" "update-instance-for-redefined-class"
    "upgraded-array-element-type" "upgraded-complex-part-type" "upper-case-p"
    "use-package" "use-value" "user-homedir-pathname" "values" "values-list"
    "vector" "vector-pop" "vector-push" "vector-push-extend" "vectorp" "warn"
    "when" "wild-pathname-p" "with-accessors" "with-compilation-unit"
    "with-condition-restarts" "with-hash-table-iterator"
    "with-input-from-string" "with-open-file" "with-open-stream"
    "with-output-to-string" "with-package-iterator" "with-simple-restart"
    "with-slots" "with-standard-io-syntax" "write" "write-byte" "write-char"
    "write-line" "write-sequence" "write-string" "write-to-string" "y-or-n-p"
    "yes-or-no-p" "zerop"))

Collect standard symbol value names

(format nil "(defvar~%  cl-value-names~%  '~S)~%"
        (standard-symbol-names #'boundp))
(defvar
  cl-value-names
  '("*" "**" "***" "*break-on-signals*" "*compile-file-pathname*"
    "*compile-file-truename*" "*compile-print*" "*compile-verbose*"
    "*debug-io*" "*debugger-hook*" "*default-pathname-defaults*"
    "*error-output*" "*features*" "*gensym-counter*" "*load-pathname*"
    "*load-print*" "*load-truename*" "*load-verbose*" "*macroexpand-hook*"
    "*modules*" "*package*" "*print-array*" "*print-base*" "*print-case*"
    "*print-circle*" "*print-escape*" "*print-gensym*" "*print-length*"
    "*print-level*" "*print-lines*" "*print-miser-width*"
    "*print-pprint-dispatch*" "*print-pretty*" "*print-radix*"
    "*print-readably*" "*print-right-margin*" "*query-io*" "*random-state*"
    "*read-base*" "*read-default-float-format*" "*read-eval*" "*read-suppress*"
    "*readtable*" "*standard-input*" "*standard-output*" "*terminal-io*"
    "*trace-output*" "+" "++" "+++" "-" "/" "//" "///" "array-dimension-limit"
    "array-rank-limit" "array-total-size-limit" "boole-1" "boole-2" "boole-and"
    "boole-andc1" "boole-andc2" "boole-c1" "boole-c2" "boole-clr" "boole-eqv"
    "boole-ior" "boole-nand" "boole-nor" "boole-orc1" "boole-orc2" "boole-set"
    "boole-xor" "call-arguments-limit" "char-code-limit" "double-float-epsilon"
    "double-float-negative-epsilon" "internal-time-units-per-second"
    "lambda-list-keywords" "lambda-parameters-limit"
    "least-negative-double-float" "least-negative-long-float"
    "least-negative-normalized-double-float"
    "least-negative-normalized-long-float"
    "least-negative-normalized-short-float"
    "least-negative-normalized-single-float" "least-negative-short-float"
    "least-negative-single-float" "least-positive-double-float"
    "least-positive-long-float" "least-positive-normalized-double-float"
    "least-positive-normalized-long-float"
    "least-positive-normalized-short-float"
    "least-positive-normalized-single-float" "least-positive-short-float"
    "least-positive-single-float" "long-float-epsilon"
    "long-float-negative-epsilon" "most-negative-double-float"
    "most-negative-fixnum" "most-negative-long-float"
    "most-negative-short-float" "most-negative-single-float"
    "most-positive-double-float" "most-positive-fixnum"
    "most-positive-long-float" "most-positive-short-float"
    "most-positive-single-float" "multiple-values-limit" "nil" "pi"
    "short-float-epsilon" "short-float-negative-epsilon" "single-float-epsilon"
    "single-float-negative-epsilon" "t"))

Finalize Common Lisp custom font locking

(defvar cl-font-lock-keywords
  (let* ((character-re (concat "#\\\\" lisp-mode-symbol-regexp "\\_>"))
         (function-re (concat "(" (regexp-opt cl-function-names t) "\\_>"))
         (value-re (regexp-opt cl-value-names 'symbols)))
    `((,character-re . 'cl-character-face)
      (,function-re
       (1 'cl-standard-function-face))
      (,value-re . 'cl-standard-value-face))))

(defvar cl-font-lock-defaults
  '((cl-font-lock-keywords)
    nil                                 ; enable syntaxic highlighting
    t                                   ; case insensitive highlighting
    nil                                 ; use the lisp-mode syntax table
    (font-lock-mark-block-function . mark-defun)
    (font-lock-extra-managed-props help-echo)
    (font-lock-syntactic-face-function
     . lisp-font-lock-syntactic-face-function)))

(defun cl-init-lisp-font-lock ()
  "Hook to initialize Common Lisp font locking."
  (setq font-lock-defaults cl-font-lock-defaults))

(add-hook 'lisp-mode-hook 'cl-init-lisp-font-lock)

Emacs Lisp Programming (info)

Here is a list of links describing how to program and debug Emacs Lisp (info) code:

  1. Evaluating Elisp in Emacs
  2. Debugging Elisp Part 1: Earn your independence
  3. Debugging Elisp Part 2: Advanced topics
  4. Xah talk show: Elisp coding: xah-add-space-after-comma
  5. Xah talk show: Elisp coding: narrow-to-region, sort-lines, hilight-unicode

Ref. [cite:@Monnier.ACM-PL.4.1] exposes the evolution of Emacs Lisp and explains important Emacs Lisp idioms.

Bug Reference Mode (info)

Listing lst:bug-reference-mode configures bug-reference-mode for use with the GNU Bug Tracker.

;; (info "(debbugs) Top")
;; (info "(emacs) Bug Reference")
(ensure-package-installation 'debbugs)
(defvar bug-reference-url-format
  "https://debbugs.gnu.org/cgi/bugreport.cgi?bug=%s"
  "Format to use `gnu-debbugs' URL.")
(put 'bug-reference-mode 'safe-local-variable 'booleanp)

Debugging Emacs Lisp (info)

GAV: I fail to instrument the functions from the source level debugger (edebug) wiki entry as in Edebug (info).

Evaluating Elisp in Emacs

Listing setup ielm configures the Interactive Emacs Lisp Mode for better interoperability with smartparens: get help on the key bindings by means of src_emacs-lisp[:results none]{(describe-function 'ielm)}.

(with-eval-after-load 'ielm
  (setopt ielm-dynamic-return nil))

Writing Better Elisp Docstrings

;; Stolen from Charles Choi:
;; http://yummymelon.com/devnull/writing-better-elisp-docstrings.html
;; GAV: Org changes the semantics of calling `beginning-of-defun'.
(defun describe-function-at-point ()
  "Call `describe-function' on the Elisp function at point.
Switch temporarily from `org-mode' to `text-mode', since `org-mode'
changes the semantics of calling `beginning-of-defun'."
  (interactive)
  (let ((mode major-mode))
    (when (eq major-mode 'org-mode)
      (text-mode))
    (save-excursion
      (beginning-of-defun)
      (forward-char)
      (forward-sexp 2)
      (let ((end-point (point)))
        (backward-sexp)
        (let* ((fn-name (buffer-substring (point) end-point))
               (interned (intern-soft fn-name)))
          (if interned
              (describe-function interned)
            (message "Can't find function at point")))))
    (when (eq mode 'org-mode)
      (org-mode))))

(with-eval-after-load 'org-mode
  (keymap-set org-mode-map "C-c d" #'describe-function-at-point))
(with-eval-after-load 'emacs-lisp-mode
  (keymap-set emacs-lisp-mode-map "C-c d" #'describe-function-at-point))

Go Programming

Links for further investigation are:

  1. Go by example is a showcase of short examples with explanations.
(when (featurep 'treesit)
  (setopt go-ts-mode-indent-offset 2)
  (add-hook 'go-ts-mode-hook (lambda ()
                               (setq-local tab-width 2)))
  (add-to-list 'auto-mode-alist `(,(rx ".go" eos) . go-ts-mode)))

Python programming

The Python Programming in Emacs wiki page lists options to enhance Emacs's built-in python-mode. Here, the focus is on one Emacs package and three Python packages:

  1. Eglot - Emacs polyGLOT: a builtin LSP client since Emacs-29.1 and its author (who is a prolific Common Lisp and Emacs Lisp programmer) has also contributed to other parts of Emacs.
  2. Jedi is a static analysis tool for Python that is typically used in plugins for editors or integrated development environments. Jedi has a focus on autocompletion and object definition lookup functionality.
  3. Nowadays, Ruff is the fastest Python linter and a replacement for Flake8 with a variety of its plugins. Charlie Marsh explains why he started Ruff in the post Python tooling could be much faster.
  4. Python LSP Server is in my opinion the most complete Language Server Protocol implementation for Python and it handles Ruff thanks to the python-lsp-ruff plugin.

Here are links covering how to integrate Emacs, Python and a Python LSP server, before plunging into the configuration steps:

  1. Building Your Own Emacs IDE with LSP
  2. Doom Emacs and Language Servers
  3. Eglot based Emacs Python IDE
  4. Getting started with lsp-mode for Python
  5. Python & Emacs, Take 3
  6. Python with Emacs: py(v)env and lsp-mode

Here are links to Python programming background videos:

  1. Dataclasses: The Code Generator to End All Code Generators - Raymond Hettinger
  2. Modern Dictionaries - Raymond Hettinger
  3. Python's Class Development Kit - Raymond Hettinger
  4. The Big Leap of Python-3.13 - Lukasz Langa
  5. The Mental Game of Python - Raymond Hettinger
  6. Thinking about Concurrency - Raymond Hettinger
  7. Thinking outside the GIL with AsyncIO and MultiProcessing - John Reese
  8. What can't WebAssembly do? - Katie Bell

Python-mode

Listing setup Python mode selects a common Python interpreter in a virtual environment for use in python-mode and ob-python. The pythonic, pyvenv, and pyvenv packages allow to handle Python virtual environments within Emacs. The pyenv package provides support to work with pyenv (eventually with pyenv-virtualenv) to select between different python versions (eventually each with different environments). In the end, all those packages do is to set python-shell-virtualenv-root (in case of pyenv and pythonic) or tweak the environment variables and restart the relevant Python child processes (in case of pyvenv). Therefore, I replace those packages with listing access pyenv and select a Python interpreter in a virtual environment to set python-shell-virtualenv-root from within Emacs.

Install isort and pyflakes to enable adding, removing, sorting, and fixing import statements in Python-mode, see the end of the commentary src_emacs-lisp[:results none]{(find-library "python")} section.

Listing kickoff pyproject.toml proposal facilitates dropping a pyproject.toml file into Python projects which anyhow should switch to using a pyproject.toml file as explained in the post This Way Up: A Bottom-Up Look At Python Packaging. The packaging Python projects tutorial and the Python sample project walk you through the process of writing a pyproject.toml file. Ruff docstring setup explains how to setup documentation string checking in the pyproject.toml file.

Finally, listing ruff-nocolor pipes the stdout output of the ruff executable through cat to remove escape sequences.

(with-eval-after-load 'ob-python
  (setopt org-babel-python-command (concat (or (executable-find "python3")
                                               (executable-find "python"))
                                           " -E")))

(with-eval-after-load 'python
  (setopt python-indent-guess-indent-offset nil
          python-shell-completion-native-disabled-interpreters '("ipython3"
                                                                 "pypy")
          python-shell-interpreter (or (executable-find "python3")
                                       (executable-find "python"))
          python-shell-interpreter-args "-E -i"
          python-check-command (executable-find "ruff-nocolor")
          python-flymake-command (list (executable-find "ruff-nocolor")
                                       "--stdin-filename" "stdin" "-")))
(when (executable-find "pyenv")
  (defun pyenv-full-path (version)
    "Return the full path for VERSION."
    (concat (pyenv-root) (file-name-as-directory "versions") version))

  (defun pyenv-root ()
    "Return \"pyenv root\" as a directory."
    (let* ((pair (get-program-code-output "pyenv" "root"))
           (return-code (car pair))
           (output (cdr pair)))
      (if (= 0 return-code)
          (file-name-as-directory (string-trim output))
        (error "%s" (string-trim output)))))

  (defun pyenv-version-name ()
    "Return \"pyenv version-name\"."
    (let* ((pair (get-program-code-output "pyenv" "version-name"))
           (return-code (car pair))
           (output (cdr pair)))
      (if (= 0 return-code)
          (string-trim output)
        (error "%s" (string-trim output)))))

  (defun pyenv-versions ()
    "Return \"pyenv versions --bare --skip-aliases\" as a list."
    (let* ((pair (get-program-code-output
                  "pyenv" "versions" "--bare" "--skip-aliases"))
           (return-code (car pair))
           (output (cdr pair)))
      (if (= 0 return-code)
          (split-string output)
        (error "%s" (string-trim output)))))

  (defun pyenv-virtualenvs ()
    "Return \"pyenv virtualenvs --bare --skip-aliases\" as a list."
    (let* ((pair (get-program-code-output
                  "pyenv" "virtualenvs" "--bare" "--skip-aliases"))
           (return-code (car pair))
           (output (cdr pair)))
      (if (= 0 return-code)
          (split-string output)
        (error "%s" (string-trim output))))))
(with-eval-after-load 'python
  (when (and (fboundp 'pyenv-full-path)
             (fboundp 'pyenv-version-name)
             (fboundp 'pyenv-versions)
             (fboundp 'pyenv-virtualenvs))
    (setq python-shell-virtualenv-root
          (pyenv-full-path (or (pyenv-version-name)
                               (car (pyenv-virtualenvs))
                               (car (pyenv-versions)))))
    (message "Now `python-shell-virtualenv-root' equals %S"
             python-shell-virtualenv-root)

    (defun set-python-shell-virtualenv-root-to-pyenv-version ()
      "Set `python-shell-virtualenv-root' to a pyenv version."
      (interactive)
      (let* ((version-name (pyenv-version-name))
             (prompt (format "pyenv version (%s): " version-name))
             (choices (pyenv-versions))
             (version (completing-read prompt choices nil 'require-match)))
        (unless (string= version-name version)
          (setq python-shell-virtualenv-root (pyenv-full-path version))
          (setenv "PYENV_VERSION" version))
        (message "Now `python-shell-virtualenv-root' equals %S"
                 python-shell-virtualenv-root)))

    (defun set-python-shell-virtualenv-root-to-pyenv-virtualenv ()
      "Set `python-shell-virtualenv-root' to a pyenv virtualenv."
      (interactive)
      (let* ((version-name (pyenv-version-name))
             (prompt (format "pyenv virtualenv (%s): " version-name))
             (choices (pyenv-virtualenvs))
             (version (completing-read prompt choices nil 'require-match)))
        (unless (string= version-name version)
          (setq python-shell-virtualenv-root (pyenv-full-path version))
          (setenv "PYENV_VERSION" version))
        (message "Now `python-shell-virtualenv-root' equals %S"
                 python-shell-virtualenv-root)))))
# See:
# https://docs.astral.sh/ruff/configuration/
# https://pycqa.github.io/isort/docs/configuration/black_compatibility.html
# https://setuptools.pypa.io/en/latest/userguide/pyproject_config.html

[tool.black]
line-length = 88

[tool.isort]
profile = "black"

[tool.ruff]
line-length = 88

[tool.ruff.lint]
select = [
  "ARG",    # flake8-unused-arguments
  "B",      # flake8-bugbear
  "C",      # mccabe
  "C4",     # flake8-comprehensions
  "E",      # pycodestyle
  "D",      # pydocstyle
  "F",      # pyflakes
  "UP",     # pyupgrade
  "W",      # pycodestyle
  "NPY201", # numpy-2.0 upgrade guide
  ]
ignore = [
  "B905", # `zip()` without an explicit `strict=` parameter
  "D202", # no blank lines allowed after function docstring
]

[tool.ruff.lint.mccabe]
max-complexity = 15

[tool.ruff.lint.pydocstyle]
convention = "numpy"

# Local Variables:
# mode: conf-toml-mode
# End:
#!/bin/sh

ruff "$@" | cat

# Local Variables:
# mode: shell-script
# sh-indentation: 2
# sh-basic-offset: 2
# End:

Package Installer for Python

Listing lst:pip-pypi-options, lst:pip-list-outdated, and lst:pip-upgrade-maybe define the pip-list-outdated and pip-upgrade-maybe commands to upgrade outdated Python packages in Emacs.

(defgroup pip nil
  "Client for accessing the \"Package Installer for Python\" and \"PyPI\"."
  :group 'applications)

(defcustom pip-frozen-packages nil
  "Frozen Python packages."
  :group 'pip
  :type '(repeat string))

;; Updating "docutils" may require a compatible "Sphinx" release.
;; (setopt pip-frozen-packages '("docutils"))
(defvar pip-outdated-packages nil
  "Outdated Python packages.")

(defun pip--list-outdated-sentinel (process _event)
  "Sentinel function for when the `pip-list-outdated' PROCESS succeeds."
  (when (and (eq (process-status process) 'exit)
             (zerop (process-exit-status process))
             (buffer-live-p (process-buffer process)))
    (with-current-buffer (process-buffer process)
      (goto-char (point-min))
      (setq pip-outdated-packages
            (json-parse-buffer :array-type 'list :object-type 'alist))
      (let ((alists pip-outdated-packages))
        (while alists
          (setcdr (assq 'name (car alists))
                  (string-replace "_" "-" (alist-get 'name (car alists))))
          (setq alists (cdr alists))))
      (kill-buffer)
      (describe-variable 'pip-outdated-packages)
      (message "Calling `%S' succeeded" #'pip-list-outdated))))

(defun pip-list-outdated ()
  "Save the outdated Python packages in `pip-outdated-packages'.

This invokes an asynchronous process and finishes with a message."
  (interactive)
  (let ((content '("pip" "list" "--outdated" "--format" "json")))
    (make-process
     :name "pip-list-outdated"
     :buffer (generate-new-buffer-name "*pip-list-outdated-output*")
     :command content
     :sentinel #'pip--list-outdated-sentinel)
    (message "Running `%s' asynchronously" (string-join content " "))))
(defun pip--upgrade-maybe-sentinel (process _event)
  "Sentinel function for when the `pip-upgrade-maybe' PROCESS succeeds."
  (when (and (eq (process-status process) 'exit)
             (buffer-live-p (process-buffer process)))
    (display-buffer (process-buffer process))))

(defun pip-upgrade-maybe ()
  "Install the latest version of outdated packages unless they are frozen.

This invokes an asynchronous process and finishes with displaying the process
buffer to check whether upgrading has made the dependencies incompatible."
  (interactive)
  (let (found)
    (dolist (alist pip-outdated-packages found)
      (let ((name (alist-get 'name alist))
            (latest_version (alist-get 'latest_version alist)))
        (unless (member name pip-frozen-packages)
          (push (format "%s==%s" name latest_version) found))))
    (if (consp found)
        (let ((content
               `("pip" "install" "--progress-bar" "off" ,@(nreverse found))))
          (make-process
           :name "pip-upgrade-maybe"
           :buffer (generate-new-buffer-name "*pip-upgrade-maybe*")
           :command content
           :sentinel #'pip--upgrade-maybe-sentinel)
          (message "Running `%s' asynchronously" (string-join content " ")))
      (message "`pip-upgrade-maybe' found no packages to install"))))

Eglot for python-mode

Listing lst:configure-eglot+pylsp-ruff configures eglot for Python using the python-lsp-server with the pylsp-ruff plugin for the easiest way to let python-lsp-server use Ruff. It is better to uninstall the Python packages autopep8, flake8, pydocstyle, pylint, and rope.

Listing lst:eglot-directory-variables-for-python shows a proper .dir-locals.el file in the root directory of any Python project to start eglot automatically.

Type

(eval

to dump a JSON representation of src_emacs-lisp[:results none]{(describe-variable 'eglot-workspace-configuration)} for debugging.

(with-eval-after-load 'eglot
  (setq-default
   eglot-workspace-configuration
   ;; Enable the `:pylsp_ruff' plugin and ensure to uninstall the
   ;; `:flake8', `:mccabe', and `:pycodestye' plugins.
   '(:pylsp (:plugins
             (:pylsp_ruff
              (:enabled t)
              :jedi
              (:auto_import_modules ["numpy"])
              :jedi_completion
              (:cache_for ["astropy"]))))))
;; A .dir-locals.el file proposal in the root of any
;; Python project or Org-mode project tangling Python files
;; to launch eglot automatically.
;; Ensure to enable the pylsp_ruff plugin.
;; Ensure to uninstall the flake8, mccabe, and pycodestyle Python packages.
((nil  ;; nil, since Emacs-29.1 filters out irrelevant variable names.
  . ((eglot-workspace-configuration
      . (:pylsp (:plugins
                 (:pylsp_ruff
                  (:enabled t)
                  :jedi
                  (:auto_import_modules ["numpy"])
                  :jedi_completion
                  (:cache_for ["astropy"]))))))))
command key map keys
xref-find-definition global-map

(eval

xref-pop global-map

(eval

flymake-goto-next-error eglot-mode-map

(eval

flymake-goto-prev-error eglot-mode-map

(eval

eglot-rename eglot-mode-map

(eval

eldoc-doc-buffer eglot-mode-map

(eval

Jedi

Listing lst:example-py is a Python example to test whether jedi in combination with eglot works when coding for instance numpy universal functions.

import numpy
import astropy.units as apu

a = numpy.arange(0, 11)
a = numpy.linspace(0, 10, num=11)
a = numpy.arccos(a)
q = apu.Quantity(a, apu.meter)
print(q)

Debugging Python programs in Emacs

RealGUD is the best option and in case of defaulting to

(eval

debugging may work better with tool-bar-mode enabled. Listing lst:ensure-realgud ensures the installation of realgud. Listing lst:pdb-numpy is a debugging start by means of pdb or realgud:pdb. Links of interest are:

Python info links of interest are:

  • Debugging C API extensions and CPython Internals with GDB (info).
  • Debugging and Profiling (info).
(ensure-package-installation 'realgud)
def buggy():
    from numpy import array, matrix
    from numpy.linalg import inv

    a = matrix(array([[1.0, 1.0], [1.0, 1.0]]))
    # Use "display", "p", or "pp" to inspect "a".
    # PDB: A series of "s" does not raise the exception here, contrary to "c".
    b = inv(a)
    return b

if __name__ == "__main__":
    b = buggy()
    # PDB: The series of "s" says only here "Uncaught exception".

Libraries

Library dash.el (info)

The library dash.el (info) positions itself as a modern alternative for the list processing API of cl. It is a requirement of important packages in this Emacs setup (for instance citeproc, magit, and smartparens). Listing lst:configure-dash enables fontification of dash macros and makes info-lookup-symbol take into account the dash macros.

(when (fboundp #'global-dash-fontify-mode)
  (global-dash-fontify-mode))

(when (fboundp #'dash-register-info-lookup)
  (with-eval-after-load 'info-look
    (dash-register-info-lookup)))

Library f.el

The library f.el positions itself as a modern API for working with files and directories in Emacs. It is a requirement of the citeproc package in this Emacs setup and requires no configuration.

Library s.el

The library s.el positions itself as the long lost Emacs string manipulation library. It is a requirement of the citeproc and citar packages in this Emacs setup and requires no configuration.

Minor Modes (info)

Synchronal multiple-region editing

(when (ensure-package-installation 'iedit)
  (require 'iedit nil 'noerror))

Unobtrusive whitespace trimming

(when (ensure-package-installation 'ws-butler)
  (setopt ws-butler-keep-whitespace-before-point nil)
  (add-hook 'prog-mode-hook #'ws-butler-mode)
  (add-hook 'text-mode-hook #'ws-butler-mode))

Structural editing

Structural editing keeps character pairs (for instance parentheses, curly and square brackets as well as single and double quotes) balanced to leave code (for instance Lisp and Python) and text (for instance LaTeX and Org) structure intact. I use smartparens which offers a normal mode (smartparens-mode) and a strict mode (smartparens-strict-mode). Although both modes insert character pairs, the normal mode allows to delete one of the paired characters easily while the strict mode does not. Therefore, the strict mode is more for code editing since it never breaks programming language rules and the normal mode is more for text editing where structure is a matter of convention instead of programming language rules.

For instance, the strict mode in Python allows to delete entire lists, tuples, or the arguments after the cursor (what Emacs calls point) in a function call without breaking the character pair balance. In order to repair a broken character pair balance, insert a single character by prefixing it with "C-q" bound to quoted-insert.

The smartparens documentation targets experienced Emacs users. The following links show how to put the documentation to practical use:

  1. The smartparens cheatsheet demonstrates its usage visually.
  2. Omar Antolin's gist "my-smartparens-config.el" is the first place to look for how to tweak smartparens. However, the gist may be partially obsolete, since it is not part of his current Emacs configuration.
  3. How to enable smartparens in the minibuffer after eval-expression explains how the machinery after the first and after later usages of eval-expression differ and discusses options how to handle those differences.

Listing lst:setup-smartparens aims to setup smartparens for Go, LaTeX, Lisp dialects, Org, and Python. Execute src_emacs-lisp[:results none]{(sp-cheat-sheet)} for short documentation taking into account the overridden key bindings in listing lst:setup-smartparens. Table tab:smartparens-commands-and-bindings lists commands with key bindings taken in order from src_emacs-lisp[:results none]{(sp-cheat-sheet)} that takes the overrides of listing lst:setup-smartparens into account. Finally, listing lst:sp-eval-expression defines an alternative to eval-expression enabling smartparens-strict-mode and font-lock-mode.

Despite the provocative post "What if structural editing was a mistake?", smartparens is one of my favorite packages. In particular, smartparens handles matching pairs in Org-mode files with export and source blocks for multiple formats and languages almost (exceptions are due to Search failed errors inside source blocks) correctly. Therefore, show-smartparens-mode highlights matching pairs immediately in front or after point in such files correctly, contrary to for instance rainbow-delimiters.

(when (ensure-package-installation 'smartparens)
  ;; GAV: Documentation says to require `smartparens-config'.
  (require 'smartparens-config)
  (setopt sp-base-key-bindings 'sp
          sp-override-key-bindings '(("C-(" . sp-backward-slurp-sexp)
                                     ("C-)" . sp-forward-slurp-sexp)
                                     ("C-M-(" . sp-backward-barf-sexp)
                                     ("C-M-)" . sp-forward-barf-sexp)))

  (add-hook 'conf-toml-mode-hook #'smartparens-mode)
  (add-hook 'prog-mode-hook      #'smartparens-mode)
  (add-hook 'text-mode-hook      #'smartparens-mode)

  (add-hook 'emacs-lisp-mode-hook      #'smartparens-strict-mode)
  (add-hook 'go-ts-mode-hook           #'smartparens-strict-mode)
  (add-hook 'ielm-mode-hook            #'smartparens-strict-mode)
  (add-hook 'inferior-python-mode-hook #'smartparens-strict-mode)
  (add-hook 'lisp-data-mode-hook       #'smartparens-strict-mode)
  (add-hook 'lisp-mode-hook            #'smartparens-strict-mode)
  (add-hook 'python-mode-hook          #'smartparens-strict-mode)
  (add-hook 'sly-mrepl-mode-hook       #'smartparens-strict-mode)

  (when (fboundp 'go-ts-mode)
    ;; Stolen from `smartparens-go':
    (sp-with-modes 'go-ts-mode
      (sp-local-pair "{" nil :post-handlers '(("||\n[i]" "RET")))
      (sp-local-pair "/*" "*/" :post-handlers '(("| " "SPC")
                                                ("* ||\n[i]" "RET"))))
    ;; Go has no sexp suffices.  This fixes slurping:
    ;; (|foo).bar -> (foo.bar)
    (add-to-list 'sp-sexp-suffix (list #'go-ts-mode 'regexp "")))

  ;; https://xenodium.com/emacs-smartparens-auto-indent/index.html
  (defun indent-between-pair (&rest _ignored)
    (newline)
    (indent-according-to-mode)
    (forward-line -1)
    (indent-according-to-mode))

  (dolist (left '("(" "[" "{"))
    (sp-local-pair 'prog-mode left
                   nil :post-handlers '((indent-between-pair "RET"))))

  (show-smartparens-global-mode +1))
command keys status
sp-forward-sexp

(eval

sp-backward-sexp

(eval

sp-next-sexp

(eval

sp-prev-sexp

(eval

sp-down-sexp

(eval

shadowed
sp-backward-down-sexp

(eval

sp-beginning-of-sexp

(eval

sp-end-of-sexp

(eval

sp-up-sexp

(eval

sp-backward-up-sexp

(eval

sp-kill-sexp

(eval

sp-copy-sexp

(eval

sp-forward-slurp-sexp

(eval

override
sp-backward-slurp-sexp override
sp-forward-barf-sexp

(eval

override
sp-backward-barf-sexp override
sp-forward-symbol

(eval

sp-backward-symbol

(eval

sp-unwrap-sexp

(eval

shadowed
sp-backward-unwrap-sexp

(eval

sp-splice-sexp

(eval

sp-splice-killing-backward

(eval

sp-splice-sexp-killing-forward

(eval

shadowed
sp-select-next-thing

(eval

sp-select-next-thing-exchange

(eval

sp-mark-sexp

(eval

(with-eval-after-load 'smartparens
  ;; https://lists.gnu.org/archive/html/help-gnu-emacs/2014-07/msg00135.html
  ;; GAV: Reuse `read--expresssion-map' instead of defining my own map.
  (defun sp--read-expression (prompt &optional initial-contents)
    (let ((minibuffer-completing-symbol t))
      (minibuffer-with-setup-hook
          (lambda ()
            (emacs-lisp-mode) ; Enables `smartparens-strict-mode' too.
            (use-local-map read--expression-map)
            (font-lock-mode t))
        (read-from-minibuffer prompt initial-contents
                              read--expression-map nil
                              'read-expression-history))))

  (defun sp-eval-expression (expression &optional arg)
    "Evaluate EXPRESSION with `smartparens' support."
    (interactive (list (read (sp--read-expression "SP eval: "))
                       current-prefix-arg))
    (if arg
        (insert (pp-to-string (eval expression lexical-binding)))
      (pp-display-expression (eval expression lexical-binding)
                             "*SP Eval Output*")))

  ;; Do not change the "M-ESC :" `eval-expression' key binding.
  (keymap-global-set "M-:" #'sp-eval-expression))

Electric operators

Listing lst:configure-electric-operator configures electric-operator-mode to add spaces around operators for compatibility with for instance the Black code formatter for Python.

(when (ensure-package-installation 'electric-operator)
  (add-hook 'c-mode-common-hook #'electric-operator-mode)
  (add-hook 'python-mode-hook   #'electric-operator-mode))

Smart snippets

(when (ensure-package-installation 'yasnippet)
  ;; Set `yas-alias-to-yas/prefix-p' before loading `yasnippet'.
  (setopt yas-alias-to-yas/prefix-p nil)
  (add-hook 'LaTeX-mode-hook #'yas-minor-mode)
  (add-hook 'org-mode-hook #'yas-minor-mode)
  (add-hook 'python-mode-hook #'yas-minor-mode))

Display (info)

Narrowing

Narrowing means focusing in on some portion of the buffer and widening means focussing out on the whole buffer. This allows to concentrate temporarily on for instance a particular function or paragraph by removing clutter. The "Do What I Mean" narrow-or-widen-dwim function allows to toggle between narrowed and widened buffer states. Here, the function narrow-or-widen-dwim operates also on any Org table, Org source block, Org block, or Org subtree.

(with-eval-after-load 'emacs
  (declare-function org-at-table-p "org" (&optional table-type))

  (defun org-narrow-to-table ()
    "Narrow buffer to table at point."
    (interactive)
    (if (org-at-table-p)
        (narrow-to-region (org-table-begin) (org-table-end))
      (user-error "Not in a table")))

  (defun narrow-or-widen-dwim (p)
    "Widen a narrowed buffer, narrow \"Do What I Mean\" otherwise.
DWIM means: region, Org table, Org source block, Org block, Org
subtree, LaTeX environment, TeX group, or defun, whichever
applies first.  Narrowing to org-src-block actually calls
`org-edit-src-code'.

With prefix P, don't widen, just narrow even if buffer is already
narrowed."
    (declare (interactive-only t))
    (interactive "P")
    (cond ((and (buffer-narrowed-p) (not p))
           (widen))
          ((and (bound-and-true-p org-src-mode) (not p))
           (org-edit-src-exit))
          ((region-active-p)
           (narrow-to-region (region-beginning) (region-end)))
          ((derived-mode-p 'org-mode)
           (or (with-demoted-errors "DWIM: %S" (org-narrow-to-table) t)
               (with-demoted-errors "DWIM: %S" (org-edit-src-code) t)
               (with-demoted-errors "DWIM: %S" (org-narrow-to-block) t)
               (org-narrow-to-subtree)))
          ((derived-mode-p 'latex-mode)
           (LaTeX-narrow-to-environment))
          ((derived-mode-p 'tex-mode)
           (TeX-narrow-to-group))
          (t
           (narrow-to-defun))))

  (keymap-set ctl-x-map "n t" #'org-narrow-to-table)
  (keymap-set ctl-x-map "C-n" #'narrow-or-widen-dwim))

Visualize color codes and names

Listing lst:setup-rainbow-mode configures rainbow-mode to colorize color codes and names in buffers for debugging.

(when (ensure-package-installation 'rainbow-mode)
  ;; GAV: Do not add `rainbow-mode' to any programming language hook,
  ;; since that interferes at least with Org export to LaTeX.
  (setopt rainbow-x-colors-major-mode-list
          (list 'c++-mode 'c-mode 'emacs-lisp-mode 'inferior-emacs-lisp-mode
                'lisp-interaction-mode 'org-mode 'python-mode)))

Flash the line around point for visual feedback

Listing lst:flash-line-around-point implements flashing of the line around point for visual feedback after a selection of commands that make it hard to track point movements visually.

;; https://karthinks.com/software/batteries-included-with-emacs/
;; https://github.com/karthink/.emacs.d/blob/master/init.el#L2077
;; BUG#71537: Using `let' in `flash-line-around-point' implies
;; requiring `pulse'.
(require 'pulse)

(defun flash-line-around-point (&rest _)
  "Flash the line around point."
  (let ((pulse-iterations 16)
        (pulse-delay 0.1))
    (pulse-momentary-highlight-one-line (point))))

(advice-add 'scroll-up-command   :after #'flash-line-around-point)
(advice-add 'scroll-down-command :after #'flash-line-around-point)
(advice-add 'recenter-top-bottom :after #'flash-line-around-point)
(advice-add 'other-window        :after #'flash-line-around-point)

Applications

Hyperlinking and Web Navigation Features (info)

Browse URL (info)

Listing lst:configure-browse-url configures browse-url.

(with-eval-after-load 'browse-url
  (defun browse-url-mpv (url &optional _)
    (start-process "mpv" nil "mpv" url))

  (defconst browse-url-mpv-regexp
    (rx bos "http" (opt "s") "://"
        (or (seq "youtu.be/")
            (seq "www.youtube.com/" (or "embed/" "watch?"))
            (seq (+? nonl) (or ".mp4" ".webm") eos)))
    "Match hyperlinks to open with mpv.")

  (setopt
   browse-url-generic-program (or (when (eq system-type 'darwin) "open")
                                  (executable-find "firefox"))
   browse-url-handlers `((,browse-url-mpv-regexp . browse-url-mpv)
                         ("." . eww-browse-url))))

Emacs Web Wowser

(with-eval-after-load 'eww
  ;; GAV: Renaming `eww' buffers conflicts with `desktop-save-mode'.
  ;; GAV: Setting `eww-restore-desktop' to something else fails too.
  (setopt eww-restore-desktop nil)

  (defun eww-display-pdf-as-binary (fn &rest args)
    (let ((buffer-file-coding-system 'binary))
      (apply fn args)))

  (advice-add 'eww-display-pdf :around #'eww-display-pdf-as-binary))

Mailcap

Extension packages as EWW (info), Gnus (info), and Org (info) rely on a mailcap file to know what application should open a specific media file type. Listing lst:tangle-mailcap-file specifies emacsclient as the application to open PDF files.

# https://emacs.stackexchange.com/a/24502 answers the question:
# How to use pdf-tools (pdf-view-mode) in emacs?
application/pdf; emacsclient %s

Open Street Map viewer for Emacs

Open Street Map is a tile-based map viewer with a responsive movable and zoomable display and with a list of multiple preconfigured tile servers. Listing lst:ensure-osm-installation ensures the installation of osm and listing lst:using-osm-example is a minimal example of how to use osm. Inside osm-mode buffers, the key binding for the command src_emacs-lisp

(eval

)} is

(eval

allowing to bookmark such buffers. Outside osm-mode buffers, the key binding for the command src_emacs-lisp

(eval

ly 'consult-bookmark)} is

(eval

.

(ensure-package-installation 'osm)
(osm "Tower of London")

Webjump

Listing lst:set-webjump-options binds

(eval

to webjump and sets the webjump-sites option.

(when (fboundp 'webjump)
  (keymap-global-set "C-c j" 'webjump)
  (with-eval-after-load 'webjump
    (setopt
     webjump-sites
     '(("ASCII Table" . "www.ascii-code.com/ASCII")
       ("CS 325 AI Programming" . "courses.cs.northwestern.edu/325")
       ("Emacs News" . "sachachua.com/blog/category/emacs-news")
       ("Mastering Emacs" . "www.masteringemacs.org")
       ("Planet Emacs Life" . "planet.emacslife.com")
       ("Worg - Org Mode Community" . "orgmode.org/worg")
       ("Git: Emacs" . "git.savannah.gnu.org/cgit/emacs.git")
       ("Git: Emacs MultiMedia System" .
        "https://git.savannah.gnu.org/cgit/emms.git")
       ("Git: GNU AUCTeX" . "git.savannah.gnu.org/cgit/auctex.git")
       ("Git: GNU ELPA" . "git.savannah.gnu.org/cgit/emacs/elpa.git")
       ("Git: NonGNU ELPA" . "git.savannah.gnu.org/cgit/emacs/nongnu.git")
       ("Git: Org Mode" . "git.savannah.gnu.org/cgit/emacs/org-mode.git")
       ("List: Org Mode" . "list.orgmode.org")
       ("List: Emacs Developer Archives" .
        "lists.gnu.org/archive/html/emacs-devel/")
       ("List: Help GNU Emacs Archives" .
        "lists.gnu.org/archive/html/help-gnu-emacs/")
       ("Counterpunch" . "www.counterpunch.org")
       ("Dictionary FR" . [simple-query "www.cnrtl.fr"
                                        "www.cnrtl.fr/definition/" ""])
       ("Dictionary NL" . [simple-query "www.woorden.org"
                                        "www.woorden.org/woord/" ""])))))

Elfeed: Emacs web feed reader

Listing lst:set-elfeed-options sets elfeed options, binds the elfeed command, and makes a minimal attempt to enable emms.

(when (ensure-package-installation 'elfeed)
  (keymap-global-set "C-x w" #'elfeed)

  (with-eval-after-load 'elfeed
    (setopt elfeed-feeds
            '(("https://frame.work/fr/fr/blog.rss" framework)
              ("https://nullprogram.com/feed/" c-wellons)
              ("https://planet.emacslife.com/atom.xml" planet-emacs)
              ("https://sachachua.com/blog/category/emacs/feed" s-chua)
              ("https://www.lemonde.fr/blog/huet/feed/" sciences))))
  (with-eval-after-load 'elfeed-show
    (when (fboundp 'emms-all)
      (emms-all))))

Emacs Multimedia System (info)

The link my emms and elfeed setup is a nice introduction to configuring and using emms with elfeed. Listing lst:set-emms-options configures emms for mpv while eliminating use of mpd.

(when (ensure-package-installation 'emms)
  (with-eval-after-load 'emms
    (emms-all) ;; Restrict now to `emms-player-mpd' use only.
    (setopt emms-player-list '(emms-player-mpv)))
  (with-eval-after-load 'emms-mode-line
    (setopt emms-mode-line-format ""))
  (with-eval-after-load 'emms-player-mpv
    (setopt emms-player-mpv-update-metadata t
            emms-player-mpv-parameters
            (append emms-player-mpv-parameters
                    '("--ytdl-format=best" "--config=no" "--fullscreen"))))
  (with-eval-after-load 'emms-playing-time
    (setopt emms-playing-time-display-format " %s "))
  (with-eval-after-load 'emms-streams
    (setopt emms-streams-file
            (no-littering-expand-etc-file-name "emms/streams.emms"))))

Local variables linking to Latexmk save-compile-display-loop

Only the Org source file shows the local variables footer.