A bootstrapping Emacs-30.0.50 setup for technical PDF reports using Org-Mode, Org-Babel, Lisp, Python, and LaTeX.
Go to file
2022-05-09 06:30:13 +02:00
.githooks Fix org-babel-tangle-file usage 2021-11-30 11:57:07 +01:00
etc Remove \usepackage{unicode-math} to squash LuaLaTeX bugs 2022-05-07 11:09:57 +02:00
.gitignore Get rid of #+setupfile: <FILE> to leave only #+include: <FILE> 2022-03-02 06:36:12 +01:00
.ignore Fix ripgrep by overriding the .gitignore file 2021-12-01 11:52:28 +01:00
Emacs-logo.png Freeze many changes 2021-12-23 15:29:03 +01:00
Makefile Fix org-babel-tangle-file usage 2021-11-30 11:57:07 +01:00
org-babel-tangle-file Tidy load-time specific source block evaluation 2022-04-17 19:05:06 +02:00
Org-mode-unicorn.png Freeze many changes 2021-12-23 15:29:03 +01:00
pylsp-auto-import-modules.patch Simplify the logic with respect to the pylsp server patch 2022-02-08 13:34:50 +01:00
README.org Evaluate specific Python source blocks only interactively 2022-05-09 06:30:13 +02:00

Emacs setup for use with LaTeX, Org, and Python

Quick start

