A bootstrapping Emacs-30.0.50 setup for technical PDF reports using Org-Mode, Org-Babel, Lisp, Python, and LaTeX.
Go to file
Gerard Vermeulen 4eaeaa43b3 Facilitate interactive exporting of one of my reports to LaTeX
* Add org=links for backwards compatibility with =org-ref=.
* Allow interactive activation of my =ox-extra= compatible filters.
2021-12-06 12:59:24 +01:00
.githooks Fix org-babel-tangle-file usage 2021-11-30 11:57:07 +01:00
.gitignore Improve the work-flow demonstration features 2021-11-30 09:35:15 +01:00
.ignore Fix ripgrep by overriding the .gitignore file 2021-12-01 11:52:28 +01:00
Makefile Fix org-babel-tangle-file usage 2021-11-30 11:57:07 +01:00
org-babel-tangle-file Fix org-babel-tangle-file usage 2021-11-30 11:57:07 +01:00
README.org Facilitate interactive exporting of one of my reports to LaTeX 2021-12-06 12:59:24 +01:00

Emacs setup for use with LaTeX, Org, and Python

Quick start

Backup your ~/.emacs.d directory to execute the following commands:

  cd ~
  git clone ccdr@mercury.grenoble.cnrs.fr:SERVER/.emacs.d
  make --directory=.emacs.d init

After its invokation, Emacs will install a minimal set of packages. Now, you have the option to install all optional packages using the command my-install-optional-packages, but you can do this any time, or you can install any package using the command package-install whenever you like. Quit Emacs and invoke Emacs again.

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 my work-flow by showing:

  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.

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 Antolín 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.
  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 and a show-case of readable Emacs lisp (info) code. I have stolen his idea of versioning the package-user-dir variable to prevent clashes between the byte-compiler output of different Emacs versions.
  6. Timothy E. Chapman's configuration

Early Init File (info)

