emacs.d/README.org

286 KiB
Raw Blame History

Emacs setup for use with LaTeX, 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, Org, and Python.

Copyright © 2021-2023 Gerard Vermeulen.

Permission is granted to copy, distribute and/or modify 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. A copy of the license is included in the section entitled "GNU Free Documentation License".

Quick start

This setup requires at least Emacs-29.0.60. Backup the user-emacs-directory (defaults often to ~/.emacs.d on Linux, Unix, or Darwin) and execute the commands in listing prepare the user-emacs-directory without ssh access or in listing prepare the user-emacs-directory with ssh access. After invoking Emacs interactively (in interactive mode, neither in batch mode, nor in server mode), 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 packages Vertico, Embark, Marginalia, and Consult. 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 excution of the org-latex-export-latex-to-latex command in this buffer.

Here follows a list of interesting Emacs configurations:

  1. Musa Al-hassy's configuration is an impressive example of producing the Emacs initialization files and other files by tangling an org file. His methodology is impressive, as his Elisp Cheat Sheet and org-special-block-extra package show. To me, this is a configuration to admire, but his methodology is way over my head.
  2. Omar Antolin Camarena's configuration exploits built-in packages, Omar's own small packages, and large external packages. Omar is the author of orderless and embark. My use setopt to set Emacs options instead of the customize interface comes from his idea of using custom-set-variables although that is bad Emacs advice for new users.
  3. Pierre Neirhardt's configuration implements lazy loading without help of external packages. I have stolen his approach of using lazy loading to silently ignore the setup stanzas of uninstalled extension packages.
  4. Sacha Chua's configuration is a practical example of producing the Emacs initialization files by tangling an org file. It gives me the impression that she is a very practical person trying to achieve her goals by the most efficient means.
  5. Steve Purcell's configuration is well organized and a showcase of readable code. Its commit and issue histories are also helpful: see for instance the discussion The order of company candidates is incorrect in Emacs lisp mode.

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

  1. Mastering Emacs is a link to a blog with many 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 many 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)