Backup your user-emacs-directory (defaults often to ~/.emacs.d on Linux, Unix, or Darwin) directory and execute the commands in listing lst:prepare-user-emacs-directory. 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 ccdr@mercury.grenoble.cnrs.fr:SERVER/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 requires Org-9.5 (info), which is already part of Emacs-28.1. Citar exploits the enhancements of Emacs' builtin selection mechanism provided by the extension packages vertico, orderless, embark, marginalia, and consult. The citeproc extension package provides CSL: citation style language processing capabilities to citar and Org Mode. Citation style language: style repository links to a curated repository of CSL styles.

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. I have stolen his idea of using custom-set-variables instead of the customize interface 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. I have stolen her idea of using quelpa to install packages from any source.
  5. Steve Purcell's configuration is well organized, a showcase of readable code, as well helpful commit and issue histories. See for instance the discussion on the correctness of order of company candidates in Emacs lisp mode.

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

  1. 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.
  2. 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).
  3. Endless Parentheses is a blog with many mindblowing code snippets.
  4. Learning Emacs and Elisp is a link to a video tutorial with a transcript on the best approach to learn Emacs and Elisp.
  5. Mastering Emacs is a link to a blog with many interesting posts that promotes a book on how to become a proficient Emacs user.

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:
  ;;; earl-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 customizes Emacs variables. It consists of three parts in listing lst:1st-custom-set-variables-call, lst:2nd-custom-set-variables-call, and lst:3rd-custom-set-variables-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 the custom-set-variables function calls in listing lst:1st-custom-set-variables-call, lst:2nd-custom-set-variables-call, and lst:3rd-custom-set-variables-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 as saving customizations (info) recommends because of the custom-set-variables function calls.

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

  (custom-set-variables
   '(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))
  (custom-set-variables
   '(package-archives '(("gnu" . "https://elpa.gnu.org/packages/")
                        ("nongnu" . "https://elpa.nongnu.org/nongnu/")
                        ("melpa" . "https://melpa.org/packages/")))
   ;; Pin those packages to GNU ELPA to get the info documentation.
   '(package-pinned-packages '((consult . "gnu")
                               (marginalia . "gnu")
                               (vertico . "gnu")))
   '(package-selected-packages
     '(async              ; asynchroneous processing
       auctex             ; Aalborg University Center TeX
       citar              ; bibliography handling
       company            ; complete anything
       consult            ; consult completing-read
       deadgrep           ; use ripgrep from Emacs
       eglot              ; Emacs polyGLOT LSP client
       embark             ; act on any buffer selection
       htmlize            ; convert buffer contents to HTML
       iedit              ; simultaneous multi-entity editing
       keycast            ; show current command with binding
       magit              ; Git Text-based User Interface
       marginalia         ; minibuffer margin notes
       no-littering       ; keep `user-emacs-directory' clean
       orderless          ; Emacs completion style
       org                ; plain text thought organizer
       org-menu           ; transient menu for org-mode
       pdf-tools          ; interactive docview replacement
       python             ; mode to edit Python code
       quelpa             ; install Emacs packages from source
       saveplace-pdf-view ; save pdf-view and doc-view places
       smartparens        ; smart editing of character pairs
       vertico            ; VERTical Interactive Completion
       wgrep              ; open a writable grep buffer
       xr                 ; undo rx to grok regular expressions
       yasnippet)))       ; code or text template expansion
  (custom-set-variables
   '(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)
   '(view-read-only t))

  (when (version< "28.0" emacs-version)
    (custom-set-variables
     '(mode-line-compact 'long)
     '(next-error-message-highlight t)
     '(use-short-answers t)))

  (when (eq system-type 'darwin)
    (custom-set-variables
     '(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)))

Text faces or look (info)

Section text faces or styles tells that this setup does not use theming. However, in order to improve visibility, it relies on:

  1. Tweaking faces in memory after loading as in listing lst:tweak-faces-faces-after.
  2. Toggling between a dark and light background by means of src_emacs-lisp{(invert-default-face)} in listing lst:invert-default-face.
  3. Shadowing the definition of faces before loading as in listing lst:tweak-org-faces-before and lst:tweak-sh-script-faces-before. The last item in the page on fontifying Org mode source code blocks describes this method.
  (with-eval-after-load 'emacs
    (defun tweak-region-face-background-color ()
      (when (featurep 'gtk)
        (set-face-attribute
         'region nil
         :background (cdr (assoc (face-attribute 'default :background)
                                 '(("white" . "LightGoldenrod2")
                                   ("black" . "blue3")))))))

    (tweak-region-face-background-color))
  (with-eval-after-load 'emacs
    (defun invert-default-face ()
      "Invert the default face."
      (interactive)
      (invert-face 'default)
      (tweak-region-face-background-color)))
  (with-eval-after-load 'emacs
    ;; Shadow the definition 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'."))
  (with-eval-after-load 'emacs
    ;; Shadow the 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."))

Advising Functions (info)

  (with-eval-after-load 'emacs
    (defun toggle-advice (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)))))

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:customize-dired makes the directory editor sort entries alphabetically after grouping the directories before grouping the files by extension.

  (with-eval-after-load 'dired
    (custom-set-variables
     '(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
    (custom-set-variables
     ;; Ensure the use of `GNU-ls' from `coreutils' on darwin.
     '(insert-directory-program (or (executable-find "gls")
                                    (executable-find "ls")))))

  (with-eval-after-load 'wdired
    (custom-set-variables
     '(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)))

    (define-key dired-mode-map (kbd "E") #'dired-eww-open-file)
    (define-key dired-mode-map (kbd "Y") #'dired-rsync))

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

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

In addition, the quelpa tool allows to fetch code from any source and build a package on your computer before installation. 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 ensures installation of quelpa before ensuring installation of org-menu.
  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 (require 'no-littering nil 'noerror)
    (package-refresh-contents)
    (package-install 'no-littering)
    (require 'no-littering))

  (unless (package-installed-p 'quelpa)
    (package-install 'quelpa))

  (unless (package-installed-p 'org-menu)
    ;; Neither GNU ELPA, nor MELPA provide this package.
    (quelpa '(org-menu :repo "sheijk/org-menu" :fetcher github)))

  (unless noninteractive
    (package-install-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))
        (if (package-installed-p package)
            (when (bound-and-true-p package-selected-packages)
              (cl-pushnew package package-selected-packages))
          (setq ok nil)))
      ok))

Help (info)

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

command keys key map
Info-goto-emacs-command-node

(eval

help-map
describe-function

(eval

help-map
describe-key

(eval

help-map
describe-symbol

(eval

help-map
describe-variable

(eval

help-map
info

(eval

help-map

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)
    (define-key help-map (kbd "y") #'shortdoc-display-group)
    (with-eval-after-load 'shortdoc
      ;; Ensure defining the functions before documenting them.
      (define-short-documentation-group init
        "Advice"
        (toggle-advice :no-manual t)
        "Face"
        (invert-default-face :no-manual t)
        (set-default-face-height :no-manual t)
        "LaTeX"
        (TeX-brace-count-line-override :no-manual t)
        (toggle-TeX-brace-count-line-override :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-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-syntax-convert-keyword-case-to-lower :no-manual t)
        "Wizard"
        (enable-this-command :no-manual t)
        (narrow-or-widen-dwim :no-manual t)
        (org-narrow-to-table :no-manual t))))

Info (info)

Listing lst:configure-info adds a path in my home directory to the places where info looks for files.

  (with-eval-after-load 'info
    ;; Emacs should find my "python.info" file.
    (add-to-list 'Info-directory-list
                 (expand-file-name "~/.local/share/info")))

Key bindings (info)

command keys key map
undo

(eval

global-map
backward-kill-word

(eval

global-map
backward-char

(eval

global-map
forward-char

(eval

global-map
backward-word

(eval

global-map
forward-word

(eval

global-map
backward-sentence

(eval

global-map
forward-sentence

(eval

global-map

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

Keycast

Listing lst:configure-keycast configures keycast.

  ;; Make `keycast-log-update-buffer' use a buffer similar to the
  ;; control buffer `ediff-setup-windows-plain' returns.
  (when (require 'keycast nil 'noerror)
    (custom-set-variables
     '(keycast-mode-line-window-predicate 'keycast-bottom-right-window-p))

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

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

    (toggle-keycast-log-update-buffer-override))

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 succesful (re)compilation on condition that the settings of the compile-command local variable in section are compatible. The local variable compile-command in the local variables section (only visible in org files, but not in html and pdf files) shows how to use 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 a 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' );
  $clean_ext .= " acr acn alg bbl dvi glo gls glg ist lol 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 "?\n?\f".
  
  # 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

  1. Patrick Goddi: macOS URI protocol handler and his URI-Handler.
  2. Hammerspoon
  3. Emacs Capture Anywhere
  (when (ensure-package-installation 'applescript-mode))
  on emacsclient(input)
    do shell script "/usr/local/bin/emacsclient -n -c '" & input & "'"
    tell application "Emacs" to activate
  end emacsclient

  on open location input
    emacsclient(input)
  end open location

  on open inputs
    repeat with raw_input in inputs
      set input to POSIX path of raw_input
      emacsclient(input)
    end repeat
  end open

  on run
    do shell script emacsclient("")
  end run

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. Orderless (info) for an advanced completion style,
  2. Embark (info) for minibuffer actions with context menus,
  3. Marginalia (info) for rich annotations in the minibuffer, and
  4. 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 and minibuffer-history-completion provides completion on previous input in the minibuffer.

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 (fboundp 'savehist-mode)
    (savehist-mode +1)
    (custom-set-variables
     '(savehist-additional-variables
       '(eww-history
         kill-ring
         regexp-search-string
         search-ring
         search-string))))
  (when (fboundp 'vertico-mode)
    (vertico-mode +1))
  (with-eval-after-load 'vertico
    (define-key vertico-map (kbd "RET") #'vertico-directory-enter)
    (define-key vertico-map (kbd "DEL") #'vertico-directory-delete-char)
    (define-key vertico-map (kbd "M-DEL") #'vertico-directory-delete-word))
command keys remap
vertico-directory-delete-char

(eval

vertico-directory-delete-word

(eval

vertico-directory-enter

(eval

vertico-exit

(eval

exit-minibuffer
vertico-exit-input

(eval

vertico-first

(eval

beginning-of-buffer
vertico-first

(eval

minibuffer-beginning-of-buffer
vertico-insert

(eval

vertico-last

(eval

end-of-buffer
vertico-next-group

(eval

forward-paragraph
vertico-next

(eval

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

(eval

next-line
vertico-previous-group

(eval

backward-paragraph
vertico-previous

(eval

previous-line-or-history-element
vertico-previous

(eval

previous-line
vertico-save

(eval

kill-ring-save
vertico-scroll-down

(eval

scroll-down-command
vertico-scroll-up

(eval

scroll-up-command

Orderless (info)

Listing lst:configure-orderless configures orderless for company (info).

  (with-eval-after-load 'orderless
    (custom-set-variables
     '(orderless-component-separator " +"))

    (defun just-one-face (fn &rest args)
      (let ((orderless-match-faces [completions-common-part]))
        (apply fn args)))

    (advice-add 'company-capf--candidates :around #'just-one-face))

Embark (info)

Listing lst:configure-embark configures embark key bindings:

  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 (cl-every #'fboundp '(embark-act embark-bindings embark-dwim))
    (global-set-key (kbd "C-,") #'embark-act)
    (global-set-key (kbd "C-:") #'embark-dwim)
    (global-set-key (kbd "C-h B") #'embark-bindings)
    (setq prefix-help-command #'embark-prefix-help-command))

Marginalia (info)

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

  (when (fboundp 'marginalia-mode)
    (marginalia-mode +1))

Consult (info)

Listing lst:configure-consult configures consult.

command keys key map
consult-apropos

(eval

help-map
consult-bookmark

(eval

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

(eval

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

(eval

ctl-x-4-keymap
consult-buffer

(eval

ctl-x-keymap
consult-compile-error

(eval

goto-map
consult-complex-command

(eval

ctl-x-keymap
consult-find

(eval

search-map
consult-focus-lines

(eval

search-map
consult-git-grep

(eval

search-map
consult-global-mark

(eval

goto-map
consult-goto-line

(eval

goto-map
consult-goto-line

(eval

goto-map
consult-history

(eval

global-map
consult-imenu-project

(eval

goto-map
consult-keep-lines

(eval

search-map
consult-line

(eval

search-map
consult-mark

(eval

goto-map
consult-mode-command

(eval

global-map
consult-multi-occur

(eval

search-map
consult-outline

(eval

goto-map
consult-register

(eval

ctl-x-r-keymap
consult-yank-pop

(eval

global-map
deadgrep

(eval

search-map
elfeed

(eval

global-map
embark-act

(eval

global-map
embark-bindings

(eval

global-map
embark-dwim

(eval

global-map
iedit-mode

(eval

global-map
minibuffer-complete-history

(eval

minibuffer-local-map
narrow-or-widen-dwim

(eval

ctl-x-keymap
org-agenda

(eval

global-map
org-capture

(eval

global-map
org-cite

(eval

org-mode-map
org-insert-link-global

(eval

global-map
org-narrow-to-table

(eval

ctl-x-keymap
org-store-link

(eval

global-map
  (when (fboundp 'consult-apropos)
    (custom-set-variables
     '(consult-project-root-function #'vc-root-dir))
    ;; C-c bindings (current-global-map)
    (global-set-key (kbd "C-c h") #'consult-history)
    (global-set-key (kbd "C-c m") #'consult-mode-command)
    ;; C-h bindings (help-map)
    (define-key help-map (kbd "a") #'consult-apropos)
    ;; C-x bindings (ctl-x-map)
    (define-key ctl-x-map (kbd "M-:") #'consult-complex-command)
    (define-key ctl-x-map (kbd "b") #'consult-buffer)
    (define-key ctl-x-4-map (kbd "b") #'consult-buffer-other-window)
    (define-key ctl-x-5-map (kbd "b") #'consult-buffer-other-frame)
    (define-key ctl-x-r-map (kbd "x") #'consult-register)
    (define-key ctl-x-r-map (kbd "b") #'consult-bookmark)
    ;; M-g bindings (goto-map)
    (define-key goto-map (kbd "g") #'consult-goto-line)
    (define-key goto-map (kbd "M-g") #'consult-goto-line)
    (define-key goto-map (kbd "o") #'consult-outline)
    (define-key goto-map (kbd "m") #'consult-mark)
    (define-key goto-map (kbd "k") #'consult-global-mark)
    (define-key goto-map (kbd "i") #'consult-imenu-project)
    (define-key goto-map (kbd "e") #'consult-compile-error)
    ;; M-s bindings (search-map)
    (define-key search-map (kbd "g") #'consult-git-grep)
    (define-key search-map (kbd "f") #'consult-find)
    (define-key search-map (kbd "k") #'consult-keep-lines)
    (define-key search-map (kbd "l") #'consult-line)
    (define-key search-map (kbd "m") #'consult-multi-occur)
    (define-key search-map (kbd "u") #'consult-focus-lines)
    ;; Other bindings (current-global-map)
    (global-set-key (kbd "M-y") #'consult-yank-pop)
    ;; Tweak functions
    (advice-add 'completing-read-multiple
                :override #'consult-completing-read-multiple))

Company: a modular complete anything framework for Emacs

Listing lst:configure-company configures company.

  (when (fboundp 'company-mode)
    (custom-set-variables
     ;; https://github.com/purcell/emacs.d/issues/778
     '(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)))

Minibuffer history completion

See Juri Linkov (Emacs Developer mailing list) for how to allow completion on previous input in the minibuffer. Listing lst:enable-minibuffer-history-completion enables minibuffer history completion.

  (with-eval-after-load 'minibuffer
    ;; https://github.com/oantolin/emacs-config/blob/master/init.el#L333
    (custom-set-variables
     '(completion-category-defaults nil)
     '(completion-category-overrides '((file (styles basic partial-completion))))
     '(completion-ignore-case t)
     '(completion-styles '(orderless basic partial-completion)))

    (defun minibuffer-setup-history-completions ()
      (unless (or minibuffer-completion-table minibuffer-completion-predicate)
        (setq-local minibuffer-completion-table
                    (symbol-value minibuffer-history-variable))))

    (add-hook 'minibuffer-setup-hook 'minibuffer-setup-history-completions)

    ;; Stolen from Emacs-28.1 for Emacs-27.2:
    (unless (fboundp 'minibuffer--completion-prompt-end)
      (defun minibuffer--completion-prompt-end ()
        (let ((end (minibuffer-prompt-end)))
          (if (< (point) end)
              (user-error "Can't complete in prompt")
            end))))

    ;; Adapted from minibuffer-complete:
    (defun minibuffer-complete-history ()
      "Allow minibuffer completion on previous input."
      (interactive)
      (completion-in-region (minibuffer--completion-prompt-end) (point-max)
                            (symbol-value minibuffer-history-variable)
                            nil))

    (define-key minibuffer-local-map (kbd "C-<tab>") #'minibuffer-complete-history))

Search and replace (info)

Deadgrep

Deadgrep uses ripgrep for superfast text searching in the default directory or the current VCS directory tree. Listing lst:configure-deadgrep binds deadgrep to .

  (when (autoload 'deadgrep "deadgrep" nil t)
    (define-key search-map (kbd "d") #'deadgrep))
  (with-eval-after-load 'deadgrep
    (define-key deadgrep-mode-map (kbd "C-c C-w") #'deadgrep-edit-mode))

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:configure-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
    (custom-set-variables
     '(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-show-all nil t))
    (add-hook 'org-mode-hook #'ediff-with-org-show-all))

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 (ensure-package-installation 'nov)
    (when (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 (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).
      (define-key pdf-outline-minor-mode-map (kbd "o") nil)
      (define-key pdf-outline-minor-mode-map (kbd "O") #'pdf-outline))

    (with-eval-after-load 'pdf-view
      (custom-set-variables
       '(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. For instance, 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. Listing lst:require-auctex initializes AUCTeX properly for LuaTeX or LuaLaTeX. In case the output of LuaLaTeX has missing fonts, listing lst:update-lualatex-opentype-font-name-database defines a function to update the OpenType Font name database for LuaLaTeX. Finally, out of the box, AUCTeX does not indent text between square brackets. The code in listing lst:configure-auctex corrects this by advising to override TeX-brace-count-line with TeX-brace-count-line-advice.

  ;; Use `require' to make `TeX-master' a safe local variable.
  (when (require 'tex nil 'noerror)
    (custom-set-variables
     '(TeX-auto-save t)
     '(TeX-engine 'luatex)
     '(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))))))
  (when (require 'tex nil 'noerror)
    ;; https://emacs.stackexchange.com/a/35507 answers:
    ;; How to indent between square brackets?
    (defun TeX-brace-count-line-override ()
      "Count number of open/closed braces."
      (save-excursion
        (let ((count 0) (limit (line-end-position)) char)
          (while (progn
                   (skip-chars-forward "^{}[]\\\\" limit)
                   (when (and (< (point) limit) (not (TeX-in-comment)))
                     (setq char (char-after))
                     (forward-char)
                     (cond ((eq char ?\{)
                            (setq count (+ count TeX-brace-indent-level)))
                           ((eq char ?\})
                            (setq count (- count TeX-brace-indent-level)))
                           ((eq char ?\[)
                            (setq count (+ count TeX-brace-indent-level)))
                           ((eq char ?\])
                            (setq count (- count TeX-brace-indent-level)))
                           ((eq char ?\\)
                            (when (< (point) limit)
                              (forward-char) t))))))
          count)))

    (defun toggle-TeX-brace-count-line-override ()
      "Toggle `TeX-brace-count-line-override' advice."
      (interactive)
      (toggle-advice 'TeX-brace-count-line :override #'TeX-brace-count-line-override))

    (toggle-TeX-brace-count-line-override))

Listing lst:configure-bibtex configures the Emacs bibtex library to use the LaTeX BiBTeX dialect for backwards compatibility. Listing lst:configure-font-latex disables font scaling of section titles. Listing lst:configure-latex configures latex for a full featured LaTeX-section-command.

  (with-eval-after-load 'bibtex
    (custom-set-variables '(bibtex-dialect 'BibTeX)))
  (with-eval-after-load 'font-latex
    (custom-set-variables
     '(font-latex-fontify-sectioning 1.0)))
  (with-eval-after-load 'latex
    (custom-set-variables
     '(LaTeX-section-hook '(LaTeX-section-heading
                            LaTeX-section-title
                            LaTeX-section-toc
                            LaTeX-section-section
                            LaTeX-section-label))))

TODO Improve the AUCTeX configuration slowly

Writing Org files

Org activation (info)

  (when (cl-every #'fboundp '(org-agenda org-capture))
    (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)))

Org customization

The code in listing lst:customize-org-babel, lst:customize-org, lst:customize-org-link, lst:customize-org-export, and lst:customize-org-latex-export does basic customization of Org-mode variables. Listing lst:customize-org-link configures org-link to use relative file path links. Listing lst:customize-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:customize-org-latex-classes.

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

tag placeholder
+1 [DEFAULT-PACKAGES]
+2 [PACKAGES]
+3 [EXTRA]
-1 [NO-DEFAULT-PACKAGES]
-2 [NO-PACKAGES]
-3 [NO-EXTRA]
  (with-eval-after-load 'ob-core
    (custom-set-variables
     '(org-confirm-babel-evaluate nil)))

  (with-eval-after-load 'ob-latex
    (custom-set-variables
     '(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 'sly
      (custom-set-variables
       '(org-babel-lisp-eval-fn #'sly-eval))))

  (with-eval-after-load 'ob-python
    (custom-set-variables
     '(org-babel-python-command "python -E")))
  (with-eval-after-load 'org
    (custom-set-variables
     '(org-babel-load-languages '((C . t)
                                  (calc . t)
                                  (dot . t)
                                  (emacs-lisp . t)
                                  (eshell . t)
                                  (fortran . t)
                                  (gnuplot . t)
                                  (latex . t)
                                  (lisp . t)
                                  (maxima . t)
                                  (org . t)
                                  (perl . t)
                                  (python . t)
                                  (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))
     '(org-structure-template-alist '(("a" . "export ascii")
                                      ("c" . "center")
                                      ("C" . "comment")
                                      ("e" . "example")
                                      ("E" . "export")
                                      ("h" . "export html")
                                      ("l" . "export latex")
                                      ("q" . "quote")
                                      ("s" . "src")
                                      ("p" . "src python :session :async")
                                      ("v" . "verse")))))
  (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)))

    (with-eval-after-load 'org
      (define-key org-mode-map (kbd "$") #'org-electric-dollar)
      (define-key org-mode-map (kbd "M-q") #'org-fill-paragraph)
      (when (require 'org-menu nil 'noerror)
        (define-key org-mode-map (kbd "C-c m") 'org-menu))))
  (with-eval-after-load 'ol
    (custom-set-variables
     '(org-link-file-path-type 'relative)))
  (with-eval-after-load 'ox
    (custom-set-variables
     '(org-export-dispatch-use-expert-ui t)))
  (with-eval-after-load 'org
    (custom-set-variables
     '(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
    (custom-set-variables
     '(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-listings 'minted)
     '(org-latex-minted-langs '((cc "c++")
                                (conf "text")
                                (cperl "perl")
                                (diff "diff")
                                (shell-script "bash")
                                (org "text")
                                (toml "toml")))
     '(org-latex-minted-options '(("bgcolor" "LightGoldenrodYellow")))
     `(org-latex-logfiles-extensions
       ',(cl-union '("lof" "lot") org-latex-logfiles-extensions :test #'equal))
     '(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}")))))))

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, orderless, 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. Listing lst:configure-oc-cite+citar configures org-cite, citar, and org.

  (with-eval-after-load 'oc
    (require 'oc-biblatex)
    (require 'oc-csl)

    (custom-set-variables
     '(org-cite-export-processors '((latex biblatex)
                                    (t csl)))
     '(org-cite-global-bibliography '("~/VCS/research/refs.bib")))

    (when (require 'citar nil 'noerror)
      (custom-set-variables
       '(org-cite-activate-processor 'citar)
       '(org-cite-follow-processor 'citar)
       '(org-cite-insert-processor 'citar))))

  (with-eval-after-load 'org
    (when (require 'citar nil 'noerror)
      (custom-set-variables
       '(citar-bibliography '("~/VCS/research/refs.bib"))
       '(citar-file-extensions '("djvu" "pdf"))
       '(citar-library-paths '("~/VCS/research/papers/"))))

    (define-key org-mode-map (kbd "C-c b") #'org-cite-insert))

Ref. [cite:@Schulte.MCSE.2011.41] shows that Org-mode is a simple, plain-text markup language for hierarchical documents allowing intermingling of data, code, and prose. It serves as an example link to 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 an Org-mode cite link:

    1. Type .
    2. Navigate to the bibliographic entry to insert.
    3. Select it.
  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.

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-infixed-blocks (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-infixed-blocks "emacs-lisp-setup"))

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

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

  (with-eval-after-load 'emacs
    (defun my-org-eval-blocks-named (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)))))

    ;; Emacs looks for "Local variables:" after the last "?\n?\f".
    (add-to-list 'safe-local-eval-forms
                 '(apply 'my-org-eval-blocks-named '("emacs-lisp-setup"))))

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:emacs-lisp-setup-latex-header 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
      (custom-set-variables
       '(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.

#+INCLUDE: <FILE> (info)

Evaluation of the source block in listing lst:make-source-block-with-export-keyword-settings produces the source block that exports to listing lst:source-file-export-keyword-settings in order to show the first nine lines of this /gav451/emacs.d/src/commit/1ed603ba5dfa8b72362025ebbeecf33345ac0f3c/README.org file. The last line shows that include.org is the argument for #+INCLUDE: <FILE>.

  echo "#+caption[Source file export keyword settings]:"
  echo "#+caption: The first nine lines of README.org containing the export"
  echo "#+caption: keyword settings."
  echo "#+name: lst:source-file-export-keyword-settings"
  echo "#+begin_src org :tangle no"
  head -n 9 README.org
  echo -n "#+end_src"
#+title: Emacs setup for use with LaTeX, Org, and Python
#+author: Gerard Vermeulen
#+babel: :cache no
#+latex_class: article-local
#+latex_class_options: [11pt,a4paper,english,svgnames,tables]
#+macro: kbd (eval (by-backend-kbd-org-macro $1))
#+property: header-args:emacs-lisp :exports code :results silent :tangle init.el
#+property: header-args:org :tangle include.org
#+startup: content

Listing lst:by-backend-kbd-org-macro defines the tools for the definition of the Org mode kbd macro on the fifth line of listing lst:source-file-export-keyword-settings.

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

    ;; https://orgmode.org/worg/org-contrib/babel/languages/ob-doc-LaTeX.html#orge5c50fd
    (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)))))

Listing lst:use-latex-header-1, lst:use-latex-header-2, lst:use-latex-header-3, lst:use-latex-header-4, and lst:use-latex-header-5 tangle into the include.org file in order to create the LaTeX preamble.

  #+begin_src latex-header
    % LuaLaTeX-, PdfLaTeX-, or XeTeX-COMPILER COMPATIBILITY:
    % Prevent collisions by using font packages before compiler specific packages.
    \usepackage{ifthen,ifluatex,ifxetex}
    \ifthenelse{\boolean{luatex}}{
      \usepackage{fontspec}        % lualatex
    }{\ifthenelse{\boolean{xetex}}{
        \usepackage{mathspec}        % xetex
      }{
        \usepackage[T1]{fontenc}     % pdflatex
        \usepackage[utf8]{inputenc}  % pdflatex
      }
    }
    \usepackage{amsmath}
    \usepackage{amssymb}
    \usepackage{pifont}          % check mark (\ding{52}) and cross mark (\ding{56})
    \usepackage{textcomp}        % \texttimes
    \usepackage{wasysym}         % \diameter

    % Org-mode REQUIREMENTS:
    \usepackage{graphicx}
    \usepackage{longtable}
    \usepackage{wrapfig}
    \usepackage{rotating}
    \usepackage[normalem]{ulem}
    \usepackage{capt-of}
    \usepackage{hyperref}

  #+end_src
  #+begin_src latex-header
    % LANGUAGE:
    \usepackage{babel}
    \usepackage{fvextra}
    \usepackage{csquotes}

    % LISTS:
    \usepackage{enumitem}
    \setlist{noitemsep}

    % LISTINGS:
    % Section 2.6 of caption-eng.pdf (texdoc caption) explains that the sign
    % of "skip" depends on the assumption "position=above" or "position=below".
    % The assumption should match the real caption position in the LaTeX code.
    \usepackage{caption}
    \usepackage[newfloat]{minted}
    \captionsetup[listing]{position=below,skip=0em}
    \usemintedstyle{xcode}

    % TABLES:
    % https://tex.stackexchange.com/questions/341205/
    % what-is-the-difference-between-tabular-tabular-and-tabularx-environments
    % https://emacs.stackexchange.com/questions/26179/
    % change-org-mode-table-style-just-for-latex-export
    % https://tex.stackexchange.com/questions/468585/
    % table-formatting-using-siunitx
    \usepackage{booktabs}
    \usepackage{colortbl}
    \usepackage{tabularx}  % DANGER: beware of Org table :width and :align options!

  #+end_src
  #+begin_src latex-header
    % PAGE LAYOUT:
    \usepackage{fancyhdr}
    \usepackage{lastpage}
    \usepackage[
      headheight=20mm,
      top=40mm,
      bottom=20mm,
      left=60pt,
      right=60pt,
      heightrounded,
      verbose,
    ]{geometry}

    % TECHNICS:
    \usepackage{siunitx}
    \usepackage{tikz}

  #+end_src
  #+begin_src latex-header
    % FLOAT BARRIERS:
    % https://tex.stackexchange.com/questions/118662/use-placeins-for-subsections
    % Make section an implicit float barrier:
    \usepackage[section]{placeins}
    % Make subsection an implicit float barrier:
    \makeatletter
    \AtBeginDocument{%
      \expandafter\renewcommand\expandafter\subsection\expandafter{%
        \expandafter\@fb@secFB\subsection
      }%
    }
    \makeatother
    % Make subsubsection an implicit float barrier:
    \makeatletter
    \AtBeginDocument{%
      \expandafter\renewcommand\expandafter\subsubsection\expandafter{%
        \expandafter\@fb@secFB\subsubsection
      }%
    }
    \makeatother

  #+end_src
  #+begin_src latex-header
    % FANCY HEADERS AND FOOTERS:
    % Add fancy headers and footers to normal pages.
    \pagestyle{fancy}
    \fancyhf{}
    \renewcommand{\footrulewidth}{0.4pt}
    \fancyfoot[C]{\emph{
        Emacs setup for use with \LaTeX{}, Org, and Python -- Gerard Vermeulen}}
    \renewcommand{\headrulewidth}{0.4pt}
    \fancyhead[L]{\includegraphics[height=1.8cm]{Org-mode-unicorn.png}}
    \fancyhead[C]{
      Page: \thepage/\pageref{LastPage} \\
      \text{ } \\
      \text{ } \\
      DRAFT
    }
    \fancyhead[R]{\includegraphics[height=1.8cm]{Emacs-logo.png}}

    % Add fancy header and footer to custom titlepage.
    % https://tex.stackexchange.com/questions/506102/
    % adding-header-and-footer-to-custom-titlepage
    \fancypagestyle{titlepage}{%
      \fancyhf{}
      \renewcommand{\footrulewidth}{0.4pt}
      \fancyfoot[C]{\emph{
          Emacs setup for use with \LaTeX{}, Org, and Python -- Gerard Vermeulen}}
      \renewcommand{\headrulewidth}{0.4pt}
      \fancyhead[L]{\includegraphics[height=1.8cm]{Org-mode-unicorn.png}}
      \fancyhead[C]{
        \pageref{LastPage} pages \\
        \text{ } \\
        \text{ } \\
        DRAFT
      }
      \fancyhead[R]{\includegraphics[height=1.8cm]{Emacs-logo.png}}
    }
    % #+latex_header: END.
  #+end_src

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/1ed603ba5dfa8b72362025ebbeecf33345ac0f3c/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}
  \\listoftables
  \\listoffigures
  \\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 (autoload 'pp-display-expression "pp")
      (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

Wordnet

The wordnut package is a major mode interface to Wordnet, a lexical database for the English language. Listing lst:check-wordnut checks whether the system meets the wordnut prerequisites.

  (when (ensure-package-installation 'wordnut)
    (unless (executable-find "wn")
      (message "`wordnut' fails to find the `wn' executable")))

Writegood mode

Writegood mode is a minor mode to aid in finding common writing problems. Matt Might's "My Ph.D. advisor rewrote himself in bash" scripts inspired this mode. Listing lst:configure-writegood-mode configures writegood mode.

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

Programming

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)
    (when (fboundp 'format-all-ensure-formatter)
      (add-hook 'prog-mode-hook #'format-all-ensure-formatter))

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

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 'sly)
    (with-eval-after-load 'sly
      (custom-set-variables
       ;; Customize `sly-default-lisp' instead of `inferior-lisp-program',
       ;; because `sly' uses `inferior-lisp-program' only as a backwards
       ;; compatibility fallback option.
       '(sly-default-lisp 'sbcl)
       `(sly-lisp-implementations
         '((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")))

      (define-key sly-prefix-map (kbd "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:

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

  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:
  ;;; Hey Emacs, this is my -*- lisp -*- .sbclrc file.
  #-quicklisp
  (let ((quicklisp-init (merge-pathnames "quicklisp/setup.lisp"
                                         (user-homedir-pathname))))
    (when (probe-file quicklisp-init)
      (load quicklisp-init)))

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 packages:

  1. Eglot - Emacs polyGLOT: an Emacs LSP client that stays out of your way. 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. Anaconda - code navigation, documentation lookup, and completion for Python.

In my opinion, eglot has more potential than anaconda, but anaconda is compatible with source code block editing while eglot is not.

Here are a few links covering how to integrate Emacs, Python and a Python LSP server, before plunging into the configuring 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

Python-mode

Listing lst:configure-python tells the Python shell to disregard its environment variables (in particular PYTHONSTARTUPFILE). The pythonic and pyvenv packages provide support 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) and tweak the environment variables and restart the relevant Python child processes (in case of pyvenv). Therefore, this setup replaces those packages with listing lst:manage-pyenv to manage pyenv from within Emacs and listing lst:setting-python-shell-virtualenv-root to set python-shell-virtualenv-root.

  (with-eval-after-load 'python
    (custom-set-variables
     '(python-indent-guess-indent-offset nil)
     '(python-shell-interpreter-args "-i -E")))
  (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 (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)))))

Eglot

Listing lst:configure-eglot+python-lsp-server-for-python tangles to user-init-file and configures eglot for Python using the python-lsp-server. In order to enable all builtin python-lsp-server capabilities, ensure installation of the Python packages autopep8, flake8, pydocstyle, pylint, rope, and yapf. In addition, install the pyls-flake8 plugin to let python-lsp-server use flake8.

Listing lst:on-hack-local-variables-hook-eglot-maybe defines a hook function to launch eglot in presence of a proper .dir-locals.el file in the root directory of any Python project. Listing lst:eglot-directory-variables-for-python shows such a proper .dir-locals.el file.

  (with-eval-after-load 'eglot
    ;; (setq eglot-server-programs '((python-mode "pylsp")))
    (add-to-list 'eglot-server-programs '(python-mode "pylsp"))

    (setq-default
     eglot-workspace-configuration
     '(;; Disable the `:pyls_flake8' plugin to fall back to pycodestyle.
       (:pylsp . (:plugins (:pyls_flake8 (:enabled t))))
       (:pylsp . (:plugins (:jedi_completion (:cache_for ["astropy"]))))
       (:pylsp . (:plugins (:jedi (:auto_import_modules ["numpy"]))))))

    (define-key eglot-mode-map (kbd "C-c n") #'flymake-goto-next-error)
    (define-key eglot-mode-map (kbd "C-c p") #'flymake-goto-prev-error)
    (define-key eglot-mode-map (kbd "C-c r") 'eglot-rename))
  (when (fboundp '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 'hack-local-variables-hook
              (defun on-hack-local-variables-hook-eglot-maybe ()
                (when (and (derived-mode-p 'python-mode)
                           (assoc 'eglot-workspace-configuration
                                  dir-local-variables-alist))
                  (eglot-ensure)))))
  ;; Proposal for a .dir-locals.el file in the root of any Python project.
  ((python-mode
    . ((eglot-workspace-configuration
        . (;; Disable the `:pyls_flake8' plugin to fall back to pycodestyle.
           (:pylsp . (:plugins (:pyls_flake8 (:enabled t))))
           (:pylsp . (:plugins (:jedi (:auto_import_modules ["numpy"]))))
           (:pylsp . (:plugins (:jedi_completion (:cache_for ["astropy"])))))))))
command keys key map
xref-find-definition

(eval

global-map
xref-pop

(eval

global-map
flymake-goto-next-error

(eval

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

(eval

eglot-mode-map
eglot-rename

(eval

eglot-mode-map
eldoc-doc-buffer

(eval

eglot-mode-map

Listing lst:pyproject-toml-kick-off and lst:setup-cfg-kick-off implement the rules in using black with other tools in order to make flake8 or pycodestyle agree with black's uncompromising style.

  [build-system]
  requires = ["setuptools", "wheel"]
  build-backend = "setuptools.build_meta"

  [tool.black]
  line-length = 88
  [flake8]
  max-line-length = 88
  extend-ignore = E203

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

Jedi provides grammar checking and completion candidates to python-lsp-server. Only jedi-0.18.1 works with for instance numpy-1.22.0 in the sense that it does not choke on universal functions provided that jedi does not parse but imports numpy-1.22.0 (see jedi issue #1744, #1745, and #1746). Since the universal functions are neither builtin methods nor data instances but a kind of "callable instances", the Python inspect module also fails to handle the universal functions properly.

Listing lst:make-pylsp-server-patch generates and listing lst:echo-pylsp-server-patch echos the patch in listing lst:show-pylsp-server-patch to make jedi import numpy-1.22.2 in order to serve the information to python-lsp-server allowing it to handle universal functions.

  git -C $HOME/VCS/python-lsp-server diff >pylsp-auto-import-modules.patch
  echo "#+attr_latex: :options breaklines"
  echo "#+caption[Show =pylsp-auto-import-modules.patch=]:"
  echo "#+caption: Show =pylsp-auto-import-modules.patch=."
  echo "#+name: lst:show-pylsp-server-patch"
  echo "#+begin_src diff :tangle no"
  cat pylsp-auto-import-modules.patch
  echo -n "#+end_src"
diff --git a/pylsp/config/schema.json b/pylsp/config/schema.json
index c29d78b..4f30101 100644
--- a/pylsp/config/schema.json
+++ b/pylsp/config/schema.json
@@ -69,6 +69,14 @@
       "default": null,
       "description": "List of errors and warnings to enable."
     },
+    "pylsp.plugins.jedi.auto_import_modules": {
+      "type": "array",
+        "default": ["numpy", "gi"],
+      "items": {
+        "type": "string"
+      },
+      "description": "List of module names for jedi to import (jedi.settings.auto_import_modules)."
+    },
     "pylsp.plugins.jedi.extra_paths": {
       "type": "array",
       "default": [],
diff --git a/pylsp/workspace.py b/pylsp/workspace.py
index bf312f6..4758b53 100644
--- a/pylsp/workspace.py
+++ b/pylsp/workspace.py
@@ -14,6 +14,8 @@ from . import lsp, uris, _utils

 log = logging.getLogger(__name__)

+DEFAULT_AUTO_IMPORT_MODULES = ["numpy", "gi"]
+
 # TODO: this is not the best e.g. we capture numbers
 RE_START_WORD = re.compile('[A-Za-z_0-9]*$')
 RE_END_WORD = re.compile('^[A-Za-z_0-9]*')
@@ -252,6 +254,8 @@ class Document:

         if self._config:
             jedi_settings = self._config.plugin_settings('jedi', document_path=self.path)
+            jedi.settings.auto_import_modules = jedi_settings.get('auto_import_modules',
+                                                                  DEFAULT_AUTO_IMPORT_MODULES)
             environment_path = jedi_settings.get('environment')
             extra_paths = jedi_settings.get('extra_paths') or []
             env_vars = jedi_settings.get('env_vars')

Jedi

Listing lst:example-py is a Python example to test whether jedi in combination with and either anaconda or eglot works when coding certain functions of for instance numpy and scipy.

  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))
        (define-key map (kbd "M-p") #'code-cells-backward-cell)
        (define-key map (kbd "M-n") #'code-cells-forward-cell)
        (define-key map (kbd "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

  (with-eval-after-load 'emacs
    (require 'iedit nil 'noerror))

Unobtrusive whitespace trimming

  (when (ensure-package-installation 'ws-butler)
    (when (require 'ws-butler nil 'noerror)
      (custom-set-variables
       '(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 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.

  (with-eval-after-load 'emacs
    ;; Require `smartparens-config' instead of `smartparens' to disable
    ;; pairing of the quote character for lisp modes,
    (when (require 'smartparens-config nil 'noerror)
      (custom-set-variables
       '(sp-base-key-bindings 'sp)
       '(sp-override-key-bindings '(("C-(" . sp-backward-barf-sexp)
                                    ("C-)" . sp-forward-slurp-sexp))))

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

      (dolist (hook '(emacs-lisp-mode-hook
                      ielm-mode-hook
                      lisp-mode-hook
                      python-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 (ensure-package-installation 'electric-operator)
    (when (fboundp 'electric-operator-mode)
      (add-hook 'c-mode-common #'electric-operator-mode)
      (add-hook 'python-mode-hook #'electric-operator-mode)))

Smart snippets

  (when (require 'yasnippet nil 'noerror)
    (custom-set-variables
     '(yas-alias-to-yas/prefix-p nil))
    (yas-global-mode +1))

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 tables by means of org-narrow-to-table.

  (with-eval-after-load 'emacs
    (autoload 'org-at-table "org-table")

    (defun org-narrow-to-table ()
      "Narrow buffer to current table."
      (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-dwim otherwise.
  Dwim means: region, org-src-block, org-table, 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 (ignore-errors (org-edit-src-code))
                 (ignore-errors (org-narrow-to-block))
                 (ignore-errors (org-narrow-to-table))
                 (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))))

    (define-key ctl-x-map (kbd "n t") #'org-narrow-to-table)
    (define-key ctl-x-map (kbd "C-n") #'narrow-or-widen-dwim))

Text faces or styles (info)

This setup does not configure custom themes (info) in order to prevent endless useless tweaking. 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. In case of proper initialization of all face heigths, font scaling is easy as listing lst:set-default-face-height shows.

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

Visualize color codes and names

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

  (when (ensure-package-installation 'rainbow-mode)
    (when (fboundp 'rainbow-mode)
      (custom-set-variables
       '(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://www.reddit.com/r/emacs/comments/jwhr6g/batteries_included_with_emacs/
    (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
          (or "http://" "https://")
          (or (or "www.youtube.com/" "youtu.be/" "soundcloud.com/")
              (seq (+? nonl) (or ".mp4" ".webm") eos)))
      "Match hyperlinks to browse with mpv.")

    (if (version< emacs-version "28.0")
        (custom-set-variables
         '(browse-url-browser-function
           `((,browse-url-mpv-regexp . browse-url-mpv)
             ("." . eww-browse-url))))
      (custom-set-variables
       '(browse-url-handlers
         `((,browse-url-mpv-regexp . browse-url-mpv)
           ("." . eww-browse-url)))))

    (custom-set-variables
     `(browse-url-generic-program ,(or (when (eq system-type 'darwin) "open")
                                       (executable-find "firefox")))))

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

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

Webjump

Listing lst:configure-webjump binds

(eval

to webjump and initializes a list of webjump-sites.

  (when (fboundp 'webjump)
    (global-set-key (kbd "C-c j") 'webjump)
    (with-eval-after-load 'webjump
      (custom-set-variables
       '(webjump-sites
         '(("CS 325 AI Programming" . "courses.cs.northwestern.edu/325")
           ("Emacs Git Repository" . "git.savannah.gnu.org/cgit/emacs.git")
           ("Emacs News" . "sachachua.com/blog/category/emacs-news")
           ("GNU ELPA Git Repository" . "git.savannah.gnu.org/cgit/emacs/elpa.git")
           ("NonGNU ELPA Git Repository" . "git.savannah.gnu.org/cgit/emacs/nongnu.git")
           ("Org Mode Git Repository" . "git.savannah.gnu.org/cgit/emacs/org-mode.git")
           ("Org Mode List" . "list.orgmode.org")
           ("Worg - Org Mode Community" . "orgmode.org/worg")
           ("Planet Emacs Life" . "planet.emacslife.com")
           ("Asian Pacific Journal Japan Focus" . "apjjf.org")
           ("Counterpunch" . "www.counterpunch.org")
           ("Dictionary EN" . [simple-query "thefreedictionary.com"
                                            "thefreedictionary.com/" ""])
           ;; CNTRL refuses http, accepts https, and lets Eww hang.
           ;; ("Dictionary FR" . [simple-query "www.cntrl.fr"
           ;;                                  "https://www.cntrl.fr/definition/" ""])
           ("Dictionary NL" . [simple-query "www.woorden.org"
                                            "www.woorden.org/woord/" ""])
           ("Dictionary Webster" . [simple-query
                                    "www.webster-dictionary.org"
                                    "www.webster-dictionary.org/definition/" ""])
           ("NRC". "www.nrc.nl")
           ("The Guardian" . "www.theguardian.com/international")
           ("Trouw" . "www.trouw.nl")
           ("Volkskrant" . "www.volkskrant.nl"))))))

Elfeed: Emacs web feed reader

Listing lst:configure-elfeed configures elfeed and makes a minimal attempt to enable emms.

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

    (with-eval-after-load 'elfeed
        (custom-set-variables
         '(elfeed-feeds
           '(("http://www.howardism.org/index.xml" h-abrams)
             ("https://emacshorrors.com/feed.atom" v-schneidermann)
             ("https://emacsninja.com/emacs.atom" v-schneidermann)
             ("https://feeds.feedburner.com/InterceptedWithJeremyScahill" j-scahill)
             ("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://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 (require 'emms-setup nil 'noerror)
        (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:configure-emms configures emms.

  (when (ensure-package-installation 'emms)
    (with-eval-after-load 'emms
      (custom-set-variables
       '(emms-player-list '(emms-player-mpd emms-player-mpv))))

    (with-eval-after-load 'emms-mode-line
      (custom-set-variables
       '(emms-mode-line-format "")))

    (with-eval-after-load 'emms-player-mpd
      (custom-set-variables
       `(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
      (custom-set-variables
       '(emms-player-mpv-ipc-method 'ipc-server)
       '(emms-player-mpv-update-metadata t)))

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

    (with-eval-after-load 'emms-playlist-mode
      (custom-set-variables
       '(emms-playlist-mode-center-when-go t)))

    (with-eval-after-load 'emms-streams
      (custom-set-variables
       `(emms-streams-file
         ,(no-littering-expand-etc-file-name "emms/streams.emms")))
      (emms-all)))

Taming spurious buffers

When emms-player-mpd opens a connection to mpd, 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:taming-spurious-buffers makes those *spurious* buffers less intrusive. See:

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

for technical information.

  (with-eval-after-load 'emms-player-mpd
    ;; https://stackoverflow.com/a/47587185 answers:
    ;; How to avoid pop-up of *Async Shell Command* buffers in Emacs?
    ;; Avoid popping up of `*spurious*' buffers:
    (add-to-list 'display-buffer-alist
                 '("\\*spurious\\*.*"
                   (display-buffer-at-bottom)
                   (window-height . fit-window-to-buffer))))

Music Player Daemon configuration configuration

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

  case "$(uname -s)" in
      Darwin)
          cat <<EOF > ~/.mpd/mpd.conf
  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"
  }
  EOF
          ;;
  esac

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

Only the Org source file shows the local variables footer.

\printbibliography