The configuration inhibits enabling of Emacs' package management system at startup in order to get more control in the Package Bootstrapping section.

  ;;; early-init.el --- user early-init file        -*- lexical-binding: t -*-
  ;;; Commentary:
  ;;; Code:
  (setq package-enable-at-startup nil)
  (setq-default load-prefer-newer t)

  (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

Init File (info) header

The quoting (info) and the backquote (info) pages explain how to understand the reader macros ' (quote), ` (backquote), , (substitute) and @, (splice) in the custom-set-variable function call below. A tutorial of how to use those reader macro's is the didactic emacs-lisp macro example.

Because of the custom-set-variable function call, the init file (info) does not load the custom-file as saving customizations (info) recommends.

  ;;; 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
       (format "custom-%s.%s.el" emacs-major-version emacs-minor-version)))
   '(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)
   '(package-archives '(("gnu" . "https://elpa.gnu.org/packages/")
                        ("nongnu" . "https://elpa.nongnu.org/nongnu/")
                        ("melpa" . "https://melpa.org/packages/")))
   `(package-user-dir
     ,(locate-user-emacs-file
       (format "elpa-%s.%s" emacs-major-version emacs-minor-version)))
   '(python-indent-guess-indent-offset nil)
   '(recentf-mode t)
   '(save-place-mode t)
   '(savehist-additional-variables
     '(eww-history
       kill-ring
       regexp-search-string
       search-ring
       search-string))
   '(savehist-mode t)
   '(savehist-save-minibuffer-history 1)
   '(scroll-bar-mode nil)
   '(tab-always-indent 'complete)
   '(tab-width 8)
   '(tool-bar-mode nil)
   '(url-cookie-trusted-urls nil)
   '(url-cookie-untrusted-urls '(".*"))
   '(use-dialog-box nil)
   '(use-short-answer t)
   '(view-read-only 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)))

Package bootstrapping

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 MELPA - Milkypostmans Emacs Lisp Package Archive.

Finally, the quelpa tool allows to fetch code from any source and build a package on your computer before installation.

The output of the byte-compiler may change with each new Emacs release. Therefore, in order to prevent collisions between different Emacs versions, the package-user-directory has a suffix containing the major- and minor-version numbers of Emacs.

The order of the next 1nd, 2nd, and 3rd package-bootstrapping blocks matters because each of those blocks prepares Emacs for the next block.

If present, the package no-littering helps to keep ~/.emacs.d clean.

The code assumes that the package system is in a virgin state in case the package no-littering is not present. Refreshing the contents of available packages at least once is a requirement in order to be able to install and load any packages, hence also no-littering.

You have to refresh the list of available packages yourself before updating the installed packages.

Finally, my-install-packages facilitates installation of all packages in a list of packages.

  ;; The is the 1st package bootstrapping block.
  (require 'package)
  (package-initialize)

  (unless (require 'no-littering nil 'noerror)
    (package-refresh-contents)
    (package-install 'no-littering)
    (require 'no-littering))

  (defun my-install-packages (packages)
    "Ensure installation of all packages in PACKAGES."
    (dolist (package packages)
      (unless (package-installed-p package)
        (package-install package))))

Install the basic packages and in case this is Emacs-27.2, upgrade Org Mode for compatibility with Emacs-28.1. The quoting (info) and the backquote (info) pages explain how to understand the reader macros ' (quote), ` (backquote), , (substitute) and @, (splice) in the definition of my-packages variable below.

  ;; The is the 2nd package bootstrapping block.
  (defvar my-packages
    `(
      ,@(when (version< emacs-version "28.0")
          '(org))                 ; plain text thought organizer
      anaconda-mode               ; strangles python-mode
      auctex                      ; Aalborg University Center TeX
      blacken                     ; Black Python-code formatter client
      citar                       ; bibliography handling
      citeproc                    ; bibliography handling
      company-anaconda            ; complete anything in anaconda-mode
      consult                     ; consult completing-read
      eglot                       ; Emacs polyGLOT LSP client
      embark                      ; act on any buffer selection
      htmlize                     ; convert buffer contents to HTML
      leuven-theme                ; beautiful color theme
      marginalia                  ; minibuffer margin notes
      orderless                   ; Emacs completion style
      pdf-tools                   ; interactive docview replacement
      pyenv-mode                  ; Python environment selector
      quelpa                      ; install Emacs packages from source
      vertico)                    ; VERTical Interactive Completion
    "List of packages required packages.")

  (my-install-packages my-packages)

Facilitate installlation of the optional packages.

  ;; The is the 3rd package bootstrapping block.
  (defvar my-optional-packages
    `(
      ,@(when (version< emacs-version "28.0")
          '(modus-themes))        ; high foreground/background contrast themes
      async                       ; asynchroneous processing
      company                     ; complete anything
      electric-operator           ; automatic spacing around operators
      elfeed                      ; web feed reader
      emms                        ; Emacs Multi-Media System
      iedit                       ; simultaneous multi-entity editing
      laas                        ; LaTeX Auto-Activating Snippets
      magit                       ; Git Text-based User Interface
      markdown-mode               ; markdown text mode
      nov                         ; EPUB reader
      rainbow-mode                ; set background color to color string
      smartparens                 ; smart editing of character pairs
      toml-mode                   ; Tom's Obvious Minimal Language mode
      wgrep                       ; open a writable grep buffer
      which-key                   ; on the fly key-binding help
      wordnut                     ; WordNet lexical database
      writegood-mode              ; bullshit and weasel-word detector
      ws-butler                   ; remove trailing whitespace
      xr                          ; undo rx to grok regular expressions
      yasnippet))                 ; code or text template expansion

  (defun my-install-optional-packages ()
    (interactive)
    (my-install-packages my-optional-packages))

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.

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

The next two configuration blocks 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.

LaTeX 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 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 glo gls glg ist run.xml";
  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))

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 fills another niche than the five packages above.