(provide 'early-init)
;; Emacs looks for "Local variables:" after the last "?\n?\f".

;; 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{(describe-variable #'load-prefer-newer t)}
  2. src_emacs-lisp{(apropos-library "no-littering")}
  3. 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.

Init File (info) header

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

The quoting (info) and the backquote (info) pages explain how to understand the ' (quote), ` (backquote), , (substitute) and @, (splice) in in listing lst:1st-setopt-call and lst:2nd-setopt-call. A tutorial of how to use those reader macros is the didactic emacs-lisp macro example.

The init file (info) does not load the custom-file in spite of the recommendation of saving customizations (info).

;;; init.el --- user init file                    -*- lexical-binding: t -*-
;;; Commentary:
;;; Code:
(require 'cl-lib)

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

(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")
 epg-pinentry-mode 'loopback
 global-hl-line-mode t
 global-hl-line-sticky-flag t
 history-delete-duplicates t
 history-length 500
 indent-tabs-mode nil
 inhibit-startup-buffer-menu t
 inhibit-startup-screen t
 initial-buffer-choice t
 initial-scratch-message ""
 insert-directory-program (or (executable-find "gls")
                              (executable-find "ls"))
 kill-ring-max 300
 mode-line-compact 'long
 next-error-message-highlight t
 recentf-mode t
 save-place-mode t
 scroll-bar-mode nil
 tab-always-indent 'complete
 tab-width 8
 tool-bar-mode nil
 url-cookie-trusted-urls nil
 url-cookie-untrusted-urls '(".*")
 use-dialog-box nil
 use-short-answers t
 view-read-only t)

(when (eq system-type 'darwin)
  (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)))
(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/"))
 ;; Pin packages to GNU ELPA for info or stability.
 package-pinned-packages  '((auctex . "gnu")
                            (compat . "gnu")
                            (consult . "gnu-devel")
                            (embark . "gnu-devel")
                            (embark-consult . "gnu-devel")
                            (engrave-faces . "gnu-devel")
                            (hyperbole . "gnu-devel")
                            (marginalia . "gnu-devel")
                            (org . "gnu-devel")
                            (osm . "gnu-devel")
                            (parsebib . "melpa-stable")
                            (queue . "gnu")
                            (rainbow-mode . "gnu")
                            (spinner . "gnu")
                            (xr . "gnu")
                            (vertico . "gnu-devel"))
 package-selected-packages '(async         ; asynchroneous processing
                             debbugs       ; access the GNU bug tracker
                             no-littering  ; keep `user-emacs-directory' clean
                             org           ; thought organizer
                             wgrep))       ; open a writable grep buffer

Install the selected packages (info)

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

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

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 installs Org (GNU-devel ELPA archive) early to shadow the built-in package while preventing collisions between the snapshot and built-in packages.
  3. It calls src_emacs-lisp{(package-install-selected-packages)} to check the installation status of all packages in src_emacs-lisp{package-selected-packages} and to install the missing packages after the user has agreed to its prompt.
  4. It defines a function to ensure the installation of packages in other source blocks. This allows skipping installation in sections with a :noexport: tag by disallowing tangling.

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

(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)
    ;; https://emacs.stackexchange.com/a/45939 answers
    ;; "How to shadow automatically a built-in package by installing it?"
    (defun shadow-builtin-by-install (pkg)
      (when-let ((desc (cadr (assq pkg package-archive-contents))))
        (package-install desc 'dont-select)))
    ;; Shadow built-in `org' by installing `org'.
    (shadow-builtin-by-install 'org)
    ;; 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

(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 "https://github.com/tree-sitter/tree-sitter-go" "master" "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")
     (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")))

  (add-to-list 'auto-mode-alist
               `(,(rx ".py" (opt (any "iw")) eos) . python-ts-mode)))

Text faces or styles (info)

This setup does not use custom themes (info) to avoid endless tweaking, but it does a minimal setup of fonts and faces. See 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 source implements those rules. Listing lst:set-default-face-height shows that font scaling is easy in case of proper initialization of all face heigths. Listing lst:fix-gtk-color-for-invert-default-face, lst:shadow-org-font-lock-faces, and lst:shadow-org-font-lock-faces show a few tweaks to improve visibility without theming:

  1. Fixing the gtk background color of the already loaded region face.
  2. Toggling between a dark and light background by means of src_emacs-lisp{(invert-default-face)}.
  3. Shadowing the definition of faces before loading. The last item in the page on fontifying Org mode source code blocks describes this method.
(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
  (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 heigth (%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))))
;; Use proportional font faces in current buffer
(defun set-buffer-variable-pitch-face ()
  "Set a variable width (proportional) font in current buffer."
  (interactive)
  (setq 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 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)
(with-eval-after-load 'emacs
  (defun fix-gtk-region-face-background-color ()
    (when (featurep 'gtk)
      (set-face-attribute
       'region nil
       :background (cdr (assoc (face-attribute 'default :background)
                               '(("white" . "LightGoldenrod2")
                                 ("black" . "blue3")))))))

  (defun invert-default-face ()
    "Invert the default face."
    (interactive)
    (invert-face 'default)
    (fix-gtk-region-face-background-color))

  (fix-gtk-region-face-background-color))
(with-eval-after-load 'emacs
  ;; 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 various 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'.")

  (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."))
(with-eval-after-load 'emacs
  ;; 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."))

Window management (info)

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

(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 (buffer-name action)
      (with-current-buffer buffer-name (apply #'derived-mode-p major-modes))))

   (keymap-global-set "M-o" #'other-window))
(with-eval-after-load 'emacs
  (winner-mode +1))
(with-eval-after-load 'emacs
  ;; https://www.masteringemacs.org/article/demystifying-emacs-window-manager
  (when (version< "29.0.0" emacs-version)
    (setopt switch-to-buffer-in-dedicated-window 'pop
            switch-to-buffer-obey-display-actions t))

  (add-to-list 'display-buffer-alist
               `(,(rx (or "*Apropos*"
                          "*Help*"
                          "*info"))
                 (display-buffer-reuse-window display-buffer-pop-up-window)
                 (inhibit-same-window . nil)))
  (add-to-list 'display-buffer-alist
               `(,(rx (or "*Occur*"
                          "*grep*"
                          "*xref*"))
                 display-buffer-reuse-window
                 (inhibit-same-window . nil)))
  (add-to-list 'display-buffer-alist
               '("\\*compilation\\*"
                 display-buffer-no-window
                 (allow-no-window . t)))
  (add-to-list 'display-buffer-alist
               '("^\\*Dictionary\\*" display-buffer-in-side-window
                 (side . left)
                 (window-width . 70))))

Advising functions (info)

(with-eval-after-load 'emacs
  (defun advice-toggle (symbol where function &optional props)
    "Toggle between states after `advice-remove' and `advice-add'."
    (let ((how "%s `%s' advice `%s' %s `%s'"))
      (if (advice-member-p function symbol)
          (progn
            (message how "Removal of" where function "from" symbol)
            (advice-remove symbol function))
        (message how "Addition of" where function "to" symbol)
        (advice-add symbol where function props)))))
(with-eval-after-load 'emacs
  (defun toggle-eww-display-pdf-around ()
    "Toggle `eww-display-advice' advice."
    (interactive)
    (advice-toggle 'eww-display-pdf :around #'eww-display-pdf-as-binary))

  (defun toggle-ilog-timer-function-after ()
    "Toggle `ilog-timer-function' advice."
    (interactive)
    (advice-toggle 'ilog-timer-function :after #'ilog-ensure-ilog-buffer-window))

  (defun toggle-keycast-log-update-buffer-override ()
    "Toggle `keycast-log-update-buffer' advice."
    (interactive)
    (advice-toggle 'keycast-log-update-buffer
                   :override #'keycast-log-update-buffer-plain))

  (defun toggle-org-babel-python-format-session-value-override ()
    "Toggle `org-babel-python-format-session-value' advice."
    (interactive)
    (advice-toggle 'org-babel-python-format-session-value
                   :override #'org-babel-python-format-session-value-override)))

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 an poorly identified 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 pooly identified 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 'files
  ;; Ensure the use of `GNU-ls' from `coreutils' on darwin.
  (setopt insert-directory-program (or (executable-find "gls")
                                       (executable-find "ls"))))

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

Listing lst:extra-dired-key-bindings adds new key bindings to dired-mode-map to:

  1. Open files with the Emacs Web Wowser inside Emacs.
  2. Let rsync copy marked files outside Emacs to a local or remote directory.

The link asynchoronous execution library for Emacs Dired is the original source for the idea of using rsync and the blog articles using rsync in dired and asynchronous rsync with Emacs, dired and tramp are vivid recommendations written by experienced Emacs users.

(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))))

  ;; https://truongtx.me/tmtxt-dired-async.html
  (defun dired-rsync (target)
    "Copy marked files with `rsync' to TARGET directory."
    (interactive
     (list (expand-file-name
            (read-file-name "Rsync to:" (dired-dwim-target-directory)))))
    ;; Store all marked files into the `files' list and intialize
    ;; the `rsync-command'.
    (let ((files (dired-get-marked-files nil current-prefix-arg))
          (rsync-command "rsync -av --progress "))
      ;; Add all marked files as arguments to the `rsync-command'.
      (dolist (file files)
        (setq rsync-command
              (concat rsync-command
                      (if (string-match "^/ssh:\\(.*\\)$" file)
                          (format " -e ssh %s" (match-string 1 file))
                        (shell-quote-argument file)) " ")))
      ;; Append the destination directory to the `rsync-command'.
      (setq rsync-command
            (concat rsync-command
                    (if (string-match "^/ssh:\\(.*\\)$" target)
                        (format " -e ssh %s" (match-string 1 target))
                      (shell-quote-argument target))))
      ;; Run the async shell command.
      (async-shell-command rsync-command)
      ;; Finally, switch to that window.
      (other-window 1)))

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

Minibuffer completion styles (info)

Listing set minibuffer options implements ideas of the post Understanding minibuffer completion.

(when (require  'minibuffer nil 'noerror)
  ;; https://www.masteringemacs.org/article/understanding-minibuffer-completion
  (setopt
   completion-category-overrides '((file (styles basic substring)))
   completion-ignore-case nil
   completion-styles '(basic flex partial-completion substring)))

Processes (info)

Listing lst:process-utilities defines a function to run a (command-line) program with arguments and to obtain a list with its numeric exit status as well as its output to stdout.

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

Help (info)

Table tab:help-key-bindings lists a number of key bindings to start playing with the help facilities of Emacs. Try or

(eval

.

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

(defun org-key-binding-macro (cmd)
  (let* ((sym (intern-soft cmd))
         (keys (and (commandp sym) (where-is-internal sym nil 'first-only)))
         (prefix (seq-take keys (1- (length keys))))
         (keymap (key-binding prefix 'accept-default)))
    (format "%s %s %s {{{kbd(%s)}}}"
            cmd keymap (help--binding-locus keys nil) (key-description keys))))

(org-key-binding-macro 'consult-buffer)

Shortdoc-display-group (info)

Listing lst:configure-shortdoc binds

(eval

to short-doc-display-group and defines a short documentation group for functions defined in this Org file.

(when (fboundp 'shortdoc-display-group)
  (keymap-set help-map "y" #'shortdoc-display-group)
  (with-eval-after-load 'shortdoc
    ;; Ensure defining the functions before documenting them.
    (define-short-documentation-group init
      "Advice"
      (advice-toggle :no-manual t)
      (toggle-eww-display-pdf-around :no-manual t)
      (toggle-ilog-timer-function-after :no-manual t)
      (toggle-keycast-log-update-buffer-override :no-manual t)
      (toggle-org-babel-python-format-session-value-override :no-manual t)
      "Face"
      (invert-default-face :no-manual t)
      (set-default-face-height :no-manual t)
      "Interaction"
      (enable-this-command :no-manual t)
      (narrow-or-widen-dwim :no-manual t)
      (org-narrow-to-table :no-manual t)
      "LaTeX"
      (biber-delete-cache :no-manual t)
      (update-lualatex-opentype-font-name-database :no-manual t)
      "Org"
      (by-backend :no-manual t)
      (by-backend-kbd-org-macro :no-manual t)
      (org-active-current-time-stamp :nomanual t)
      (org-babel-execute:latex-extra-header :no-manual t)
      (org-babel-execute:latex-header :no-manual t)
      (org-electric-dollar :no-manual t)
      (org-eval-emacs-lisp-setup-blocks :no-manual t)
      (org-eval-python-setup-blocks :no-manual t)
      (org-eval-infixed-blocks :no-manual t)
      (org-latex-engraved-source-block-filter :no-manual t)
      (org-inactive-current-time-stamp :nomanual t)
      (org-syntax-convert-keyword-case-to-lower :no-manual t)
      (smart-latex-engrave-org-source-blocks :no-manual t)
      (zero-all-org-src-blocks-indentation :no-manual t)
      "Python"
      (choose-common-python-interpreter :no-manual t)
      (choose-common-python-linter :no-manual t))))

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 my "python.info" file.
  (add-to-list 'Info-directory-list
               (expand-file-name "~/.local/share/info")))

Key bindings (info)

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

Disabling Commands (info)

Execute src_emacs-lisp{(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 a disabled command is executed.
Enable it and re-execute it."
          (put this-command 'disabled nil)
          (message "You typed %s.  %s was disabled until now."
                   (key-description (this-command-keys)) this-command)
          (sit-for 0)
          (call-interactively this-command))))

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))

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 and #sec:qutebrowser-userscript show how to use emacsclient to:

  1. Install an asynchronous (or background) loop of saving a LaTeX file, compiling it, and redisplaying the output in Emacs.
  2. Make qutebrowser send html links with document titles to Emacs.

The code in listing lst:start-emacs-server starts the Emacs server.

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

Latexmk save-compile-display loop

The latexmk resource file in the next source code block 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 "newline-formfeed".

# Local Variables:
# mode: perl
# End:

Qutebrowser userscript

The next block contains an userscript that sends a store-link org-protocol message with the url and the title from qutebrowser to emacsclient. The function urlencode translates the url and the title for the message. The Python urllib examples show how to use urlencode. The final execvp call deals with a qutebrowser userscript requirement: the emacsclient process must get the PID of the userscript that must kill itself after the take-over. Termination of the emacsclient process hands control back to qutebrowser.

On a POSIX system, you can run the userscript from qutebrowser or from a terminal to see whether it works. In case you try to run it from Emacs, Emacs may hang or die.

#!/usr/bin/env python
from urllib.parse import urlencode
from os import environ, execvp

url = environ.get("QUTE_URL", "https://orgmode.org")
title = environ.get("QUTE_TITLE", "Org Mode")
parameters = urlencode({"url": url, "title": title})
print(payload := f"org-protocol://store-link?{parameters}")
execvp("emacsclient", ("-n", payload))

TODO Look into: org-protocol handling with other browser on Darwin

Completion

Vertico (info) provides a performant and minimalistic vertical completion UI based on the default completion system and behaves therefore correctly under all circumstances. Using Vertico, Marginalia, Consult, and Embark links to a video demonstration. Vertico integrates well with fully supported complementary packages to enrich the completion UI:

  1. Embark (info) for minibuffer actions with context menus,
  2. Marginalia (info) for rich annotations in the minibuffer, and
  3. Consult (info) for useful search and navigation commands,

where the order is that of enhancing citar's experience and the configuration steps below.

Finally, company: a modular complete-anything framework for Emacs provides completion in any buffer.

Company (info)

Company (info) is a modular completion framework and the the snippet below ensures its installation.

(ensure-package-installation 'company)

Vertico (info)

Listing lst:enable-vertico-mode configures and enables savehist-mode and enables vertico-mode. The documentation src_emacs-lisp{(describe-function 'savehist-mode)} why it is best to turn on savehist-mode in the Emacs init file.

(when (require 'savehist nil 'noerror)
  (setopt savehist-additional-variables '(eww-history
                                          kill-ring
                                          regexp-search-string
                                          search-ring
                                          search-string))
  (savehist-mode +1))
(when (and (ensure-package-installation 'vertico)
           (fboundp 'vertico-mode))
  (vertico-mode +1))
(with-eval-after-load 'vertico
  (keymap-set vertico-map "RET" #'vertico-directory-enter)
  (keymap-set vertico-map "DEL" #'vertico-directory-delete-char)
  (keymap-set vertico-map "M-DEL" #'vertico-directory-delete-word))
command remap keys
vertico-directory-delete-char

(eval

vertico-directory-delete-word

(eval

vertico-directory-enter

(eval

vertico-exit exit-minibuffer

(eval

vertico-exit-input

(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

(eval

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

Embark (info)

Listing bind embark commands binds embark commands to the global key map:

  1. embark-act prompts the user for an action and performs it.
  2. Except for highlighting of email and web URLs, embark-dwim englobes the functionality of src_emacs-lisp{(find-library "goto-addr")} that activates mail and web URLs to turn them into clickable buttons. Since embark-dwim acts on a superset of target types, it renders goto-addr superfluous. See:

    1. Goto Address mode (info).
    2. The default (embark-dwim) action on a target (info).
  3. embark-bindings allows to explore all current command key bindings in the minibuffer.
  4. The initialization src_emacs-lisples minibuffer help after a prefix key (for instance or

    (eval

    shows.
(when (ensure-package-installation 'embark 'embark-consult)
  (when (fboundp 'embark-act)
    (keymap-global-set "C-," #'embark-act))
  (when (fboundp 'embark-dwim)
    (keymap-global-set "C-:" #'embark-dwim))
  (when (fboundp 'embark-bindings)
    (keymap-global-set "C-h B" #'embark-bindings))
  (when (fboundp 'embark-prefix-help-command)
    (setq prefix-help-command #'embark-prefix-help-command)))

Marginalia (info)

Listing lst:enable-marginalia-mode enables marginalia-mode.

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

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.

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-outline goto-map

(eval

consult-register ctl-x-r-keymap

(eval

consult-yank-pop global-map

(eval

deadgrep search-map

(eval

elfeed global-map

(eval

embark-act global-map

(eval

embark-bindings global-map

(eval

embark-dwim global-map

(eval

iedit-mode global-map

(eval

minibuffer-complete-history minibuffer-local-map

(eval

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

(eval

org-agenda global-map

(eval

org-capture global-map

(eval

org-cite org-mode-map

(eval

org-insert-link-global global-map

(eval

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

(eval

org-store-link global-map

(eval

(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)
  ;; 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))

Company: a modular complete anything framework for Emacs

Listing lst:setup-company configures company.

(when (fboundp 'company-mode)
  ;; https://github.com/purcell/emacs.d/issues/778
  (setopt company-transformers '(company-sort-by-occurrence))
  (dolist (hook '(LaTeX-mode-hook
                  org-mode-hook
                  emacs-lisp-mode-hook
                  lisp-interaction-mode-hook
                  lisp-mode-hook
                  python-mode-hook
                  ielm-mode-hook
                  sly-mrepl-mode-hook))
    (add-hook hook #'company-mode)))

Search and replace (info)

Regexp Replace (info)

Executing

(eval

prompts fo a regular expression REGEXP and a substitution NEWSTRING to replace REGEXP with NEWSTRING. The tools in section Narrowing allow to limit the scope of

(eval

. 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 perfom 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+ \#)._

Deadgrep

Deadgrep uses ripgrep for superfast text searching in the default directory or the current VCS directory tree. Listing setup deadgrep commands binds deadgrep globally to

(eval

and deadgrep-edit-mode locally to

(eval

.

(when (and (ensure-package-installation 'deadgrep)
           (fboundp 'deadgrep))
  (keymap-set search-map "d" #'deadgrep)
  (with-eval-after-load 'deadgrep
    (keymap-set deadgrep-mode-map "C-c C-w" #'deadgrep-edit-mode)))

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.

(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>")
                                       " ")))
  (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.

(when (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 'emacs
  (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
  (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))

  (add-hook 'org-mode-hook #'ediff-with-org-show-all))

Git

How to push new branches to remote repositories on Github or Gitlab explains how to do this from the command line. Listing lst:forking-and-branching-remote-repositories shows an example work flow of command line git and nano invocations. I did yet not figure out how to do this by means of Magit (info): 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)}.

# Clone the repository
git clone git@github.com:gav451/engrave-faces engrave-faces-fork
cd engrave-face-fork
# Handle 1st branch:
# - Create and switch to a new branch with either:
git switch -c latex-engrave-symbolic-color-names
#   or: git checkout -b latex-engrave-symbolic-color-names
# - Use "--set-upstream origin" as a 1st-time branch push:
git push --set-upstream origin latex-engrave-symbolic-color-names
# - Create, add, and commit, and push changes in this branch:
nano engrave-faces-latex.el
git add engrave-faces-latex.el
git commit -m 'Commit latex-engrave-dont-symbolic-color-names changes'
git push
# - Switch to master branch:
git checkout master
# Handle 2nd branch:
# - Create and switch to a new branch with either:
git switch -c latex-engrave-dont-wrap-white-space
#   - or: git checkout -b latex-engrave-dont-wrap-white-space
# - Use "--set-upstream origin" as a 1st-time branch push:
git push --set-upstream origin latex-engrave-dont-wrap-white-space
# - Create, add, commit, and push changes in this branch:
nano engrave-faces-latex.el
git add engrave-faces-latex.el
git commit -m 'Commit latex-engrave-dont-wrap-white-space changes'
git push
# - Switch to master branch:
git checkout master

List all Git branches

git -C ~/VCS/engrave-faces-fork branch -a
* gav
  latex-engrave-dont-wrap-white-space
  latex-engrave-symbolic-color-names
  master
  remotes/origin/HEAD -> origin/master
  remotes/origin/gav
  remotes/origin/latex-engrave-dont-wrap-white-space
  remotes/origin/latex-engrave-symbolic-color-names
  remotes/origin/master

Magit (info)

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

(ensure-package-installation 'magit)

Reading

Reading DjVu files

This setup relies on Document View (info) to facilitate reading DjVu files. Reading the code shown by src_emacs-lisp{(find-function 'doc-view-djvu->tiff-converter-ddjvu)} shows that reading DjVu files requires the command line DjVu decoder ddjvu from the DjVuLibre utilities.

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 (and (ensure-package-installation 'nov)
           (fboundp 'nov-mode))
  (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. The saveplace-pdf-view package saves pdf-view and doc-view places.

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.

(when (and (ensure-package-installation 'pdf-tools 'saveplace-pdf-view)
           (fboundp 'pdf-loader-install))
  ;; `pdf-loader-install' is the lazy equivalent of `pdf-tools-install':
  ;; see the README file.
  (pdf-loader-install)

  (with-eval-after-load 'pdf-outline
    ;; Unmask the `image-save' key binding in `pdf-view-mode-map' and
    ;; in `image-mode-map' (by parenthood).
    (keymap-set pdf-outline-minor-mode-map "o" nil)
    (keymap-set pdf-outline-minor-mode-map "O" #'pdf-outline))

  (with-eval-after-load 'pdf-view
    (setopt pdf-view-display-size 'fit-page
            pdf-view-use-scaling (eq system-type 'darwin))
    (require 'saveplace-pdf-view nil 'noerror)))

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 LuaTeX or LuaLaTeX.
  2. Listing lst:set-tex-options also enables indenting between square brackets which is a recent feature.
  3. Listing lst:update-lualatex-opentype-font-name-database defines a function to update the OpenType Font name database for LuaLaTeX when the output of LuaLaTeX shows missing fonts.
  4. Listing lst:set-bibtex-options configures the Emacs bibtex library to use the LaTeX BiBTeX dialect for backwards compatibility.
  5. Listing lst:set-font-latex-options disables font scaling of section titles.
  6. Listing lst:set-latex-options 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-install-font-lock #'font-latex-setup
     TeX-parse-self t
     ;; Disable `TeX-electric-math' to prevent collisions with `smartparens'.
     TeX-electric-math nil)))
(with-eval-after-load 'emacs
  (defun update-lualatex-opentype-font-name-database ()
    "Update the \"OpenType Font\" name database for \"LuaLaTeX\"."
    (interactive)
    (cl-destructuring-bind (exit-code output)
        (shell-command-with-exit-code
         "luaotfload-tool" "-vv" "--update" "--force")
      (if (= 0 exit-code)
          (message "%s" (string-trim output))
        (error "%s" (string-trim output))))))
(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)))

Math-preview

Math-preview uses Mathjax-3 to display LaTeX, MathML,and asciimath in Emacs buffers with help of an external node.js program.

(when (ensure-package-installation 'math-preview)
  (with-eval-after-load 'math-preview
    ;; https://docs.mathjax.org/en/latest/input/tex/extensions/physics.html
    (let ((physics (split-string "
abs absolutevalue acomm acos acosecant acosine acot acotangent
acsc admat anticommutator antidiagonalmatrix arccos arccosecant
arccosine arccot arccotangent arccsc arcsec arcsecant arcsin
arcsine arctan arctangent asec asecant asin asine atan atangent
bmqty bqty Bqty bra braket comm commutator cos cosecant cosh
cosine cot cotangent coth cp cross crossproduct csc csch curl dd
derivative det determinant diagonalmatrix diffd differential div
divergence dmat dotproduct dv dyad erf ev eval evaluated exp
expectationvalue exponential expval fderivative fdv flatfrac
functionalderivative grad gradient gradientnabla hypcosecant
hypcosine hypcotangent hypsecant hypsine hyptangent
identitymatrix Im imaginary imat innerproduct ip ket ketbra
laplacian ln log logarithm matrixdeterminant matrixel
matrixelement matrixquantity mdet mel mqty naturallogarithm norm
op order outerproduct partialderivative paulimatrix pb
pderivative pdv pmat pmqty Pmqty poissonbracket pqty Pr
principalvalue Probability pv PV qall qand qas qassume qc qcc
qcomma qelse qeven qfor qgiven qif qin qinteger qlet qodd qor
qotherwise qq qqtext qsince qthen qty quantity qunless qusing
rank Re real Res Residue sbmqty sec secant sech sin sine sinh
smallmatrixquantity smdet smqty spmqty sPmqty svmqty tan tangent
tanh tr Tr trace Trace va var variation vb vdot vectorarrow
vectorbold vectorunit vmqty vnabla vqty vu xmat xmatrix
zeromatrix zmat")))
      (cl-pushnew `("physics" . ,physics)
                  math-preview-tex-packages-autoload-packages))
    (setq math-preview-raise 0.5
          math-preview-scale 1
          math-preview-tex-default-packages '("autoload" "ams" "physics"))
    (let ((command (executable-find "~/node_modules/.bin/math-preview")))
      (if command
          (setq math-preview-command command)
        ;; https://stackoverflow.com/a/17509764 answers:
        ;; How to install an npm package from github directly?
        (unless (file-directory-p "~/node_modules")
          (make-directory "~/node_modules"))
        (cl-destructuring-bind (exit-code output)
            (shell-command-with-exit-code
             "npm" "install"
             "git+https://gitlab.com/matsievskiysv/math-preview.git")
          (if (= 0 exit-code)
              (message "%s" (string-trim output))
            (error "%s" (string-trim output))))))))

TODO Improve the AUCTeX configuration slowly

Writing Markdown files

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

Writing Org (info) files

The Org Babel reference card complements section Working with Source Code (info) of the Org (info) manual.

Org activation (info)

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

Setup Org

I have split the initial Org-mode setup over several listings. Here, follows a list detailing and motivating each listing:

  1. Listing lst:set-org-options handles basic Org-mode options.
  2. Listing lst:setup-org-babel handles basic Org-mode options specific to work with source code (info).
  3. Listing lst:set-org-link-options handles Org-mode options specific to hyperlinks (info).
  4. Listing lst:setup-org-mode-map extends the org-mode-map with useful key-bindings.
  5. Listing lst:setup-org-src facilitates editing Python source code blocks, and it provides a function to remove the indentation of all org-src-mode blocks without -i switch.
  6. Listing lst:setup-ob-python allows to pretty-print Python session source block values with black instead of pprint.
  7. Listing lst:set-org-export-options selects the non-intrusive expert user interface for export dispatching.
  8. Listing lst:setup-org-for-lualatex-export and lst:set-ox-latex-options-for-lualatex-export configure Org-mode to generate output for the LuaLaTeX compiler.
  9. Listing lst:setup-org-latex-classes defines org-latex-classes (info) for backward compatibility. See table tab:org-latex-class-tag-placeholder and type

    (eval

    for an explanation of the code in listing lst:setup-org-latex-classes.
(with-eval-after-load 'org
  (setopt
   org-babel-load-languages
   `((C . t)
     (calc . t)
     (dot . ,(fboundp 'grapviz-dot-mode))
     (emacs-lisp . t)
     (eshell . t)
     (fortran . t)
     (gnuplot . ,(fboundp 'gnuplot-mode))
     (js . t)
     (latex . t)
     (lilypond . ,(fboundp 'lilypond-mode))
     (lisp . t)
     (lua . ,(fboundp 'lua-mode))
     (maxima . ,(fboundp 'maxima-mode))
     (org . t)
     (perl . t)
     ;; Postpone Python activation to prevent calling still unbound functions.
     (python . nil)
     (shell . t))
   org-export-backends '(ascii beamer html icalendar latex odt texinfo)
   org-file-apps '((auto-mode . emacs)
                   (directory . emacs)
                   ("\\.mm\\'" . default)
                   ("\\.x?html?\\'" . default)
                   ("\\.pdf\\'" . emacs))
   org-modules '(ol-bibtex
                 ol-doi
                 ol-eww
                 ol-info
                 org-id
                 org-protocol
                 org-tempo)))
(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")
   org-babel-latex-begin-env (lambda (_)
                               "\\begin{document}\n")
   org-babel-latex-end-env (lambda (_)
                             "\\end{document}\n")))

(with-eval-after-load 'ob-lisp
  ;; (with-eval-after-load 'slime
  ;;   (setopt org-babel-lisp-eval-fn #'slime-eval))
  (with-eval-after-load 'sly
    (setopt org-babel-lisp-eval-fn #'sly-eval))
  )

(with-eval-after-load 'emacs
  (defun set-org-babel-language-activity (lang active)
    "Set the `org-babel' activity of language LANG to ACTIVE.

Let `org-babel-do-load-languages' load ob-LANG when ACTIVE is t or
unbind the `org-babel' interface functions when ACTIVE is nil."
    (if (locate-library (format "ob-%s" lang))
        (or (org-babel-do-load-languages
             'org-babel-load-languages
             (cons (cons lang active)
                   (assq-delete-all
                    lang (default-toplevel-value 'org-babel-load-languages))))
            org-babel-load-languages)
      (warn "Can't locate `ob-%s', hence don't set `%s' activity" lang lang)))

  (defun activate-org-babel-language (lang)
    "Activate LANG for `org-babel'."
    (interactive "SActivate language: ")
    (if (set-org-babel-language-activity lang t)
        (message "Did activate `%s' for `org-babel'" lang)
      (message "Can't activate `%s' for `org-babel'" lang)))

  (defun deactivate-org-babel-language (lang)
    "Activate LANG for `org-babel'."
    (interactive "SDeactivate language: ")
    (if (set-org-babel-language-activity lang nil)
        (message "Did deactivate `%s' for `org-babel'" lang)
      (message "Can't deactivate `%s' for `org-babel'" lang))))
(with-eval-after-load 'ol
  (setopt org-link-file-path-type 'relative))
(with-eval-after-load 'emacs
  ;; 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)))

  (defun org-active-current-time-stamp ()
    "Insert an active date-time stamp of the current time."
    (interactive)
    (if (fboundp 'org-insert-time-stamp)
        (org-insert-time-stamp (current-time) 'with-hm)
      (let ((stamp (format-time-string "<%Y-%m-%d %a %H:%M>" (current-time))))
        (insert stamp)
        stamp)))

  (defun org-inactive-current-time-stamp ()
    "Insert an inactive date-time stamp of the current time."
    (interactive)
    (if (fboundp 'org-insert-time-stamp)
        (org-insert-time-stamp (current-time) 'with-hm 'inactive)
      (let ((stamp (format-time-string "[%Y-%m-%d %a %H:%M]" (current-time))))
        (insert stamp)
        stamp)))

  (with-eval-after-load 'org
    (setopt org-return-follows-link t)
    (keymap-set org-mode-map "C-c <SPC>" #'org-inactive-current-time-stamp)
    (keymap-set org-mode-map "C-c C-<SPC>" #'org-active-current-time-stamp)
    (keymap-set org-mode-map "$" #'org-electric-dollar)
    (keymap-set org-mode-map "M-q" #'org-fill-paragraph)))
(with-eval-after-load 'emacs
  (with-eval-after-load 'org-src
    (setopt org-src-preserve-indentation t))

  (declare-function org-babel-map-src-blocks "ext:ob-core" (file &rest body))
  (declare-function org-do-remove-indentation "ext:org-macs" (n skip-fl))

  (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
  (defun org-babel-python-format-session-value-override
      (src-file result-file result-params)
    "Return Python code to evaluate SRC-FILE and write result to RESULT-FILE.
Use `black' instead of `pprint' when \"pp\" is a member of RESULT-PARAMS."
    (format "\
import ast
with open('%s') as __org_babel_python_tmpfile:
    __org_babel_python_ast = ast.parse(__org_babel_python_tmpfile.read())
__org_babel_python_final = __org_babel_python_ast.body[-1]
if isinstance(__org_babel_python_final, ast.Expr):
    __org_babel_python_ast.body = __org_babel_python_ast.body[:-1]
    exec(compile(__org_babel_python_ast, '<string>', 'exec'))
    __org_babel_python_final = eval(
        compile(ast.Expression(__org_babel_python_final.value), '<string>', 'eval')
    )
    with open('%s', 'w') as __org_babel_python_tmpfile:
        if %s:
            import black
            __org_babel_python_tmpfile.write(
                black.format_str(repr(__org_babel_python_final), mode=black.Mode())
            )
        else:
            __org_babel_python_tmpfile.write(str(__org_babel_python_final))
else:
    exec(compile(__org_babel_python_ast, '<string>', 'exec'))
    __org_babel_python_final = None"
            (org-babel-process-file-name src-file 'noquote)
            (org-babel-process-file-name result-file 'noquote)
            (if (member "pp" result-params) "True" "False")))

  (advice-add 'org-babel-python-format-session-value
              :override #'org-babel-python-format-session-value-override))
(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
   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))))
(with-eval-after-load 'ox-latex
  (setopt
   org-latex-compiler "lualatex"
   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-src-block-backend 'minted
   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
   org-latex-subtitle-separate t))
(with-eval-after-load 'ox-latex
  (mapc (function (lambda (element)
                    (add-to-list 'org-latex-classes element)))
        (nreverse
         (quote (("elsarticle-1+2+3"	; Elsevier journals
                  "\\documentclass{elsarticle}
[NO-DEFAULT-PACKAGES]
[PACKAGES]
[EXTRA]"
                  ("\\section{%s}" . "\\section*{%s}")
                  ("\\subsection{%s}" . "\\subsection*{%s}")
                  ("\\subsubsection{%s}" . "\\subsubsection*{%s}")
                  ("\\paragraph{%s}" . "\\paragraph*{%s}")
                  ("\\subparagraph{%s}" . "\\subparagraph*{%s}"))
                 ("article-1+2+3"
                  "\\documentclass{article}
[NO-DEFAULT-PACKAGES]
[PACKAGES]
[EXTRA]"
                  ("\\section{%s}" . "\\section*{%s}")
                  ("\\subsection{%s}" . "\\subsection*{%s}")
                  ("\\subsubsection{%s}" . "\\subsubsection*{%s}")
                  ("\\paragraph{%s}" . "\\paragraph*{%s}")
                  ("\\subparagraph{%s}" . "\\subparagraph*{%s}"))
                 ("report-1+2+3"
                  "\\documentclass[11pt]{report}
[NO-DEFAULT-PACKAGES]
[PACKAGES]
[EXTRA]"
                  ("\\part{%s}" . "\\part*{%s}")
                  ("\\chapter{%s}" . "\\chapter*{%s}")
                  ("\\section{%s}" . "\\section*{%s}")
                  ("\\subsection{%s}" . "\\subsection*{%s}")
                  ("\\subsubsection{%s}" . "\\subsubsection*{%s}"))
                 ("book-1+2+3"
                  "\\documentclass[11pt]{book}
[NO-DEFAULT-PACKAGES]
[PACKAGES]
[EXTRA]"
                  ("\\part{%s}" . "\\part*{%s}")
                  ("\\chapter{%s}" . "\\chapter*{%s}")
                  ("\\section{%s}" . "\\section*{%s}")
                  ("\\subsection{%s}" . "\\subsection*{%s}")
                  ("\\subsubsection{%s}" . "\\subsubsection*{%s}")))))))

#+caption[The relation tag-placeholder in listing lst:setup-org-latex-classes]:

tag placeholder
+1 [DEFAULT-PACKAGES]
+2 [PACKAGES]
+3 [EXTRA]
-1 [NO-DEFAULT-PACKAGES]
-2 [NO-PACKAGES]
-3 [NO-EXTRA]

Debug setting org-babel-load-languages options

(defun all-org-babel-execute-fns ()
  "Find `ob-LANGUAGE' files in Org defining `org-babel-execute:LANGUAGE'.

Return a list of items where the filename is the `car' of each item and the
`cdr' of each item lists the `org-babel-execute:LANGUAGE' functions."
  (let* ((dir (file-name-parent-directory (locate-library "org")))
         (names (directory-files dir t (rx "ob-" (+ print)  ".el" eos))))
    (cl-loop for name in names
             for found = (has-org-babel-execute-fn name)
             when found collect found)))

(defun has-org-babel-execute-fn (name)
  (let* ((buffer (find-file-noselect name))
         (base (file-name-base (buffer-file-name buffer)))
         (regexp (rx "defun" (+ blank) "org-babel-execute:" (group (+ graphic))))
         (matches))
    (save-match-data
      (save-excursion
        (with-current-buffer buffer
          (save-restriction
            (widen)
            (goto-char 1)
            (while (re-search-forward regexp nil t 1)
              (push (match-string-no-properties 1) matches))))))
    (when matches
      `(,base ,@matches))))

(all-org-babel-execute-fns)

#+caption[Org Babel libraries with org-babel-execute:language functions]:

(("ob-C" "C" "D" "C++" "cpp")
 ("ob-R" "R")
 ("ob-awk" "awk")
 ("ob-calc" "calc")
 ("ob-clojure" "clojurescript" "clojure")
 ("ob-css" "css")
 ("ob-ditaa" "ditaa")
 ("ob-dot" "dot")
 ("ob-emacs-lisp" "emacs-lisp")
 ("ob-eshell" "eshell")
 ("ob-forth" "forth")
 ("ob-fortran" "fortran")
 ("ob-gnuplot" "gnuplot")
 ("ob-groovy" "groovy")
 ("ob-haskell" "haskell")
 ("ob-java" "java")
 ("ob-js" "js")
 ("ob-julia" "julia")
 ("ob-latex" "latex")
 ("ob-lilypond" "lilypond")
 ("ob-lisp" "lisp")
 ("ob-lua" "lua")
 ("ob-makefile" "makefile")
 ("ob-maxima" "maxima")
 ("ob-ocaml" "ocaml")
 ("ob-octave" "octave" "matlab")
 ("ob-org" "org")
 ("ob-perl" "perl")
 ("ob-plantuml" "plantuml")
 ("ob-processing" "processing")
 ("ob-python" "python")
 ("ob-ruby" "ruby")
 ("ob-sass" "sass")
 ("ob-scheme" "scheme")
 ("ob-screen" "screen")
 ("ob-sed" "sed")
 ("ob-shell" "shell")
 ("ob-sql" "sql")
 ("ob-sqlite" "sqlite"))
Org Babel libraries with org-babel-execute:language functions.

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. In combination with vertico, embark, marginalia, and consult, Citar provides quick filtering and selecting of bibliographic entries from the minibuffer as well as the option to run different commands on those selections. 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 convertor) to an Emacs setup. Listing lst:set-org-cite+citar-options shows my org-cite, citar, and org setup.

(with-eval-after-load 'oc
  ;; Org-9.5 needs the requirements, but Org-9.6 does not.
  (when (version< (org-version) "9.6")
    (require 'oc-biblatex)
    (require 'oc-csl)))

(with-eval-after-load 'org
  (keymap-set org-mode-map "C-c b" #'org-cite-insert))

(when (ensure-package-installation 'citar 'citar-embark)
  (with-eval-after-load 'embark
    (when (fboundp 'citar-embark-mode)
      (citar-embark-mode +1)))

  (when (require 'citar nil 'noerror)
    (setopt citar-bibliography '("~/VCS/research/refs.bib")
            citar-library-file-extensions '("djvu" "pdf")
            citar-library-paths '("~/VCS/research/papers/")))

  (with-eval-after-load 'oc
    (setopt org-cite-export-processors '((latex biblatex)
                                         (t csl))
            org-cite-global-bibliography '("~/VCS/research/refs.bib"))

    (when (require 'citar nil 'noerror)
      (setopt org-cite-activate-processor 'citar
              org-cite-follow-processor 'citar
              org-cite-insert-processor 'citar))))

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 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. Navigate to a bibliographic entry to insert:

      1. Select it with

        (eval

        to select more entries.
      2. Select it with

        (eval

        to select the last entry.
  2. View an electronic copy of an Org-mode cite link:

    1. Move point to the Org-mode cite link.
    2. Type .
    3. Navigate to the corresponding library file.
    4. Select it.
  3. Open the DOI of an Org-mode cite link:

    1. Move point to the Org-mode cite link.
    2. Type .
    3. Navigate to the corresponding link.
    4. Select it.

TODO Compare bibtex and biblatex

  1. Using bibtex: a short guide
  2. The biblatex package
  3. Rebiber: A tool for normalizing bibtex with official info
  4. A biblatex implementation of the AIP and APS bibliography style
(with-eval-after-load 'emacs
  ;; https://tex.stackexchange.com/a/579356 answers
  ;; "How to solve Biber exiting with error code 2 but no error messages?"
  (defun biber-delete-cache ()
    "Delete the `Biber' cache to get rid of `Biber' exit code 2."
    (interactive)
    (cl-destructuring-bind (exit-code output)
        (shell-command-with-exit-code "rm" "-rf" "$(biber --cache)")
      (if (= 0 exit-code)
          (message "%s" (string-trim output))
        (error "%s" (string-trim output))))))

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 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)
    (let ((enter "^\\\\begin{Code}\n\\\\begin{Verbatim}")
          (leave "^\\\\end{Verbatim}\n\\\\end{Code}"))
      ;; Transform only blocks matching the enter regexp at position 0.
      ;; Do not transform blocks that are listing environments.
      (when (and (string-match enter data) (eql 0 (match-beginning 0)))
        (setq data (replace-match
                    "\\begin{Breakable}\n\\begin{Verbatim}" t t data))
        (when (string-match leave data (match-end 0))
          (setq data (replace-match
                      "\\end{Verbatim}\n\\end{Breakable}" t t data))
          (when (string-match enter data)
            (user-error "The `enter' regexp `%s' should not match" enter))
          (when (string-match leave data)
            (user-error "The `leave' regexp `%s' should not match" leave))
          data)))))
(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}}
% In case engrave-faces-latex-gen-preamble has not been run.
\\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
  (make-variable-buffer-local '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 "newline-formfeed".
  (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)))

Easy LaTeX preamble editing

There are at least two ways (new and old) to edit the LateX preamble latex_header and latex-extra_header export options easily in LaTeX source or export blocks. This Org (info) file uses the new way, but keeps the old way for backwards compatibility.

The new way exploiting an idea of Omar Antolin Camarena is to code new <LANGUAGE>-modes allowing to edit in LaTeX mode and to export to LaTeX code with correct LaTeX preamble export setting prefixes. Here, are links to three posts exposing his idea:

  1. Export LaTeX macros to LaTeX and HTML/MathJax preambles (reddit),
  2. Export JavaScript source blocks to script tags in HTML (reddit),
  3. Export JavaScript source blocks to script tags in HTML (SX).

Listing lst:org-babel-latex-header-blocks implements this way by means of two new <LANGUAGE>-modes: latex-header and latex-extra-header.

(with-eval-after-load 'emacs
  (defun prefix-all-lines (prefix body)
    (with-temp-buffer
      (insert body)
      (string-insert-rectangle (point-min) (point-max) prefix)
      (buffer-string)))

  (defun org-babel-execute:latex-extra-header (body _params)
    "Execute a block of LaTeX extra header lines with org-babel.
This function is called by `org-babel-execute-src-block' and
prefixes all lines with \"#+latex_extra_header: \"."
    (prefix-all-lines "#+latex_extra_header: " body))

  (defun org-babel-execute:latex-header (body _params)
    "Execute a block of LaTeX header lines with org-babel.
This function is called by `org-babel-execute-src-block' and
prefixes all lines with \"#+latex_header: \"."
    (prefix-all-lines "#+latex_header: " body))

  (defvar org-babel-default-header-args:latex-extra-header
    '((:exports . "results") (:results . "raw")))

  (defvar org-babel-default-header-args:latex-header
    '((:exports . "results") (:results . "raw")))

  (with-eval-after-load 'org-src
    (setopt org-src-window-setup 'current-window)

    (add-to-list 'org-src-lang-modes '("toml" . conf-toml))
    (add-to-list 'org-src-lang-modes '("latex-header" . latex))
    (add-to-list 'org-src-lang-modes '("latex-extra-header" . latex))))

The old way is to use a special export attribute as in the function org-latex-header-blocks-filter in ox-extra.el. Apparently, nobody is using this broken function (broken, since it relies on support only in org-mode before 2014-11-11). Listing lst:org-latex-header-blocks-filter proposes a fix for org-latex-header-blocks-filter.

(with-eval-after-load 'ox
  (defun org-latex-header-blocks-filter (backend)
    "Convert marked LaTeX export blocks to \"#+latex_header: \" lines.
The marker is a line \"#+header: :header yes\" preceding the block.

For instance, the LaTeX export block

,#+header: :header yes
,#+begin_export latex
% This line converts to a LaTeX header line.
,#+end_export

converts to

\"#+latex_header: % This line converts to a LaTeX header line.\"."
    (when (org-export-derived-backend-p backend 'latex)
      (let ((blocks
             (org-element-map
                 (org-element-parse-buffer 'greater-element nil) 'export-block
               (lambda (block)
                 (let ((type (org-element-property :type block))
                       (header (org-export-read-attribute :header block :header)))
                   (when (and (string= type "LATEX") (string= header "yes"))
                     block))))))
        (mapc (lambda (block)
                ;; Set point to where to insert LaTeX header lines
                ;; after deleting the block.
                (goto-char (org-element-property :post-affiliated block))
                (let ((lines
                       (split-string (org-element-property :value block) "\n")))
                  (delete-region (org-element-property :begin block)
                                 (org-element-property :end block))
                  (dolist (line lines)
                    (insert (concat "#+latex_header: "
                                    (replace-regexp-in-string "\\` *" "" line)
                                    "\n")))))
              ;; Reverse to go upwards to avoid wrecking the list of
              ;; block positions in the file that would occur in case
              ;; of going downwards.
              (reverse blocks)))))

  ;; Push the filter on the hook.
  (cl-pushnew #'org-latex-header-blocks-filter
              org-export-before-parsing-hook))

This file uses the new way, while keeping the old way for backwards compatibility, because the new way feels less hackish than the old way. A practical difference is that new way source blocks (contrary to old way export blocks) do not work in #+SETUPFILE: <FILE>, but only in #+INCLUDE: <FILE> files.

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
     (html (format "@@html:<kbd>%s</kbd>@@" (htmlize-protect-string keys)))
     (latex (format "@@latex:\\colorbox{PowderBlue}{\\texttt{%s}}@@" keys)))))

File inclusion (info) and Noweb (info) trickery

Note: How to include LaTeX packages in a LaTeX export block.

Listing lst:source-file-export-keyword-settings shows the preamble lines of this /gav451/emacs.d/src/commit/b3518382bb99624c6686b27eb01abc86f82736f6/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 generate the LaTeX preamble. All listings in this section exists thanks to file inclusion (info) and noweb (info) trickery.

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

Advanced LaTeX export settings

Listing lst:ox-latex-emacs-lisp-setup initializes the buffer local variables org-latex-classes, org-latex-title-command, org-latex-toc-command, and org-latex-subtitle-format. Listing /gav451/emacs.d/src/commit/b3518382bb99624c6686b27eb01abc86f82736f6/lst/title-page is a template to initialize org-latex-title-command. Type

(eval

,

(eval

}},

(eval

, and

(eval

to read 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
  (make-variable-buffer-local 'org-latex-classes)
  (cl-pushnew '("article-local"
                "\\documentclass[11pt]{article}
[NO-DEFAULT-PACKAGES]
[PACKAGES]
[EXTRA]"
                ("\\section{%s}" . "\\section*{%s}")
                ("\\subsection{%s}" . "\\subsection*{%s}")
                ("\\subsubsection{%s}" . "\\subsubsection*{%s}")
                ("\\paragraph{%s}" . "\\paragraph*{%s}")
                ("\\subparagraph{%s}" . "\\subparagraph*{%s}"))
              org-latex-classes :key #'car :test #'equal)

  (make-variable-buffer-local 'org-latex-title-command)
  (setq org-latex-title-command (concat title-page))

  (make-variable-buffer-local 'org-latex-toc-command)
  (setq org-latex-toc-command "
\\tableofcontents\\label{toc}
\\listoflistings
\\listoftables
\\newpage
")

  (make-variable-buffer-local 'org-latex-subtitle-format)
  (setq org-latex-subtitle-format ""))
\begin{titlepage}
  %% 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}

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
  (when (require 'pp nil 'noerror)
    (defconst grok-org-output
      "*Grok Org Element Output*"
      "Grok Org output buffer name.")

    (defun grok-org-element-at-point ()
      (interactive)
      (pp-display-expression
       (org-element-at-point) grok-org-output))

    (defun grok-org-element-context ()
      (interactive)
      (pp-display-expression
       (org-element-context) grok-org-output))

    (defun grok-org-element-parse-buffer ()
      (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-heading-components ()
      (interactive)
      (pp-display-expression
       (org-heading-components) 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{global-abbrev-table} and to store it in src_emacs-lisp{abbrev-file-name} for git management. I can change the abbreviation definitions in this file by means of:

  1. Execute src_emacs-lisp{(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))

Listing lst:word-games defines the anagram-p function that migth be used games.

;; https://funcall.blogspot.com/2022/07/lets-play-wordle.html
;; https://github.com/tabatkins/wordle-list
(defun anagram-p (evil vile)
  "Check whether strings EVIL and VILE are anagrams of each other."
  (if (string= evil vile)
      nil
    (string= (cl-sort evil #'<) (cl-sort vile #'<))))

;; https://funcall.blogspot.com/2020/01/palindromes-redux-and-sufficiently.html
;; https://irreal.org/blog/?p=8570
(defun palindrome-p (word)
  "Check whether the string WORD is a palindrome."
  (cl-do ((head 0 (1+ head))
          (tail (1- (length word)) (1- tail))
          (ok t))
      ((or (not ok) (>= head tail) ok)
    (setq ok (= (aref word head) (aref word tail))))))

DICT.org

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.

(when (and (ensure-package-installation 'writegood-mode)
           (fboundp 'writegood-mode))
  (global-set-key (kbd "C-c g") #'writegood-mode))

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.0.60. The following listings contribute to a programming language mode independent Eglot configuration:

  1. Listing minimal Eglot setup ensures installation of Eglot, shows how to get debug information from the Python LSP server, and adds key bindings to eglot-mode-keymap.
  2. Listing lst:help-setup-org-src-mode-for-eglot and lst:setup-python-org-src-mode-for-eglot try to prepare any org-src-mode buffers for use with Eglot. They are a refactored implementation of the post Using rustic, eglot, and org-babel with LSP support in Emacs for my use with Python.
  3. Listing lst:narrow-format-all-python defines a function to format only the narrowed region of Python org-src-mode blocks prepared by means of the code in listing lst:setup-python-org-src-mode-for-eglot. Do not use format-all-buffer on such buffers, since format-all-buffer does not handle the narrowing.
  4. Listing lst:eglot-maybe-ensure starts Eglot in case of proper programming modes and proper directory local variables (meaning in presence of a proper file .dir-locals.el in the root directory of any project using proper programming modes).
(with-eval-after-load 'emacs
  ;; Replace `nil' with `t' for debugging.
  (when nil
    (when (require 'eglot nil t)
      (setq eglot-server-programs
            `(((python-ts-mod python-mode)
               . ,(eglot-alternatives
                   '(("pylsp" "-vv")
                     ("ruff-lsp")))))))))

(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))
(with-eval-after-load 'emacs
  (defcustom eglot-maybe-ensure-modes '(python-mode)
    "Modes where maybe `eglot-ensure' should be or has been called.
This may be in the case of proper directory local variables or in
the case of proper `org-src-mode' buffers.")

  ;; https://www.reddit.com/r/emacs/comments/w4f4u3
  ;; /using_rustic_eglot_and_orgbabel_for_literate/
  (defun eglot-org-babel-edit-prep (info)
    "Try to setup an `org-mode-src' buffer to make `eglot-ensure' succeed.
INFO has a form similar to the return value of
`org-babel-get-src-block-info'.  Try to load the tangled file
into the `org-src-mode' buffer as well as to narrow the region to
the Org-mode source block code before calling `eglot-ensure'."
    (unless (bound-and-true-p org-src-mode)
      (user-error "Buffer %s is no `org-src-mode' buffer" (buffer-name)))
    (let ((mark (point))
          (body (nth 1 info))
          (filename (cdr (assq :tangle (nth 2 info)))))
      (when (string= filename "no")
        (user-error "Org source block has no tangled file"))
      (setq filename (expand-file-name filename))
      (unless (file-readable-p filename)
        (user-error "Tangled file %s is not readable" filename))
      (with-temp-buffer
        (insert-file-contents filename 'visit nil nil 'replace)
        (unless (search-forward body nil 'noerror)
          (user-error "Org source block does not occur in tangled file %s"
                      filename))
        (when (search-forward body nil 'noerror)
          (user-error "Org source block occurs twice or more in tangled file %s"
                      filename)))
      (goto-char (point-min))
      (insert-file-contents filename 'visit nil nil 'replace)
      (let ((max-point (search-forward body))
            (min-point (search-backward body)))
        (narrow-to-region min-point max-point))
      (goto-char mark)
      (eglot-ensure))))
(with-eval-after-load 'emacs
  ;; https://www.reddit.com/r/emacs/comments/w4f4u3
  ;; /using_rustic_eglot_and_orgbabel_for_literate/
  (defun undo-eglot-org-babel-edit-prep()
    "Undo the `eglot' setup by deleting the text hidden by narrowing.
This is to advice `org-edit-src-exit' and `org-edit-src-save'."
    (when (and (bound-and-true-p org-src-mode)
               (buffer-file-name)
               (apply #'derived-mode-p eglot-maybe-ensure-modes))
      (save-excursion
        (goto-char (point-min))
        (save-restriction
          (widen)
          (delete-region (point-min) (point)))
        (goto-char (point-max))
        (save-restriction
          (widen)
          (delete-region (point) (point-max))))))

  (defun org-babel-edit-prep:python (info)
    (eglot-org-babel-edit-prep info))

  (advice-add 'org-edit-src-exit :before #'undo-eglot-org-babel-edit-prep)
  (advice-add 'org-edit-src-save :before #'undo-eglot-org-babel-edit-prep))

#+caption[Experimental =narrow-format-all:python=]:

(when (fboundp 'format-all-buffer)
  (defun narrow-format-all:python ()
    "Format narrowed Python `org-src-mode' buffers correctly.
Use this function instead of `format-all-buffer' on `org-babel-edit-prep:python'
prepared buffers."
    (interactive)
    (when (eq major-mode 'python-mode)
      (let ((source-min (point-min))
            (source-max (point-max))
            (source (current-buffer))
            (target (get-buffer-create "*narrow-format-all:python*")))
        (with-current-buffer target
          (barf-if-buffer-read-only)
          (erase-buffer)
          (save-excursion
            (insert-buffer-substring-no-properties
             source source-min source-max))
          (python-mode)
          (setq-local format-all-formatters '(("Python" black)))
          (format-all-buffer)
          (let ((target-min (point-min))
                (target-max (point-max)))
            (with-current-buffer source
              (erase-buffer)
              (save-excursion
                (insert-buffer-substring-no-properties
                 target target-min target-max)))))))))
(with-eval-after-load 'emacs
  (defun eglot-maybe-ensure ()
    (when (and (apply #'derived-mode-p eglot-maybe-ensure-modes)
               (assoc 'eglot-workspace-configuration dir-local-variables-alist))
      (eglot-ensure)))

  ;; The two hooks `after-change-major-mode-hook' and
  ;; `hack-local-variables-hook' are OK, but language mode hooks like
  ;; `python-mode-hook' are not.
  (add-hook 'after-change-major-mode-hook #'eglot-maybe-ensure))

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)
  ;; (with-eval-after-load 'prog-mode
  ;;   (when (autoload 'format-all-ensure-formatter "format-all")
  ;;     (add-hook 'prog-mode-hook #'format-all-ensure-formatter)))
  (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.

Programming Modes

Common Lisp programming

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

  1. It configures sly-default-lisp and sly-lisp-implementations as in the sly documentation string instead of in the multiple lisps (info) manual.
  2. It ensures the automatic connection to the lisp server (info) when opening a Common Lisp file.
  3. It configures searching documentation in the Common Lisp HyperSpec according to the basic customization (info) manual.

Finally, listing lst:configure-sly uses a technique to load Slynk faster (info) by means of a custom core file in src_emacs-lisp{no-littering-var-directory}. Listing lst:sbcl-core-for-sly tangles to a script to dump such a SBCL core.

(when (ensure-package-installation 'slime)
  (with-eval-after-load 'slime
    (setq slime-default-lisp 'sbcl
          slime-lisp-implementations
          `((sbcl (,(executable-find "sbcl")
                   "--core"
                   ,(no-littering-expand-var-file-name "sbcl.core-for-sly")))))

    ;; (add-hook 'slime-mode-hook
    ;;           (defun on-slime-mode-hook ()
    ;;             (unless (slime-connected-p)
    ;;               (save-excursion (slime)))))

    (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 slime-prefix-map "M-h" #'slime-documentation-lookup)))
(when (ensure-package-installation 'sly '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-db-initial-restart-limit 10
            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")))))

    ;; (add-hook 'sly-mode-hook
    ;;           (defun on-sly-mode-hook ()
    ;;             (unless (sly-connected-p)
    ;;               (save-excursion (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

sbcl <<EOF
(mapc 'require '(sb-bsd-sockets sb-posix sb-introspect sb-cltl2 asdf))
(save-lisp-and-die "sbcl.core-for-sly")
EOF

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

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.")
;; Works in case of sly, but fails in case of slime.
(rplacd (assoc 'slynk:*string-elision-length* slynk:*slynk-pprint-bindings*)
        (ash 1 14))
(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<)))
(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"))

≠wpage

(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"))
(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 ()
  (setq font-lock-defaults cl-font-lock-defaults))

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

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)))
(let ((url "https://raw.githubusercontent.com/hellerve/sbcli/master/repl.lisp")
      (file "~/bin/sbcli"))
  (url-copy-file url file 'ok-if-already-exists)
  (set-file-modes file #o700))
(setf *repl-name* "Gerard's custom REPL for SBCLI")
;; The style option fails:
;; (setf *pygmentize* (merge-pathnames ".pyenv/shims/pygmentize"
;;                                     (user-homedir-pathname)))
;; (defvar *pygmentize-options* (list "-s" "-l" "lisp" "-O" "style=zenburn"))

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 many Emacs Lisp idioms.

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{(describe-function 'inferior-emacs-lisp-mode)}.

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

Disassemble dynamical and lexical scope

Listing lexical scope disassembly and dynamical scope disassembly disassemble identical code in case of respectively lexical and dynamical scope. Listing lexical scope disassembly results and dynamical scope disassembly results show the respective results.

The link how to enable lexical scope for Emacs Lisp in org-mode source blocks explains all possibilities of disabling or enabling lexical scope.

(with-temp-buffer
  (disassemble (lambda (n)
                 "Demonstrate lexical scope."
                 (let* ((lower most-negative-fixnum)
                        (upper most-positive-fixnum)
                        (sum (+ lower upper)))
                   (expt sum n)))
               (current-buffer))
  (buffer-substring-no-properties (point-min) (point-max)))
byte code:
  doc:  Demonstrate lexical scope. ...
  args: (arg1)
0       varref    most-negative-fixnum
1       varref    most-positive-fixnum
2       stack-ref 1
3       stack-ref 1
4       plus
5       constant  expt
6       stack-ref 1
7       stack-ref 5
8       call      2
9       return
(with-temp-buffer
  (disassemble (lambda (n)
                 "Demonstrate lexical scope."
                 (let* ((lower most-negative-fixnum)
                        (upper most-positive-fixnum)
                        (sum (+ lower upper)))
                   (expt sum n)))
               (current-buffer))
  (buffer-substring-no-properties (point-min) (point-max)))
byte code:
  doc:  Demonstrate lexical scope.
  args: (n)
0       varref    most-negative-fixnum
1       varbind   lower
2       varref    most-positive-fixnum
3       varbind   upper
4       varref    lower
5       varref    upper
6       plus
7       varbind   sum
8       constant  expt
9       varref    sum
10      varref    n
11      call      2
12      unbind    3
13      return

Fennel Programming

Here is a list of links describing how to install and setup Fennel and how to install and use ob-fennel with the LÖVE 2D game engine:

  1. The Fennel Programming Language
  2. Setting up Fennel
  3. Org Babel support for the Fennel language: ob-fennel
  4. Reproducible Research with Org Mode, Fennel, and LÖVE
(when (and (executable-find "fennel")
           (ensure-package-installation 'fennel-mode))
  (unless (package-installed-p 'ob-fennel)
    (package-vc-install "https://gitlab.com/andreyorst/ob-fennel"))
  (when (package-installed-p 'ob-fennel)
    (ensure-package-installation 'ob-fennel)
    (set-org-babel-language-activity 'fennel (fboundp 'fennel-mode))))
(+ x y)
luarocks \
    install --local readline \
    HISTORY_INCDIR=/usr/local/opt/readline/include \
    HISTORY_DIR=/usr/local/opt/readline/lib

Scheme Programming

# https://gitlab.com/emacs-geiser/chicken
# Install the necessary support eggs:
chicken-install -s apropos chicken-doc srfi-18 srfi-1
# Update the Chicken documentation database:
cd $(csi -R chicken.platform -p '(chicken-home)')
curl https://3e8.org/pub/chicken-doc/chicken-doc-repo-5.tgz | sudo tar zx
(when (ensure-package-installation 'geiser 'geiser-chez 'geiser-chicken
                                   'geiser-mit)
  (setopt geiser-chez-binary (executable-find "chez")))

Lua Programming

(when (and (executable-find "lua")
           (ensure-package-installation 'company-lua 'lua-mode)))
return (x+y)

Maxima Programming (info)

Listing lst:configure-maxima configures Maxima Mode. The following list contains more information:

  1. Maxima Mode is an Emacs interface to the computer algebra system Maxima.
  2. Maxima Source Code Blocks in Org Mode shows how to use Maxima in Org Mode.
  3. Maxima By Example is a tutorial for Maxima.

Listing lst:maxima-example is a minimal example of how to use how to use Maxima in Org Mode.

(when (ensure-package-installation 'maxima 'company-maxima)
  (add-hook 'maxima-mode-hook #'maxima-hook-function)
  (add-hook 'maxima-inferior-mode-hook #'maxima-hook-function)
  (add-to-list 'auto-mode-alist
               (cons "\\.mac\\'" 'maxima-mode))
  (add-to-list 'interpreter-mode-alist
               (cons "maxima" 'maxima-mode)))
print(1+1);

Python programming

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

  1. Eglot - Emacs polyGLOT: a builtin LSP client since Emacs-29.0.60. The maintainer also contributes to Emacs itself and has a deep understanding of the Way of Emacs. He refuses to add new features without seeing how they fit into the Way of Emacs as this discussion on org-mode source code blocks shows.
  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. Ruff is an extremely fast 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.
  5. Code-cells allows to edit IPython or Jupyter notebooks in Emacs with help from either Jupytext or Pandoc.

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

Videos

  1. Python's Class Development Kit - Raymond Hettinger

Python-mode

Listing setup Python mode and choose common Python interpreter select a common Python interpreter in a virtual environment for use in python-mode and ob-python by means of src_emacs-lisp{(choose-common-python-interpreter)}. 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.

Listing setup Python mode and choose common Python linter select a common Python linter for the python-check-command and the python-flymake-command by means of src_emacs-lisp{(choose-common-python-linter)} from:

  1. Pyflakes - simple tool which checks Python source files for errors.
  2. Flake8 - your tool for style guide enforcement.
  3. Ruff - an extremely fast Python linter written in Rust.

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.

Listing kickoff setup.cfg proposal implements the using black with other tools rules which make flake8 or pycodestyle agree with black's uncompromising style.

Finally, listing flake8-nocolor and ruff-nocolor pipe the stdout output of the flake8 and ruff executables through cat to remove escape sequences.

(defun choose-common-python-interpreter (&optional interpreter)
  "Let `ob-python' and `python-mode' use the same Python INTERPRETER."
  (interactive)
  (unless (featurep 'ob-python)
    (set-org-babel-language-activity 'python (fboundp 'python-mode)))
  (let* ((prompt (format "Choose Python (%s): "
                          (bound-and-true-p python-shell-interpreter)))
         (choices '(ipython python))
         (choice (if (member interpreter choices)
                     (symbol-name interpreter)
                   (completing-read prompt choices nil t))))
    (when (boundp 'org-babel-python-command)
      (pcase choice
        ("python"
         (setopt org-babel-python-command
                 (concat (or (executable-find "python3")
                             (executable-find "python"))
                         " -E")))
        ("ipython"
         (setopt org-babel-python-command
                 (concat (or (executable-find "ipython3")
                              (executable-find "ipython"))
                         " --simple-prompt --HistoryAccessor.enabled=False"))))
      (message "Now `org-babel-python-command' equals %S"
               org-babel-python-command))
    (when (boundp 'python-shell-interpreter)
      (pcase choice
        ("python"
         (setopt python-shell-interpreter (or (executable-find "python3")
                                              (executable-find "python"))
                 python-shell-interpreter-args "-E -i"))
        ("ipython"
         (setopt python-shell-interpreter (or (executable-find "ipython3")
                                              (executable-find "ipython"))
                 python-shell-interpreter-args
                 "--simple-prompt --HistoryAccessor.enabled=False")))
      (message "Now `python-shell-interpreter' equals %S"
               python-shell-interpreter))))
(defun choose-common-python-linter (&optional linter)
  "Let `python-check-command' and `python-flymake-command' use the same LINTER."
  (interactive)
  (let* ((prompt (format "Choose Python checker (%s): "
                         (bound-and-true-p python-check-command)))
         (choices '(flake8-nocolor pyflakes ruff-nocolor))
         (choice (if (member linter choices)
                     (symbol-name linter)
                   (completing-read prompt choices nil t))))
    (when (and (boundp 'python-check-command) (boundp 'python-flymake-command))
      (pcase choice
        ("flake8-nocolor"
         (setopt python-check-command (executable-find choice)
                 python-flymake-command (list (executable-find choice) "-")))
        ("pyflakes"
         (setopt python-check-command (executable-find choice)
                 python-flymake-command `(,(executable-find choice))))
        ("ruff-nocolor"
         (setopt python-check-command (executable-find choice)
                 python-flymake-command (list (executable-find choice)
                                              "--stdin-filename" "stdin" "-"))))
      (when (bound-and-true-p python-check-custom-command)
        (setq python-check-custom-command nil))
      (message "Python checker commands are %S and %S"
               python-check-command python-flymake-command))))
(with-eval-after-load 'python
  (setopt python-indent-guess-indent-offset nil
          python-shell-completion-native-disabled-interpreters '("ipython3"
                                                                 "pypy"))
  (choose-common-python-interpreter 'python)
  (choose-common-python-linter 'ruff-nocolor))
(when (executable-find "pyenv")
  (defun pyenv-full-path (version)
    "Return the full path for VERSION."
    (unless (string= version "system")
      (concat (pyenv-root) (file-name-as-directory "versions") version)))

  (defun pyenv-root ()
    "Return \"pyenv root\" as a directory."
    (cl-destructuring-bind (exit-code output)
        (shell-command-with-exit-code "pyenv" "root")
      (if (= 0 exit-code)
          (file-name-as-directory (string-trim output))
        (error "%s" (string-trim output)))))

  (defun pyenv-version-name ()
    "Return \"pyenv version-name\"."
    (cl-destructuring-bind (exit-code output)
        (shell-command-with-exit-code "pyenv" "version-name")
      (if (= 0 exit-code)
          (string-trim output)
        (error "%s" (string-trim output)))))

  (defun pyenv-versions ()
    "Return \"pyenv versions --bare --skip-aliases\" as a list.
Complete the result with \"system\"."
    (cl-destructuring-bind (exit-code output)
        (shell-command-with-exit-code
         "pyenv" "versions" "--bare" "--skip-aliases")
      (if (= 0 exit-code)
          (cons "system" (split-string output))
        (error "%s" (string-trim output)))))

  (defun pyenv-virtualenvs ()
    "Return \"pyenv virtualenvs --bare --skip-aliases\" as a list."
    (cl-destructuring-bind (exit-code output)
        (shell-command-with-exit-code
         "pyenv" "virtualenvs" "--bare" "--skip-aliases")
      (if (= 0 exit-code)
          (split-string output)
        (error "%s" (string-trim output))))))
(with-eval-after-load 'python
  (when (cl-every #'fboundp '(pyenv-full-path
                              pyenv-version-name
                              pyenv-versions
                              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-virtual-env-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-virtual-env-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)))))
# Use for instance "hatch new" to create or initialize a project.
# See: https://pypi.org/project/hatch

[tool.black]
line-length = 88

[tool.ruff]
line-length = 88
select = [
  "ARG", # flake8-unused-arguments
  "B",   # flake8-bugbear
  "C",   # mccabe
  "C4",  # flake8-comprehensions
  "E",   # pycodestyle
  "D",   # pydocstyle
  "F",   # pyflakes
  "UP",  # pyupgrade
  "W",   # pycodestyle
]
ignore = [
  "B905", # `zip()` without an explicit `strict=` parameter
]

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

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

# Local Variables:
# mode: conf-toml-mode
# End:
[flake8]
docstring-convention = numpy
extend-select = B,F,W
extend-ignore = W503
max-complexity = 15
max-line-length = 88

[pycodestyle]
ignore = W503
max-line-length = 88

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

flake8 "$@" | cat

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

ruff "$@" | cat

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

Package Installer for Python

Listing lst:shell-pip-list-outdated and lst:shell-pip-index-versions are two examples of invoking pip in org-mode shell source blocks with the respective results in listing lst:shell-pip-list-outdated-results and lst:shell-pip-index-versions-results.

# WARNING: "pip index" is currently an experimental command.
pip list --outdated
Package             Version Latest Type
------------------- ------- ------ -----
aiofiles            22.1.0  23.1.0 wheel
jupyter_server_ydoc 0.6.1   0.8.0  wheel
jupyter-ydoc        0.2.2   0.3.4  wheel
y-py                0.5.9   0.6.0  wheel
ypy-websocket       0.8.2   0.8.4  wheel
# WARNING: "pip index" is currently an experimental command.
pip index versions circular-buffer --no-color
circular-buffer (0.2.0)
Available versions: 0.2.0, 0.1.1, 0.1.0
(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))

;; Frozen until the release of jupyterlab-4.0.0.
(setopt pip-frozen-packages '("aiofiles"
                              "jupyter-server-ydoc"
                              "jupyter-ydoc"
                              "y-py"
                              "ypy-websocket"))
(defvar pip-outdated-packages nil
  "Outdated Python packages.")

(defun pip--list-outdated-sentinel (process event)
  (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 asynchonous process and finishes with a message."
  (interactive)
  (let ((command '("pip" "list" "--outdated" "--format" "json")))
    (make-process
     :name "pip-list-outdated"
     :buffer (generate-new-buffer-name "*pip-list-outdated-output*")
     :command command
     :sentinel #'pip--list-outdated-sentinel)
    (message "Running `%s' asynchonously" (string-join command " "))))
(defun pip--upgrade-maybe-sentinel (process event)
  (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 asynchonous 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 ((command
               `("pip" "install" "--progress-bar" "off" ,@(nreverse found))))
          (make-process
           :name "pip-upgrade-maybe"
           :buffer (generate-new-buffer-name "*pip-upgrade-maybe*")
           :command command
           :sentinel #'pip--upgrade-maybe-sentinel)
          (message "Running `%s' asynchonously" (string-join command " ")))
      (message "`pip-upgrade-maybe' found no packages to install"))))
;; https://emacs.stackexchange.com/questions/61754/
;; "How can I enable lexical binding for elisp code in Org mode?"

(defvar pip--simple-details-cache (make-hash-table :test 'equal)
  "Cache for PyPI project details")

(defun pip-simple-get-json ()
  (save-excursion
    (goto-char (point-min))
    (and (re-search-forward "^HTTP/.+ 200 OK$" nil (line-end-position))
         (search-forward "\n\n" nil t)
         (json-parse-buffer :array-type 'list))))

(defun pip-simple-details-retrieve (name callback &optional cbargs)
  (pip--simple-details-prune-cache)
  (if-let ((cached (gethash name pip--simple-details-cache)))
      (apply callback (cdr cached) cbargs)
    (let ((url (format "https://pypi.org/simple/%s" name))
          (url-request-method "GET")
          (url-mime-accept-string "application/vnd.pypi.simple.latest+json"))
      (url-retrieve
       url
       (lambda (status)
         (let ((details (and (not (plist-get status :error))
                             (pip-simple-get-json))))
           (when details
             (setf (gethash name pip--simple-details-cache)
                   (cons (time-convert nil 'integer) details)))
           (prog1
               (apply callback (or details 'error) cbargs)
             (kill-buffer))))
       nil t))))

(defun pip--simple-details-prune-cache ()
  (let ((expired nil)
        (time (- (time-convert nil 'integer)
                 ;; Ten minutes
                 (* 10 60))))
    (maphash (lambda (key val)
               (when (< (car val) time)
                 (push key expired)))
             pip--simple-details-cache)
    (dolist (key expired)
      (remhash key pip--simple-details-cache))))
;; https://emacs.stackexchange.com/questions/61754/
;; "How can I enable lexical binding for elisp code in Org mode?"

(defun pip-simple-project-versions (name)
  "Return the versions of Python package NAME."
  (interactive "sPython package name: ")
  (pip-simple-details-retrieve
   name
   (lambda (details)
     (when-let ((versions (reverse (cdr (gethash "versions" details)))))
       (message "`%s' versions: %S" name versions)))))

(defun pip-simple-project-list ()
  "Get the Python package project list (PEP-691 and PEP-700)."
  (interactive)
  (let ((url-request-method "GET")
        (url-mime-accept-string "application/vnd.pypi.simple.latest+json"))
    (url-retrieve "https://pypi.org/simple"
                  (lambda (status) (switch-to-buffer (current-buffer))))))
(defvar pip-package-dependency-tree nil
  "Python package dependency tree.")

(defun pip--dependency-tree-sentinel (process event)
  (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-package-dependency-tree
            (json-parse-buffer :array-type 'list :object-type 'alist))
      (kill-buffer)
      (message "Calling `%S' succeeded" #'pip-dependency-tree))))

(defun pip-dependency-tree ()
  "Save the Python package dependency tree in `pip-package-dependency-tree'."
  (interactive)
  (let ((command '("pipdeptree" "--json-tree")))
    (make-process
     :name "pip-dependency-tree"
     :buffer (generate-new-buffer-name "*pip-dependency-tree-output*")
     :command command
     :sentinel #'pip--dependency-tree-sentinel)
    (message "Running `%s' asynchonously" (string-join command " "))))
(defun pip--flatten-dependencies (parents found)
  (dolist (parent parents)
    (when-let ((children (alist-get 'dependencies parent)))
      (setq found (pip--flatten-dependencies children found)))
    (if-let* ((pair `(key . ,(alist-get 'key parent)))
              (old (cl-loop for alist in found thereis (member pair alist)))
              (rvs (split-string (alist-get 'required_version parent) "[,]+")))
        (dolist (rv rvs)
          (cl-pushnew rv (alist-get 'required_version old) :test #'equal))
      (let* ((new (assq-delete-all 'dependencies (copy-alist parent)))
             (rvs (split-string (alist-get 'required_version new) "[,]+")))
        (setcdr (assq 'required_version new) rvs)
        (push new found))))
  found)

(defun pip-flatten-package-dependency-tree ()
  (let (found)
    (dolist (package pip-package-dependency-tree)
      (when-let ((children (alist-get 'dependencies package)))
        (setq found (pip--flatten-dependencies children found))))
    found))

(defconst pip-canonical-version-regexp-alist
  '(("^[.]dev$" . -4)
    ("^a$" . -3)
    ("^b$" . -2)
    ("^rc$" . -1)
    ("^[.]post$" . 1))
  "Specify association between canonical \"PEP-440\" version and its priority.")

(defun pip-canonical-version-to-list (version)
  (let ((version-regexp-alist pip-canonical-version-regexp-alist))
    (version-to-list version)))

;; https://peps.python.org/pep-0440/
(defun pip-pep-440 (clauses)
  (let (compatibles exclusions greater-equals less-thans unknowns)
    (dolist (clause clauses)
      (cond ((string-prefix-p "~=" clause)
             (push (substring clause 2) compatibles))
            ((string-prefix-p "!=" clause)
             (push (substring clause 2) exclusions))
            ((string-prefix-p ">=" clause)
             (push (substring clause 2) greater-equals))
            ((string-prefix-p "<" clause)
             (push (substring clause 1) less-thans))
            (t (push clause unknowns))))
    (list compatibles exclusions greater-equals less-thans unknowns)))

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 according to the configuration in listing lst:eglot-maybe-ensure.

Type

(eval

to dump a JSON representation of src_emacs-lisp{eglot-workspace-configuration} for debugging. The comment in listing minimal Eglot setup also shows how to decrease or to increase the verbosity of Python LSP server log output for debugging.

(with-eval-after-load 'eglot
  (setq-default
   eglot-workspace-configuration
   ;; Enable the `:pylsp_ruff' plugin and ensure to disable the
   ;; `:flake8', `:mccabe', and `:pycodestye' plugins.
   '(:pylsp (:plugins
             (:pylsp_ruff
              (:enabled t)
              :flake8           ;; It is better to uninstall flake8
              (:enabled :json-false)
              :mccabe           ;; It is better to uninstall mccabe
              (:enabled :json-false)
              :pycodestyle      ;; It is better to uninstall pycodestyle
              (:enabled :json-false)
              :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.
((nil  ;; nil, since Emacs-29.1 filters out irrelevant variable names.
  . ((eglot-workspace-configuration
      ;; Enable the `:pylsp_ruff' plugin and ensure to disable the
      ;; `:flake8', `:mccabe', and `:pycodestye' plugins.
      . (:pylsp (:plugins
                 (:pylsp_ruff
                  (:enabled t)
                  :flake8           ;; It is better to uninstall flake8
                  (:enabled :json-false)
                  :mccabe           ;; It is better to uninstall mccabe
                  (:enabled :json-false)
                  :pycodestyle      ;; It is better to uninstall pycodestyle
                  (:enabled :json-false)
                  :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

Listing lst:make-pylsp-server-patch is useful to propagate eventual patches of a local fork of python-lsp-server.

  git -C $HOME/VCS/python-lsp-server diff >pylsp.patch

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)

Code-cells

Code-cells allows to edit IPython or Jupyter notebooks in Emacs with help from either Jupytext or Pandoc to translate between the ipynb format and different plain text formats. Sofar, I have used code-cells to open ipynb notebooks with

(eval

in Emacs for viewing. Listing lst:configure-code-cells configures code-cells.

(when (ensure-package-installation 'code-cells)
  (with-eval-after-load 'code-cells
    (let ((map code-cells-mode-map))
      (keymap-set map "M-p" #'code-cells-backward-cell)
      (keymap-set map "M-n" #'code-cells-forward-cell)
      (keymap-set map "C-c C-c" #'code-cells-eval))))

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 several 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 (and (ensure-package-installation 'ws-butler)
           (require 'ws-butler nil 'noerror))
  (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. 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.
  2. 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:configure-smartparens aims to configure smartparens for Elisp, LaTeX, Org, and Python.

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 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 (and (ensure-package-installation 'smartparens)
           ;; Require `smartparens-config' instead of `smartparens' to
           ;; disable pairing of the quote character for lisp modes.
           (require 'smartparens-config nil 'noerror))
  (setopt sp-base-key-bindings 'sp
          sp-override-key-bindings '(("C-(" . sp-backward-barf-sexp)
                                     ("C-)" . sp-forward-slurp-sexp)))

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

  (dolist (hook '(emacs-lisp-mode-hook
                  geiser-repl-startup-hook
                  ielm-mode-hook
                  lisp-data-mode-hook
                  lisp-mode-hook
                  python-mode-hook
                  slime-repl-mode-hook
                  sly-mrepl-mode-hook))
    (add-hook hook #'smartparens-strict-mode))

  ;; 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))

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 (and (ensure-package-installation 'electric-operator)
           (fboundp 'electric-operator-mode))
  (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)
  (dolist (hook '(LaTeX-mode-hook
                  org-mode-hook
                  python-mode-hook
                  python-ts-mode-hook))
    (add-hook hook #'yas-minor-mode)))

Tempo (info)

The official tempo (info) documentation is very short and the following links are obsolete or do not reveal the full power of tempo (info) after its adaptation to lexical-binding:

  1. Tempo (Emacs Wiki).
  2. David Kågedals elisp.
  3. Emacs's Native Snippets, Emacs's native templating and snippet functionality.
  4. Templating in Emacs with Tempo.

Listing lst:configure-tempo-ui defines a function that inserts major modes specific tempo templates by means of marginalia, a function to define local keybindings and the tools to add tempo-tags to abbreviation tables.

Listing lst:configure-tempo-latex-completing-read shows how to combine tempo, latex and completing-read for the builtin tempo package. Listing lst:setup-python-tempo configures tempo for python-mode.

(when (require 'tempo nil 'noerror)
  (setopt tempo-interactive t)

  (with-eval-after-load 'marginalia
    (add-to-list 'marginalia-prompt-categories
                 '("\\<tempo template\\>" . command)))

  ;; https://www.n16f.net/blog/templating-in-emacs-with-tempo/
  (defun insert-tempo-template ()
    "Insert a major mode specific tempo template."
    (interactive)
    (let* ((choices (mapcar #'cdr (tempo-build-collection)))
           (function-name (completing-read "Tempo template: " choices)))
      (funcall (intern function-name))))

  (defun setup-local-tempo-key-bindings ()
    "Initialize local `tempo' key bindings."
    (keymap-local-set "M-n" #'tempo-forward-mark)
    (keymap-local-set "M-p" #'tempo-backward-mark)
    (keymap-local-set "M-+" #'insert-tempo-template))

  (defun abbrev-tempo-expand-if-complete ()
    "Hook function for `define-abbrev' with `no-self-insert' property `t'."
    (tempo-expand-if-complete))

  (put 'abbrev-tempo-expand-if-complete 'no-self-insert t)

  (defun add-tempo-tags-to-abbrev-table (tempo-tags abbrev-table)
    "Add the tags in TEMPO-TAGS to ABBREV-TABLE.
Allows `tempo' expansion by typing <SPC> after a complete `tempo' tag."
    (dolist (tag tempo-tags)
      (unless (abbrev-symbol (car tag) abbrev-table)
        (define-abbrev abbrev-table
          (car tag) 1 'abbrev-tempo-expand-if-complete)))))

Tempo for AUCTeX's LaTeX-mode

(with-eval-after-load 'latex
  (require 'tempo nil 'noerror)

  (defun tempo-latex-environment-handler (element)
    (when (eq element 'latex-environment)
      (let ((environment (completing-read "LaTeX environment: "
                                          (LaTeX-environment-list)
                                          #'always 'require-match)))
        `(l "\\begin{" ,environment "}" > n > r n "\\end{" ,environment "}" >))))

  (cl-pushnew 'tempo-latex-environment-handler tempo-user-elements)

  (defun setup-tempo-latex ()
    (defvar latex-tempo-tags nil)

    (tempo-define-template
     "LaTeX-environment"
     '('latex-environment)
     "env"
     "Insert a LaTeX environment"
     'latex-tempo-tags)

    (tempo-use-tag-list 'latex-tempo-tags)
    (add-tempo-tags-to-abbrev-table latex-tempo-tags latex-mode-abbrev-table)
    (setup-local-tempo-key-bindings))

  (add-hook 'LaTeX-mode-hook 'setup-tempo-latex))

Tempo for python-mode

(with-eval-after-load 'python
  (require 'tempo nil 'noerror)

  (defun setup-tempo-python ()
    (defvar python-tempo-tags nil)
    (tempo-define-template
     "python-base-class"
     '("class " p ":" n >)
     "clsb" "Define a base class" 'python-tempo-tags)
    (tempo-define-template
     "python-derived-class"
     '("class " p "(" p "):" n >)
     "clsd" "Define a derived class" 'python-tempo-tags)
    (tempo-define-template
     "python-class-method"
     '("@classmethod" > n > "def " p "(cls, " p "):" n >)
     "defc" "Define a class method" 'python-tempo-tags)
    (tempo-define-template
     "python-function"
     '("def " p "(" p "):" n >)
     "deff" "Define a function" 'python-tempo-tags)
    (tempo-define-template
     "python-normal-method"
     '("def " p "(self, " p "):" n >)
     "defm" "Define a normal method" 'python-tempo-tags)
    (tempo-define-template
     "python-static-method"
     '("@staticmethod" > n > "def " p "(" p "):" n >)
     "defs" "Define a static method" 'python-tempo-tags)

    (tempo-use-tag-list 'python-tempo-tags)
    (add-tempo-tags-to-abbrev-table python-tempo-tags python-mode-abbrev-table)
    (setup-local-tempo-key-bindings))

  (add-hook 'python-mode-hook 'setup-tempo-python))

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 if buffer is narrowed, 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."
    (interactive "P")
    (declare (interactive-only))
    (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:enable-rainbow-mode enables rainbow-mode to colorize color codes and names in buffers for debugging.

(when (and (ensure-package-installation 'rainbow-mode)
           (fboundp 'rainbow-mode))
  (setopt rainbow-x-colors-major-mode-list '(c++-mode
                                             c-mode
                                             emacs-lisp-mode
                                             inferior-emacs-lisp-mode
                                             java-mode
                                             lisp-interaction-mode
                                             org-mode
                                             python-mode))
  (rainbow-mode +1))

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.

;; Ensure loading `pulse' with the dynamic variables `pulse-delay' and
;; `pulse-iterations' before masking them lexically instead of after
;; to prevent the warning triggered by lazy loading.
(when (require 'pulse nil 'noerror)
  ;; https://karthinks.com/software/batteries-included-with-emacs/
  ;; https://github.com/karthink/.emacs.d/blob/master/init.el#L2077
  (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))))

  (dolist (command '(scroll-up-command
                     scroll-down-command
                     recenter-top-bottom
                     other-window))
    (advice-add command :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

(when (fboundp 'eww-browse-url)
  (defun eww-subreddit ()
    "Read a subreddit in Emacs."
    (interactive)
    (eww-browse-url (format "www.reddit.com/r/%s/.mobile"
                            (completing-read "subreddit: "
                                             '("emacs"
                                               "i3wm"
                                               "orgmode")
                                             nil t)))))
(with-eval-after-load 'eww
  (defun eww-display-pdf-as-binary (fn &rest args)
    (let ((buffer-file-coding-system 'binary))
      (apply fn args)))

  (defun eww-rename-buffer ()
    "Rename the buffer using title or url."
    (let* ((title (plist-get eww-data :title))
           (name (or (and (eq "" title) (plist-get eww-data :url)) title)))
      (rename-buffer (format "*%s # eww*" name) t)))

  (add-hook 'eww-after-render-hook #'eww-rename-buffer)
  (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

(when (ensure-package-installation 'osm)
  (with-eval-after-load 'org
    (require 'osm-ol)))

Restclient

Restclient is a tool to manually explore and test HTTP REST webservices. It runs queries from a plain-text query sheet and displays results as images, pretty-printed XML, and pretty-printed JSON.

(ensure-package-installation 'restclient)
# Get the JSON project list from PyPI using PEP-691 and PEP-700
GET https://pypi.org/simple
Accept: application/vnd.pypi.simple.latest+json

# Get the JSON project details of "circular-buffer" using PEP-691 and PEP-700
GET https://pypi.org/simple/circular-buffer
Accept: application/vnd.pypi.simple.latest+json

# Get the JSON project details of "holygrail" using PEP-691 and PEP-700
GET https://pypi.org/simple/holygrail
Accept: application/vnd.pypi.simple.latest+json

# Get the JSON project details of "pipdeptree" using PEP-691 and PEP-700
GET https://pypi.org/simple/pipdeptree
Accept: application/vnd.pypi.simple.latest+json

# Get the JSON project details of "numpy" using PEP-691 and PEP-700
GET https://pypi.org/simple/numpy
Accept: application/vnd.pypi.simple.latest+json

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
     '(("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")
       ("Real Python" . "realpython.com")
       ("Worg - Org Mode Community" . "orgmode.org/worg")
       ("Git: Emacs" . "git.savannah.gnu.org/cgit/emacs.git")
       ("Git: Emacs Core Python Mode" .
        "git.savannah.gnu.org/cgit/emacs.git/log/lisp/progmodes/python.el")
       ("Git: Emacs MultiMedia System" .
        "https://git.savannah.gnu.org/cgit/emms.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/")
       ("Asian Pacific Journal Japan Focus" . "apjjf.org")
       ("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/" ""])
       ("Le Figaro" . "www.lefigaro.fr")
       ("Le Monde" . "www.lemonde.fr")
       ("Libération" . "www.liberation.fr")
       ("Monde Diplomatique" . "www.monde-diplomatique.fr")
       ("NRC". "www.nrc.nl")
       ("The Guardian" . "www.theguardian.com/international")
       ("Trouw" . "www.trouw.nl")
       ("Volkskrant" . "www.volkskrant.nl")))))

Reading News and Mail (info)

  1. How to use Emacs as a USENET reader with Gnus
  2. A practical guide to Gnus
  3. Fast reading and searching of Gmane.io with Gnus
  4. Setting up Leafnode on macOS
  5. Gnus
  6. Stupid GMail hacks for Gnus
  7. More stupid Gnus hacks
  8. See Mu4e section of Phundrak's Emacs configuration
  9. Pass: the standard unix password manager
  10. How to use Pass, a command-line password manager for Unix systems
  11. Clever uses of pass, the Unix password manager
command map keys
gnus-group-list-active gnus-group-list-map

(eval

gnus-group-list-all-groups gnus-group-mode-map

(eval

gnus-group-toggle-subscription gnus-group-mode-map

(eval

(with-eval-after-load 'gnus
  (setopt gnus-select-method '(nntp "news.gmane.io")))

(with-eval-after-load 'gnus-start
  (setopt gnus-check-bogus-newsgroups nil
          gnus-check-new-newsgroups 'ask-server
          gnus-read-newsrc-file t
          gnus-read-active-file 'some
          gnus-save-killed-list t
          gnus-save-newsrc-file t
          gnus-use-dribble-file t))

(with-eval-after-load 'gnus-sum
  (setopt gnus-thread-hide-subtree t))

Sending Mail (info)

  1. Email setup in Emacs with Mu4e on macOS
  2. Setting up multiple IMAP and SMTP accounts in Gnus
  3. Emacs on Macos Monterey
  4. Msmtp resource file for posteo.net
  5. GPG tutorial
  6. Cryptographic key server examples
  7. Fastmail setup with Emacs, mu4e and mbsync on macOS
(setopt user-full-name "Gerard Vermeulen"
        user-mail-address "gerard.vermeulen@posteo.net")
(with-eval-after-load 'message
  (setopt message-sendmail-envelope-from 'header))
(with-eval-after-load 'sendmail
  (setopt mail-specify-envelope-from t
          mail-envelope-from 'header
          send-mail-function #'sendmail-send-it
          sendmail-program (executable-find "msmtp")))
# Set default values for all accounts.
defaults
auth on
tls on
logfile ~/.msmtp.log

# CNRS on Darwin
# security add-generic-password -s cnrs -a gerard.vermeulen@neel.cnrs.fr -w
account cnrs
host smtps.grenoble.cnrs.fr
port 465
tls_starttls off
from gerard.vermeulen@neel.cnrs.fr
user gerard.vermeulen@neel.cnrs.fr
passwordeval security find-generic-password -s cnrs -w

# https://notmuchmail.org/emacstips/#index11h2
# https://wiki.debian.org/msmtp
# https://tushartyagi.com/blog/configure-mu4e-and-msmtp/
# https://www.emacswiki.org/emacs/GnusMSMTP
# https://www.ying-ish.com/essay/emacs-notmuch-mbsync-msmtp-email/

# Local Variables:
# mode: conf-unix
# End:
# Set default values for all accounts.
defaults
auth on
tls on
tls_trust_file /etc/ssl/certs/ca-certificates.crt
logfile ~/.msmtp.log

# CNRS on Linux
# secret-tool store --label=msmtp host smtps.grenoble.cnrs.fr service smtp user gerard.vermeulen@neel.cnrs.fr
account cnrs
host smtps.grenoble.cnrs.fr
port 465
tls_starttls off
from gerard.vermeulen@neel.cnrs.fr
user gerard.vermeulen@neel.cnrs.fr

# https://notmuchmail.org/emacstips/#index11h2
# https://wiki.debian.org/msmtp
# https://tushartyagi.com/blog/configure-mu4e-and-msmtp/
# https://www.emacswiki.org/emacs/GnusMSMTP
# https://www.ying-ish.com/essay/emacs-notmuch-mbsync-msmtp-email/

# Local Variables:
# mode: conf-unix
# End:

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 (and (ensure-package-installation 'elfeed)
           (fboundp 'elfeed))
  (keymap-global-set "C-x w" #'elfeed)

  (with-eval-after-load 'elfeed
    (setopt
     elfeed-feeds
     '(("http://www.howardism.org/index.xml" h-abrams)
       ("https://bitspook.in/archive/feed.xml" c-singh)
       ("https://emacshorrors.com/feed.atom" v-schneidermann)
       ("https://emacsninja.com/emacs.atom" v-schneidermann)
       ("https://feeds.feedburner.com/InterceptedWithJeremyScahill" j-scahill)
       ("https://frame.work/fr/fr/blog.rss" framework)
       ("https://nullprogram.com/feed/" c-wellons)
       ("https://oremacs.com/atom.xml" o-krehel)
       ("https://planet.emacslife.com/atom.xml" planet-emacs)
       ("https://protesilaos.com/codelog.xml" p-stavrou)
       ("https://sachachua.com/blog/category/emacs/feed" s-chua)
       ("https://sciencescitoyennes.org/feed/" sciences)
       ("https://tdodge.consulting/blog/rss.xml" t-dodge)
       ("https://talkpython.fm/episodes/rss" talk-python)
       ("https://updates.orgmode.org/feed/updates" org-updates)
       ("https://www.bof.nl/rss/" bof)
       ("https://www.democracynow.org/podcast-video.xml" dn)
       ("https://www.laquadrature.net/fr/rss.xml" lqdn)
       ("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.

(when (ensure-package-installation 'emms)
  (with-eval-after-load 'emms
    (setopt emms-player-list '(emms-player-mpd emms-player-mpv))
    (emms-all))

  (with-eval-after-load 'emms-mode-line
    (setopt emms-mode-line-format ""))

  (with-eval-after-load 'emms-player-mpd
    (setopt emms-player-mpd-music-directory (expand-file-name "~/Music")
            emms-player-mpd-server-name "localhost"
            emms-player-mpd-server-port "6600"
            emms-player-mpd-verbose t))

  (with-eval-after-load 'emms-player-mpv
    (setopt emms-player-mpv-ipc-method 'ipc-server
            emms-player-mpv-update-metadata t))

  (with-eval-after-load 'emms-playing-time
    (setopt emms-playing-time-display-format " %s "))

  (with-eval-after-load 'emms-playlist-mode
    (setopt emms-playlist-mode-center-when-go t))

  (with-eval-after-load 'emms-streams
    (setopt emms-streams-file
            (no-littering-expand-etc-file-name "emms/streams.emms"))))

Hiding spurious buffers

When emms-player-mpd opens a connection to mpd and mpd is not running, mpd responds with a message of the form "OK MPD 0.23.5". Neither emms-player-mpd nor its support library tq anticipate the "OK MPD 0.23.5" message, but send such messages to a new intrusive buffer called *spurious*. Listing lst:hiding-spurious-buffers hides those *spurious* buffers, but you can always switch to them. See:

  1. The Zen of Buffer Display (info)
  2. Configuring the Emacs display system
  3. Demystifying Emacs's Window Manager

for technical information.

(with-eval-after-load 'emms-player-mpd
  ;; https://www.masteringemacs.org/article/demystifying-emacs-window-manager
  ;; Hide `*spurious*' buffers, but you can always switch to them:
  (add-to-list 'display-buffer-alist
               '("\\*spurious\\*.*" display-buffer-no-window
                 (allow-no-window . t))))

Music Player Daemon configuration

Listing lst:darwin-mpd-conf proposes a Music Player Daemon configuration file on Darwin that keeps each new connection for a day in order to reduce the rate of *spurious* generation.

music_directory         "~/Music"
playlist_directory      "~/.mpd/playlists"
db_file                 "~/.mpd/mpd.db"
log_file                "~/.mpd/mpd.log"
pid_file                "~/.mpd/mpd.pid"
state_file              "~/.mpd/mpdstate"
follow_outside_symlinks "yes"
follow_inside_symlinks  "yes"

bind_to_address         "localhost"
port                    "6600"
connection_timeout      "86400"

audio_output {
  type                  "osx"
  name                  "CoreAudio"
  mixer_type            "software"
}

GNU Free Documentation License

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

Only the Org source file shows the local variables footer.