Vertico (info)

  (unless noninteractive
    (when (fboundp 'vertico-mode)
      (vertico-mode +1)))

Orderless (info)

  (unless noninteractive
    (when (fboundp 'orderless-filter)
      (custom-set-variables
       ;; https://github.com/purcell/emacs.d/issues/778
       '(completion-styles '(basic completion-partial orderless))
       '(completion-category-defaults nil)
       '(completion-category-overrides
         '((file (styles partial-completion)))))
      (add-hook 'minibuffer-setup-hook
                (defun my-on-minibuffer-setup-hook()
                  (setq-default completion-styles '(substring orderless))))))

Embark (info)

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

Marginalia (info)

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

Consult (info)

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

Company: a modular complete anything framework for Emacs

  (unless noninteractive
    (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
                      python-mode-hook
                      ielm-mode-hook))
        (add-hook hook #'company-mode))))

Reading

EPUB files

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

PDF files

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

In order to use pdf-tools, you have to type M-x pdf-tools-install 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-tools-install)
    (autoload 'pdf-view-mode "pdf-view")
    (add-to-list 'magic-mode-alist '("%PDF" . pdf-view-mode)))

Writing

LaTeX

Loading tex.el immediately instead of lazily ensures proper initialization of the 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.

Out of the box, AUCTeX does not indent text between square brackets. The code below corrects this by advising to override TeX-brace-count-line with my-TeX-brace-count-line.

  ;; Try to get the `safe-local-variable` predicate for `TeX-master`
  (when (require 'tex nil 'noerror)
    ;; https://emacs.stackexchange.com/questions/17396/indentation-in-square-brackets
    (defun my-TeX-brace-count-line ()
      "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)))
    (advice-add 'TeX-brace-count-line :override #'my-TeX-brace-count-line))

Org-mode

Activation (info)

  ;; Inspect:
  ;; function with "C-h f"
  ;; symbols with "C-h o"
  ;; variables with "C-h v"

  (global-set-key (kbd "C-c a") #'org-agenda)
  (global-set-key (kbd "C-c c") #'org-capture)
  (global-set-key (kbd "C-c l") #'org-store-link)
  (global-set-key (kbd "C-c C-l") #'org-insert-link-global)

Customization

  (custom-set-variables
   '(org-babel-python-command "python -E")
   '(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)
                                (scheme . t)
                                (shell . t)))
   '(org-cite-export-processors '((latex biblatex)
                                  (t csl)))
   '(org-cite-global-bibliography '("~/VCS/research/refs.bib"))
   '(org-file-apps '((auto-mode . emacs)
                     (directory . emacs)
                     ("\\.mm\\'" . default)
                     ("\\.x?html?\\'" . default)
                     ("\\.pdf\\'" . emacs)))
   '(org-confirm-babel-evaluate nil)
   '(org-latex-compiler "lualatex")
   '(org-latex-hyperref-template nil)
   '(org-latex-listings 'minted)
   '(org-latex-logfiles-extensions '("blg" "lof" "log" "lot" "out" "toc"))
   '(org-latex-prefer-user-labels t)
   '(org-modules '(ol-bibtex
                   ol-doi
                   ol-eww
                   ol-info
                   org-id
                   org-protocol
                   org-tempo))
   '(org-src-fontify-natively t)
   '(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"))))

Citation export processors (info)

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

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

  (with-eval-after-load 'org
    (defun org-syntax-convert-keyword-case-to-lower ()
      "Convert all #+KEYWORDS to #+keywords."
      (interactive)
      (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)))))

Advanced Export Configuration (info)

Stolen from ox-extra.el

  (with-eval-after-load 'ox
    (defun my-org-export-ignore-headline-filter (_)
      "Ignore all headlines with the \":ignore:\" tag."
      (org-map-entries
       (lambda ()
         (delete-region (point) (line-beginning-position 2)))
       ":ignore:"))

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

    (defun my-activate-buffer-local-org-export-filters ()
      "Activate my export filters locally in the current buffer."
      (interactive)
      (add-hook 'org-export-before-parsing-hook
                #'my-org-export-ignore-headline-filter nil 'local)
      (add-hook 'org-export-before-parsing-hook
                #'my-org-latex-header-blocks-filter nil 'local)))
  (with-eval-after-load 'ox
    (my-activate-buffer-local-org-export-filters))
  (with-eval-after-load 'ox-latex
    (mapc (lambda (item)
            (add-to-list 'org-latex-classes item))
          '(;; The postfixes +1, +2, +3, -1, -2, and -3 denote:
            ;; +1 => [DEFAULT-PACKAGES]
            ;; +2 => [PACKAGES]
            ;; +3 => [EXTRA]
            ;; -1 => [NO-DEFAULT-PACKAGES]
            ;; -2 => [NO-PACKAGES]
            ;; -3 => [NO-EXTRA]
            ("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}")))))

Evaluate source blocks on loading

  (defun my-org-eval-blocks-named (name)
    "Evaluate all source blocks named NAME."
    (when (eq major-mode 'org-mode)
      (let ((blocks
             (org-element-map
                 (org-element-parse-buffer 'greater-element nil) 'src-block
               (lambda (block)
                 (when (string= name (org-element-property :name block))
                   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")))
  (add-to-list 'safe-local-eval-forms
               '(apply 'my-org-eval-blocks-named '("python-setup")))

Citing bibliography

Citar provides a completing-read front-end to browse and act on BibTeX, BibLaTeX, and CSL JSON bibliographic data, and LaTeX, markdown, and org-cite editing support.

Citar in combination with vertico, embark, and marginalia provides quick filtering and selecting of bibliographic entries from the minibuffer, and the option to run different commands on those selections.

  (when (cl-every #'fboundp '(citar-insert-citation
                              citar-insert-preset
                              citar-org-activate
                              citar-org-follow
                              citar-org-insert))
    (custom-set-variables
     '(org-cite-activate-processor 'citar)
     '(org-cite-follow-processor 'citar)
     '(org-cite-insert-processor 'citar))
    (global-set-key (kbd "C-c b") #'citar-insert-citation)
    (define-key minibuffer-local-map (kbd "M-b") #'citar-insert-preset))

Editing

Enable disabled commands and inform

Execute src_emacs-lisp[:exports code]{(find-library "novice")} to see how Emacs prevents new users from shooting themselves in the feet.

  (setq disabled-command-function
        (defun my-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)))

Narrowing

Emacs narrow-or-widen-dwim

  (defun narrow-or-widen-dwim (p)
    "Widen if buffer is narrowed, narrow-dwim otherwise.
  Dwim means: region, org-src-block, org-subtree, 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))
               (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 "C-n") #'narrow-or-widen-dwim)

Synchronal multiple-region editing

  (unless noninteractive
    (require 'iedit nil 'noerror))

Extraneous whitespace trimming

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

Smart character-pair handling

  (unless noninteractive
    (when (require 'smartparens-config nil 'noerror)
      ;; Requiring smartparens-config disables pairing of the quote
      ;; character for lisp modes, contrary to requiring smartparens.
      (custom-set-variables
       '(sp-base-key-bindings 'sp)
       '(sp-override-key-bindings '(("C-(" . sp-backward-barf-sexp)
                                    ("C-)" . sp-forward-slurp-sexp))))

      (add-hook 'LaTeX-mode-hook #'turn-off-smartparens-mode)
      (add-hook 'prog-mode-hook #'turn-on-smartparens-mode)
      (add-hook 'text-mode-hook #'turn-on-smartparens-mode)

      (add-hook 'emacs-lisp-mode-hook #'turn-on-smartparens-strict-mode)
      (add-hook 'ielm-mode-hook #'turn-on-smartparens-strict-mode)
      (add-hook 'python-mode-hook #'turn-on-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))

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

      (show-smartparens-global-mode +1)))

Electric operators

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

Coding

  (with-eval-after-load 'eglot
    (add-to-list 'eglot-server-programs '(python-mode "pylsp"))
    (setq-default
     eglot-workspace-configuration
     `((:pylsp . (:plugins (:jedi_completion (:eager nil))))
       (:pylsp . (:plugins (:jedi_completion (:cache_for ,(vconcat '("astropy"
                                                                     "numpy"
                                                                     "scipy")))))))))

Python coding

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. Anaconda - code navigation, documentation lookup, and completion for Python.
  2. 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.

The snippet below initializes anaconda. See elpy-module-company for how to handle company-backends as a local variable and the call to advice opens Python org-mode edit-buffers in anaconda-mode.

  (with-eval-after-load 'python

    (with-eval-after-load 'company
      (when (and (fboundp 'anaconda-mode)
                 (fboundp 'company-anaconda))
        (defun my-disable-anaconda-mode ()
          (when (derived-mode-p 'python-mode)
            (anaconda-mode -1)
            (set (make-local-variable 'company-backends)
                 (delq 'company-anaconda
                       (mapcar #'identity company-backends)))
            (anaconda-eldoc-mode -1)))

        (defun my-enable-anaconda-mode ()
          (when (derived-mode-p 'python-mode)
            (anaconda-mode +1)
            (set (make-local-variable 'company-backends)
                 (cons 'company-anaconda
                       (delq 'company-semantic
                             (delq 'company-capf
                                   (mapcar #'identity company-backends)))))
            (anaconda-eldoc-mode
             (if (file-remote-p default-directory) -1 1))))))

    (unless (and (fboundp 'my-disable-anaconda-mode)
                 (fboundp 'my-enable-anaconda-mode))
      (when (fboundp 'anaconda-mode)
        (defun my-disable-anaconda-mode ()
          (when (derived-mode-p 'python-mode)
            (anaconda-mode -1)
            (anaconda-eldoc-mode -1)))

        (defun my-enable-anaconda-mode ()
          (when (derived-mode-p 'python-mode)
            (anaconda-mode +1)
            (anaconda-eldoc-mode
             (if (file-remote-p default-directory) -1 1))))))

    (when (fboundp 'my-enable-anaconda-mode)
      (advice-add 'org-edit-src-code :after #'my-enable-anaconda-mode))

    (when (and (fboundp 'my-disable-anaconda-mode)
               (fboundp 'my-enable-anaconda-mode))
      (defun my-toggle-anaconda-mode ()
        (interactive)
        (if (bound-and-true-p anaconda-mode)
            (my-disable-anaconda-mode)
          (my-enable-anaconda-mode)))))
  import numpy
  import astropy.units as apu

  a = numpy.arange(0, 11)
  a = numpy.linspace(0, 10, num=11)
  q = apu.Quantity(a, apu.meter)
  print(q)
  (custom-set-variables
   '(python-shell-interpreter-args "-i -E"))
  (when (and (executable-find "pyenv")
             (require 'pyenv-mode nil 'noerror))
   (pyenv-mode +1)
   (pyenv-mode-set "3.9.9/envs/python-3.9.9"))
  (with-eval-after-load 'info
    (add-to-list 'Info-directory-list
                 (expand-file-name "~/.local/share/info")))

Look into:

  1. Emacs extension to insert numpy style docstrings in function definitions

Appearance

  (unless noninteractive
    ;; 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"))))

This setup prefers the leuven and leuven-dark themes from MELPA, because the very popular modus-operandi and modus-vivendi themes feel quirky: for instance those themes fail to display hl-line-mode with Emacs-27.2 on Darwin.

How to change custom theme faces

  (unless noninteractive
    ;; Try to detect `leuven-theme` from MELPA.
    (when (fboundp 'leuven-scale-font)
      (custom-set-variables
       '(leuven-scale-org-agenda-structure 1.0)
       '(leuven-scale-org-document-title 1.0)
       '(leuven-scale-outline-headlines 1.0)
       '(leuven-scale-volatile-highlight 1.0)))

    (defun my-leuven-hook-function ()
      (when (member 'leuven custom-enabled-themes)
        (let ((custom-inhibit--theme-enable nil)
              (ol1 (list :height 1.0 :weight 'bold
                         :foreground "#3C3C3C" :background "#F0F0F0"))
              (ol2 (list :height 1.0 :weight 'bold
                         :foreground "#123555" :background "#E5F4FB")))
          (custom-theme-set-faces
           'leuven
           `(font-latex-sectioning-2-face ((t ,ol1)))
           `(font-latex-sectioning-3-face ((t ,ol2)))
           `(info-title-1 ((t ,ol1)))
           `(markdown-header-face-1 ((t ,ol1)))
           `(markdown-header-face-2 ((t ,ol2)))
           `(org-level-1 ((t ,ol1)))
           `(org-level-2 ((t ,ol2)))
           `(outline-1 ((t ,ol1)))
           `(outline-2 ((t ,ol1)))))
        (enable-theme 'leuven)))

    (load-theme 'leuven 'no-confirm nil)

    (dolist (hook '(Info-mode-hook
                    latex-mode-hook
                    markdown-mode-hook
                    org-mode-hook
                    outline-mode-hook))
      (add-hook hook #'my-leuven-hook-function)))
  (unless noninteractive
    (custom-set-variables
     '(modus-themes-hl-line 'underline)
     '(modus-themes-intense-markup 't))
    (when (and (version< emacs-version "28.0")
               (require 'modus-themes nil 'noerror))
      (modus-themes-load-themes)))
  (unless noninteractive
    ;; https://karthinks.com/software/batteries-included-with-emacs/
    ;; https://www.reddit.com/r/emacs/comments/jwhr6g/batteries_included_with_emacs/
    (defun my-pulse-one-line (&rest _)
      "Pulse the current line."
      (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 #'my-pulse-one-line)))

Applications

Feed reader

  (autoload 'elfeed "elfeed" nil t)
  (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://ambrevar.xyz/atom.xml" p-neirhardt)
         ("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.aclu.org/taxonomy/feed-term/2152/feed" aclu)
         ("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)))))

Multi-media system

  (custom-set-variables
   '(emms-mode-line-format "")
   '(emms-player-list '(emms-player-mpd emms-player-mpv))
   `(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)
   '(emms-playing-time-display-format " %s ")
   '(emms-playlist-mode-center-when-go t))

  (defun my-emms-print-metadata-find ()
    (require 'find-func)
    (locate-file
     "emms-print-metadata"
     (expand-file-name
      "src"
      (file-name-directory (find-library-name "emms")))
     exec-suffixes #'file-executable-p))

  (with-eval-after-load 'emms
    (require 'emms-info-libtag)
    (let ((emms-print-metadata (my-emms-print-metadata-find)))
      (when emms-print-metadata
        (custom-set-variables
         '(emms-info-functions nil)
         `(emms-info-libtag-program-name ,emms-print-metadata))
        (add-hook 'emms-info-functions #'emms-info-libtag))))

  (with-eval-after-load 'elfeed-show
    (when (require 'emms-setup nil 'noerror)
      (emms-all)))

  (autoload 'emms-streams "emms-streams" nil 'interactive)
  (with-eval-after-load 'emms-streams (emms-all))

Local variables linking back to LaTeX save-compile-display-loop

Only the Org source file shows the local variables footer.