emacs.d/README.org

7761 lines
328 KiB
Org Mode
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#+title: Emacs setup for use with LaTeX, Lisp, Org, and Python
#+author: Gerard Vermeulen
#+latex_class: article
#+latex_class_options: [11pt,a4paper,english,svgnames]
#+macro: kbd (eval (by-backend-kbd-org-macro $1))
#+options: ^:{} timestamp:nil
#+property: header-args:emacs-lisp :exports code :tangle init.el
#+startup: showeverything
#+begin_src latex :noweb yes :results raw
,#+latex_header: <<latex-header-1>>
,#+latex_header: <<latex-header-2>>
,#+latex_header: <<latex-header-3>>
,#+latex_header: <<latex-header-4>>
,#+latex_header: <<latex-header-5>>
#+end_src
* Copying
:PROPERTIES:
:CUSTOM_ID: sec:copying
:END:
This README contains my Emacs setup for use with LaTeX, Lisp, Org, and Python.
Copyright \copy 2021-2023 Gerard Vermeulen.
#+BEGIN_QUOTE
All Org-mode source blocks with function definitions in this document are free
software: you can redistribute it and/or modify it under the terms of the GNU
General Public License as published by the Free Software Foundation, either
version 3 of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
#+END_QUOTE
#+BEGIN_QUOTE
Permission is granted to copy, distribute and/or modify the other parts
of this document under the terms of the GNU Free Documentation License,
Version 1.3 or any later version published by the Free Software
Foundation; with no Invariant Sections, with no Front-Cover Texts,
and with no Back-Cover Texts. You should have received a copy of the GNU
Free Documentation License with the source file of this document. If not,
see <https://www.gnu.org/licenses/>.
#+END_QUOTE
* Quick start
:PROPERTIES:
:CUSTOM_ID: sec:quick-start
:END:
This setup requires at least Emacs-30. The installation procedure starts with:
1. Backup the =user-emacs-directory= which defaults often to =~/.emacs.d=.
2. Run the listing [[lst:prepare-user-emacs-directory-with-https][prepare the =user-emacs-directory= without =ssh= access]] or
the listing [[lst:prepare-user-emacs-directory-with-ssh][prepare the =user-emacs-directory= with =ssh= access]] commands.
After invoking Emacs interactively, Emacs will ask you to install a selected set
of packages. Quit Emacs and invoke Emacs again.
#+caption[Prepare the user-emacs-directory without =ssh= access]:
#+caption: Clone and initialize the user-emacs-directory without =ssh= access.
#+name: lst:prepare-user-emacs-directory-with-https
#+begin_src shell -n :eval never :tangle no
cd ~
git clone https://forge.chapril.org/gav451/emacs.d.git .emacs.d
make --directory=.emacs.d init
emacs &
#+end_src
#+caption[Prepare the =user-emacs-directory= with =ssh= access]:
#+caption: Clone and initialize the user-emacs-directory with =ssh= access.
#+name: lst:prepare-user-emacs-directory-with-ssh
#+begin_src shell -n :eval never :tangle no
cd ~
git clone ssh://gitea@forge.chapril.org:222/gav451/emacs.d.git .emacs.d
make --directory=.emacs.d init
emacs &
#+end_src
* Introduction
:PROPERTIES:
:CUSTOM_ID: sec:introduction
:END:
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 [[https://orgmode.org/][Org Mode]] plain text with [[https://www.python.org/][Python]] source code blocks and the file
format of the presentations is [[https://www.latex-project.org/][LaTeX]].
This [[info:org#Top][org]] file (more precisely the original [[info:org#Top][org]] source file of this file)
illustrates three methods in my work-flow:
1. How to tangle (or export) source blocks from [[info:org#Top][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 [[info:org#Top][org]] files to other formats such as [[https://en.wikipedia.org/wiki/HTML][HTML]], [[https://www.latex-project.org/][LaTeX]], and [[https://en.wikipedia.org/wiki/PDF][PDF]].
3. How [[info:org#Hyperlinks][org hyperlinks (info)]] allow to link inside and outside [[info:org#Top][Org Mode]]: hover
over or click on the links to experiment.
The [[https://en.wikipedia.org/wiki/AUCTeX][AUCTeX - Aalborg University Center TeX]] extension package provides a
powerful [[https://en.wikipedia.org/wiki/Text-based_user_interface][Text-based User Interface (TUI)]] environment to edit the [[https://www.latex-project.org/][LaTeX]]
presentations.
The [[https://github.com/bdarcus/citar][Citar]] extension package provides quick filtering and selecting of
bibliographic entries, and the option to run different commands on those
selections. [[https://github.com/bdarcus/citar][Citar]] exploits the extension packages [[https://github.com/minad/vertico][Vertico]], [[https://github.com/oantolin/embark][Embark]], [[https://github.com/minad/marginalia][Marginalia]],
and [[https://github.com/minad/consult][Consult]]. The [[https://github.com/andras-simonyi/citeproc-el][CiteProc]] extension package provides [[https://citationstyles.org/][CSL: citation style
language]] processing capabilities to [[https://github.com/bdarcus/citar][Citar]] and [[https://orgmode.org/][Org Mode]]. A curated repository
of CSL styles is the [[https://github.com/citation-style-language/styles#readme][citation style language repository]].
The [[https://github.com/vedang/pdf-tools][PDF-Tools]] extension package renders [[https://en.wikipedia.org/wiki/PDF][PDF]] file with the possibility to
annotate the file or to click on anchors in the [[https://en.wikipedia.org/wiki/PDF][PDF]] file that link back to the
original [[https://www.latex-project.org/][LaTeX]] file of the document. An example of my work-flow are the steps
to convert this [[info:org#Top][org]] file to [[https://en.wikipedia.org/wiki/PDF][PDF]] and to see the result with [[HTTPS://github.com/vedang/pdf-tools][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
[[https://www.latex-project.org/][LaTeX]] compilation loop to update and redisplay the [[https://en.wikipedia.org/wiki/PDF][PDF]] file after execution of
the ~org-latex-export-latex-to-latex~ command in this buffer.
Here follows a list of interesting Emacs configurations:
1. [[https://git.kyleam.com/emacs.d/][Kyle Meyer's configuration]] shows the working environment of an Emacs and Org
developer.
2. [[https://github.com/alhassy/emacs.d][Musa Al-hassy's configuration]] is an impressive example of producing the Emacs
initialization files and other files by tangling an [[info:org#Top][org]] file. His methodology
is impressive, as his [[https://alhassy.github.io/ElispCheatSheet/][Elisp Cheat Sheet]] and [[https://alhassy.github.io/org-special-block-extras/][org-special-block-extra package]]
show. To me, this is a configuration to admire, but his methodology is way
over my head.
3. [[https://github.com/oantolin/emacs-config][Omar Antolin Camarena's configuration]] exploits built-in packages, Omar's own
small packages, and large external packages. Omar is the author of [[https://github.com/oantolin/orderless][orderless]]
and [[https://github.com/oantolin/embark][embark]]. My use =setopt= to set Emacs options instead of the customize
interface comes from his idea of using =custom-set-variables= although that
idea is [[https://www.masteringemacs.org/article/bad-emacs-advice][bad Emacs advice]] for new users.
4. [[https://gitlab.com/ambrevar/dotfiles][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.
5. [[https://sachachua.com/dotemacs/][Sacha Chua's configuration]] is a practical example of producing the Emacs
initialization files by tangling an [[info:org#Top][org]] file. It gives me the impression
that she is a practical person trying to achieve her goals by the most
efficient means.
6. [[https://github.com/purcell/emacs.d][Steve Purcell's configuration]] is well organized and a showcase of readable
code. Its commit and issue histories are also helpful: see for instance the
discussion [[https://github.com/purcell/emacs.d/issues/778][The order of company candidates is incorrect in Emacs lisp mode]].
Here follows a list of links on how to use Emacs and Elisp:
1. [[https://www.masteringemacs.org/][Mastering Emacs]] is a link to a blog with interesting posts that promotes a
book on how to become a proficient Emacs user.
2. [[https://www2.lib.uchicago.edu/keith/emacs/][Use GNU Emacs: The Plain Text Computing Environment]] explains the fundamentals
of the Emacs abstractions before showing how to exploit those abstractions
interactively. It targets a similar audience as the [[https://www.masteringemacs.org/][Mastering Emacs]] book.
3. [[https://www.youtube.com/watch?v=lkIicfzPBys][A guided tour of Emacs]] is a link to a video tour pointing how [[info:emacs#Buffers][buffers (info)]],
[[info:emacs#Dired][dired (info)]], [[info:emacs#Help][documentation (info)]], [[info:elisp#Top][elisp (info)]], [[info:elisp#Debugging][elisp debugging (info)]],
[[info:eshell#Top][eshell (info)]], and [[info:emacs#Keyboard Macros][keyboard macros (info)]] turn Emacs in an powerful coding
and editing environment.
4. [[https://www.youtube.com/watch?v=6ZWp05OW1c0][Emergency Emacs]] is a link to a video on fundamental editing features and
principles with focus on [[info:ediff#Top][ediff (info)]], [[info:emacs#Undo][undo (info)]], [[info:emacs#Moving Point][moving point (info)]],
[[info:emacs#Erasing][erasing (info)]], and [[info:emacs#Dynamic Abbrevs][dynamic abbreviations (info)]].
5. [[https://endlessparentheses.com/][Endless Parentheses]] is a blog with mindblowing code snippets.
6. [[https://www.murilopereira.com/how-to-open-a-file-in-emacs/][How to open a file in Emacs]] looks into [[info:elisp#Top][elisp (info)]] and
[[info:elisp#Debugging][elisp debugging (info)]] and then compares Emacs, [[https://www.vim.org/][Vim]],
[[https://neovim.io/][Neovim]], and [[https://code.visualstudio.com/][Visual Studio Code]] with respect to values and technology.
7. [[https://protesilaos.com/codelog/2022-01-31-learning-emacs/][Learning Emacs and Elisp]] is a link to a video tutorial with a transcript on
the best approach to learn Emacs and Elisp.
* [[info:emacs#Early Init File][Early Init File (info)]]
:PROPERTIES:
:CUSTOM_ID: sec:early-init-file
:END:
Try to load [[https://github.com/emacscollective/no-littering][no-littering]] as early as possible, since it helps to keep
=~/.emacs.d= clean.
#+caption[Tangle the early-init-file]:
#+caption: Tangle the early-init-file.
#+name: lst:tangle-early-init-file
#+begin_src emacs-lisp -n :results silent :tangle early-init.el
;;; early-init.el --- user early-init file -*- lexical-binding: t -*-
;;; Commentary:
;;; Code:
(setq load-prefer-newer t)
(require 'no-littering nil 'noerror)
(provide 'early-init)
;; Emacs looks for "Local variables:" after the last "?\n?\f".
;; Local Variables:
;; indent-tabs-mode: nil
;; End:
;;; early-init.el ends here
#+end_src
In order to get help in understanding the code block above in a buffer showing
the original [[info:org#Top][Org]] source file, type {{{kbd(C-c C-c)}}} after moving
point (or cursor) to one of the items of the list:
1. src_emacs-lisp[:results none]{(describe-variable #'load-prefer-newer t)}
2. src_emacs-lisp[:results none]{(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.*
* [[info:emacs#Init File][Init File (info)]] header
:PROPERTIES:
:CUSTOM_ID: sec:init-file-header
:END:
The =user-init-file= header requires =cl-lib= and sets Emacs options. It
consists of three parts in listing [[lst:1st-setopt-call]], [[lst:2nd-setopt-call]], and
[[lst:2nd-setopt-call]] in order to limit the length of the listings for exporting
to LaTeX.
The [[info:emacs#Init File][init file (info)]] does not load the ~custom-file~ in spite of the
recommendation of [[info:emacs#Saving Customizations][saving customizations (info)]].
#+caption[Set the first set of Emacs options]:
#+caption: Set the first set of Emacs options.
#+name: lst:1st-setopt-call
#+begin_src emacs-lisp -n :results silent
;;; init.el --- user init file -*- lexical-binding: t -*-
;;; Commentary:
;;; Code:
(require 'cl-lib)
(when (< emacs-major-version 30)
(error "This `init.el' requires at least Emacs-30"))
(setopt
after-save-hook #'executable-make-buffer-file-executable-if-script-p
column-number-mode t
cursor-type 'box
custom-file (locate-user-emacs-file "custom.el")
;; Known problem: EasyPG fails to save files encrypted with GnuPG v2.4.1.
;; Darwin: brew install gnupg@2.2
epg-pinentry-mode 'loopback
global-hl-line-mode t
global-hl-line-sticky-flag t
history-delete-duplicates t
history-length 500
indent-tabs-mode nil
inhibit-startup-buffer-menu t
inhibit-startup-screen t
initial-buffer-choice t
initial-scratch-message ""
isearch-lazy-count t
kill-ring-max 300
lazy-count-prefix-format nil
lazy-count-suffix-format " (%s/%s)"
lazy-highlight-initial-delay 1.0
mode-line-compact t
next-error-message-highlight t
recentf-max-saved-items 100
recentf-mode t
scroll-bar-mode nil
tab-always-indent 'complete
tab-width 8
tool-bar-mode nil
url-cookie-trusted-urls nil
url-cookie-untrusted-urls '(".*")
use-dialog-box nil
use-short-answers t
view-read-only t)
#+end_src
#+caption[Set the second set of Emacs options: pin packages to archives]:
#+caption: Set the second set of Emacs options: pin packages to archives.
#+name: lst:2nd-setopt-call
#+begin_src emacs-lisp -n :results silent
(setopt
;; See https://github.com/melpa/melpa#mirrors for official Melpa mirrors.
;; ("melpa" . "https://www.mirrorservice.org/sites/melpa.org/packages/")
package-archives '(("gnu" . "https://elpa.gnu.org/packages/")
("gnu-devel" . "https://elpa.gnu.org/devel/")
("melpa" . "https://melpa.org/packages/")
("melpa-stable" . "https://stable.melpa.org/packages/")
("nongnu" . "https://elpa.nongnu.org/nongnu/"))
package-install-upgrade-built-in t
;; Pin packages to GNU ELPA or to MELPA STABLE for info and/or stability.
package-pinned-packages '((auctex . "gnu")
(citar . "melpa-stable")
(citar-embark . "melpa-stable")
(compat . "gnu")
(consult . "gnu")
(dash . "melpa-stable")
(denote . "gnu-devel")
(eldoc . "nongnu")
(embark . "gnu")
(embark-consult . "gnu")
(emms . "gnu")
(engrave-faces . "gnu-devel")
(f . "melpa-stable")
(forge . "melpa-stable")
(git-commit . "nongnu")
(hyperbole . "gnu-devel")
(keycast . "nongnu")
(magit . "nongnu")
(magit-section . "nongnu")
(marginalia . "gnu")
(osm . "gnu")
(parsebib . "melpa-stable")
(queue . "gnu")
(rainbow-mode . "gnu")
(s . "melpa-stable")
(xr . "gnu")
(vertico . "gnu")
(with-editor . "nongnu")
(yasnippet . "gnu"))
package-selected-packages '(async debbugs no-littering))
#+end_src
#+caption[Set the third set of Emacs options: upgrade Org and transient]:
#+caption: Set the third set of Emacs options: upgrade Org and transient.
#+name: lst:3rd-setopt-call
#+begin_src emacs-lisp -n :results silent
;; To use different Org git repositories (org or gro):
(push (expand-file-name "~/VCS/org-mode/lisp") load-path)
;; Postpone (require 'org) after shadowing Org and sh-script faces below.
;; Enable `package-install-upgrade-built-in' to upgrade Org and transient.
;; Caveat: works with `list-packages' but not with `package-upgrade-all'.
;; Disable upgrading other packages by pinning them to "nongnu".
(setopt package-install-upgrade-built-in t
package-pinned-packages
(cl-union '((bind-key . "nongnu")
(eglot . "nongnu")
(erc . "nongnu")
(external-completion . "nongnu")
(faceup . "nongnu")
(flymake . "nongnu")
(jsonrpc . "nongnu")
(let-alist . "nongnu")
(map . "nongnu")
(ntlm . "nongnu")
(org . "nongnu") ; Use builtin or git!
(python . "nongnu")
(project . "nongnu")
(seq . "nongnu")
(soap-client . "nongnu")
(so-long . "nongnu")
(svg . "nongnu")
(transient . "nongnu")
(use-package . "nongnu")
(use-package-ensure-system-package . "nongnu")
(verilog-mode . "nongnu")
(xref . "nongnu"))
package-pinned-packages :key #'car))
(when (eq system-type 'darwin)
(setopt ns-alternate-modifier nil
ns-command-modifier 'meta
ns-right-command-modifier 'super))
(when (eq window-system 'ns)
(add-to-list 'initial-frame-alist '(height . 51))
(add-to-list 'initial-frame-alist '(width . 180)))
#+end_src
#+caption[Handle ~user-emacs-directory~ site-lisp directories as packages]:
#+caption: Handle ~user-emacs-directory~ site-lisp directories as packages.
#+name: lst:site-lisp-packages
#+begin_src emacs-lisp -n :results silent
(with-eval-after-load 'emacs
;; https://www.emacswiki.org/emacs/LoadPath
;; Make user-emacs-directory site-lisp directories look like packages.
(mapc (apply-partially 'add-to-list 'load-path)
(cl-remove-duplicates
(mapcar (lambda (file)
(directory-file-name (file-name-directory file)))
(directory-files-recursively
(expand-file-name "site-lisp" user-emacs-directory) "\\.el$"))
:test #'string=))
;; See package.el for how to use `loaddefs-generate-dir'.
(defun make-loaddefs-in-buffer-directory ()
"Make or update the autoloads in the directory of the visited file."
(interactive)
(let ((dir (directory-file-name (file-name-directory (buffer-file-name))))
(name (file-name-base (directory-file-name
(file-name-directory (buffer-file-name))))))
(loaddefs-generate dir
(expand-file-name (format "%s-autoloads.el" name) dir)
nil nil nil 'generate-full))))
#+end_src
* [[info:emacs#Package Installation][Install the selected packages (info)]]
:PROPERTIES:
:CUSTOM_ID: sec:install-selected-packages
:END:
[[info:emacs#Package Installation][Emacs installs packages]] from archives on the internet. This setup uses five
archives:
1. The [[https://elpa.gnu.org/][GNU Emacs Lisp Package Archive]].
2. The [[https://elpa.gnu.org/devel/index.html][Development Emacs Lisp Package Archive]].
3. The [[https://elpa.nongnu.org/][NonGNU Emacs Lisp Package Archive]].
4. The [[https://melpa.org/#/][Milkypostman's Emacs Lisp Package Archive (MELPA)]] with its official
[[https://www.mirrorservice.org/sites/melpa.org/packages/][mirror]] in case [[https://downforeveryoneorjustme.com/melpa.org][is Melpa.org down?]] tells [[https://melpa.org/#/][MELPA]] is down for everyone.
5. The [[https://stable.melpa.org/#/getting-started][Stable Milkypostman's Emacs Lisp Package Archive]].
The code in listing [[lst:install-selected-packages]] assumes that the package
system is in a *virgin* state if the package [[https://github.com/emacscollective/no-littering][no-littering]] is not present:
1. It installs and loads [[https://github.com/emacscollective/no-littering][no-littering]] after ensuring refreshing of the
contents of available packages.
2. It upgrades the build in Org package.
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.
#+caption[Install the selected packages]:
#+caption: Install the selected packages.
#+name: lst:install-selected-packages
#+begin_src emacs-lisp -n :results silent
(unless noninteractive
(unless (require 'no-littering nil 'noerror)
(unless (bound-and-true-p package-archive-contents)
(package-refresh-contents))
;; Install and require `no-littering'.
(package-install 'no-littering)
(require 'no-littering)
(package-upgrade 'org)
;; Install the selected packages.
(package-install-selected-packages)))
(defun ensure-package-selection (package)
"Ensure selection of PACKAGE."
(when (and (package-installed-p package)
(bound-and-true-p package-selected-packages))
(cl-pushnew package package-selected-packages)))
(defun ensure-package-installation (&rest packages)
"Ensure installation of all packages in PACKAGES."
(let ((ok t))
(dolist (package packages)
(unless (package-installed-p package)
(package-install package))
(unless (ensure-package-selection package)
(setq ok nil)))
ok))
#+end_src
* [[https://git.savannah.gnu.org/cgit/emacs.git/tree/admin/notes/tree-sitter/starter-guide?h=feature/tree-sitter][Emacs Tree-sitter]]
:PROPERTIES:
:CUSTOM_ID: sec:emacs-tree-sitter
:END:
Mickey Peterson's post [[https://www.masteringemacs.org/article/how-to-get-started-tree-sitter?utm_source=newsletter&utm_medium=rss][How To Get Started with Tree-Sitter]] explains how to use
=tree-sitter= in Emacs-29.1. Listing [[lst:setup-treesit-sources][Setup =treesit= grammar sources]] configures
where to find the different =tree-sitter= grammar sources for different
languages. Listing [[lst:setup-go][Setup Go programming]] shows how to configure ~tab-width~ for
~go-ts-mod~ and ~go-mode-ts-mode~.
#+caption[Setup =treesit= grammar sources]:
#+caption: Setup =treesit= grammar sources.
#+name: lst:setup-treesit-sources
#+begin_src emacs-lisp -n :results silent
(when (require 'treesit nil 'noerror)
(setopt
treesit-language-source-alist
'((bash "https://github.com/tree-sitter/tree-sitter-bash" "master" "src")
(c "https://github.com/tree-sitter/tree-sitter-c" "master" "src")
(cmake "https://github.com/uyha/tree-sitter-cmake" "master" "src")
(cpp "https://github.com/tree-sitter/tree-sitter-cpp" "master" "src")
(css "https://github.com/tree-sitter/tree-sitter-css" "master" "src")
(glsl "https://github.com/theHamsta/tree-sitter-glsl" "master" "src")
;; go-ts-mode requires libtree-sitter-go and libtree-sitter-gomode.
(go "https://github.com/tree-sitter/tree-sitter-go" "master" "src")
(gomod "https://github.com/camdencheek/tree-sitter-go-mod" "main" "src")
(html "https://github.com/tree-sitter/tree-sitter-html" "master" "src")
(java "https://github.com/tree-sitter/tree-sitter-java" "master" "src")
(javascript "https://github.com/tree-sitter/tree-sitter-javascript"
"master" "src")
(json "https://github.com/tree-sitter/tree-sitter-json" "master" "src")
(julia "https://github.com/tree-sitter/tree-sitter-julia" "master" "src")
(lua "https://github.com/MunifTanjim/tree-sitter-lua" "main" "src")
(make "https://github.com/alemuller/tree-sitter-make" "main" "src")
(perl "https://github.com/ganezdragon/tree-sitter-perl" "master" "src")
(python "https://github.com/tree-sitter/tree-sitter-python" "master" "src")
(ruby "https://github.com/tree-sitter/tree-sitter-ruby" "master" "src")
(rust "https://github.com/tree-sitter/tree-sitter-rust" "master" "src")
(scala "https://github.com/tree-sitter/tree-sitter-scala" "master" "src")
(sql "https://github.com/m-novikov/tree-sitter-sql" "main" "src")
(swift "https://github.com/tree-sitter/tree-sitter-swift" "master" "src")
(toml "https://github.com/tree-sitter/tree-sitter-toml" "master" "src")
(tsx "https://github.com/tree-sitter/tree-sitter-typescript"
"master" "tsx/src")
(typescript "https://github.com/tree-sitter/tree-sitter-typescript"
"master" "typescript/src")
(yaml "https://github.com/ikatyang/tree-sitter-yaml" "master" "src"))))
#+end_src
* [[info:emacs#Faces][Text faces or styles (info)]]
:PROPERTIES:
:CUSTOM_ID: sec:text-faces-or-styles
:END:
This setup does not use [[info:emacs#Custom Themes][custom themes (info)]] to avoid endless tweaking, but it
does a minimal setup of fonts and faces. See the [[https://protesilaos.com/codelog/2020-09-05-emacs-note-mixed-font-heights/][note on mixed font heights in
Emacs]] for how to setup fonts properly. It boils down to two rules:
1. The height of the default face must be an integer number to make the height a
physical quantity.
2. The heights of all other faces must be real numbers to scale those heights
with respect to the height of the face (those heights default to 1.0 for no
scaling).
The code in listing [[lst:configure-face-attributes]] implements those rules.
Listing [[lst:set-default-face-height]] shows that font scaling is easy in case of
proper initialization of all face heights. To adjust the font size in the
current or future frames, invoke src_emacs-lisp{(set-default-face-height)}. To
adjust the font size in the current buffer, type:
1. {{{kbd(s-=)}}} to invoke src_emacs-lisp[:results silent]{(text-scale-adjust
+1)}.
2. {{{kbd(s--)}}} to invoke src_emacs-lisp[:results silent]{(text-scale-adjust
-1)}.
3. {{{kbd(s-0)}}} to invoke src_emacs-lisp[:results silent]{(text-scale-adjust
0)}.
Listing [[lst:fix-gtk-color-for-invert-default-face]],
[[lst:shadow-org-font-lock-faces]], and [[lst:shadow-emacs-font-lock-faces]] show a
tweaks to improve visibility without theming:
1. Fixing the =gtk= background color of the already loaded =region= face.
2. Toggling between a dark and light background by means of
src_emacs-lisp{(invert-default-face)}.
3. Shadowing the definition of faces before loading. The last item in the page
on [[https://orgmode.org/worg/org-contrib/babel/examples/fontify-src-code-blocks.html#org5c4406f][fontifying Org mode source code blocks]] describes this method.
Listing [[lst:use-buffer-face-mode-for-fixed-or-variable-pitch-face]] contains
functions to set a fixed or variable pitch face in the current buffer and
selects a fixed pitch face for =magit-mode= and =progmode= buffers.
#+caption[Configure =face-attributes=]:
#+caption: Configure =face-attributes=.
#+name: lst:configure-face-attributes
#+begin_src emacs-lisp -n :results silent
(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"))))
#+end_src
#+caption[Implement =set-default-face-height=]:
#+caption: Implement =set-default-face-height=.
#+name: lst:set-default-face-height
#+begin_src emacs-lisp -n :results silent
(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 height (%s): "
(face-attribute 'default :height)))
(choices (mapcar #'number-to-string
(number-sequence 50 200 10)))
(height (string-to-number
(completing-read prompt choices nil 'require-match))))
(message "Setting the height of the default face to %s" height)
(set-face-attribute 'default nil :height height))))
#+end_src
#+caption[Fix a `gtk' color and implement =invert-default-face=]:
#+caption: Fix a `gtk' color and implement =invert-default-face=.
#+name: lst:fix-gtk-color-for-invert-default-face
#+begin_src emacs-lisp -n :results silent
(with-eval-after-load 'emacs
(defun fix-gtk-region-face-background-color ()
(when (featurep 'gtk)
(set-face-attribute
'region nil
:background (cdr (assoc (face-attribute 'default :background)
'(("white" . "LightGoldenrod2")
("black" . "blue3")))))))
(defun invert-default-face ()
"Invert the default face."
(interactive)
(invert-face 'default)
(fix-gtk-region-face-background-color))
(fix-gtk-region-face-background-color))
#+end_src
#+caption[Shadow Org font-lock faces to improve the readability]:
#+caption: Shadow Org font-lock faces to improve the readability.
#+name: lst:shadow-org-font-lock-faces
#+begin_src emacs-lisp -n
(with-eval-after-load 'emacs
;; Shadow two definitions in org-faces.el:
(defface org-block
;; https://emacs.stackexchange.com/a/9604 answers:
;; How to override a defined face for light and dark backgrounds?
`((((background dark))
:inherit (fixed-pitch)
,@(and (>= emacs-major-version 27) '(:extend t))
:background "#444444")
(t
:inherit (fixed-pitch)
,@(and (>= emacs-major-version 27) '(:extend t))
:background "#FFFFD0"))
"My face used for text inside blocks.
It is always used for source blocks. You can refine what face
should be used depending on the source block language by setting,
`org-src-block-faces', which takes precedence.
When `org-fontify-quote-and-verse-blocks' is not nil, text inside
verse and quote blocks are fontified using the `org-verse' and
`org-quote' faces, which inherit from `org-block'."
:group 'org-faces)
(defface org-table
;; https://emacs.stackexchange.com/a/9604 answers:
;; How to override a defined face for light and dark backgrounds?
`((((background dark))
:inherit (fixed-pitch)
,@(and (>= emacs-major-version 27) '(:extend t))
:background "#444444")
(t
:inherit (fixed-pitch)
,@(and (>= emacs-major-version 27) '(:extend t))
:background "#FFFFD0"))
"My face for tables."
:group 'org-faces))
#+end_src
#+caption[Shadow Emacs font-lock faces to improve the readability]:
#+caption: Shadow Emacs font-lock faces to improve the readability.
#+name: lst:shadow-emacs-font-lock-faces
#+begin_src emacs-lisp -n
(with-eval-after-load 'emacs
;; Shadow one definition in sh-script.el:
(defface sh-heredoc
'((((class color) (background dark))
(:foreground "yellow"))
(((class color) (background light))
(:foreground "magenta"))
(t
(:weight bold)))
"My face to show a here-document."
:group 'sh-indentation))
(require 'org)
#+end_src
#+caption[Use =buffer-face-mode= to set fixed or variable pitch face]:
#+caption: Use =buffer-face-mode= to set fixed or variable pitch face.
#+name: lst:use-buffer-face-mode-for-fixed-or-variable-pitch-face
#+begin_src emacs-lisp -n
;; Use proportional font faces in current buffer
(defun set-buffer-variable-pitch-face ()
"Set a variable width (proportional) font in current buffer."
(interactive)
(setq buffer-face-mode-face 'variable-pitch)
(buffer-face-mode))
;; Use monospaced font faces in current buffer
(defun set-buffer-fixed-pitch-face ()
"Set a fixed width (monospace) font in current buffer."
(interactive)
(setq buffer-face-mode-face 'fixed-pitch)
(buffer-face-mode))
(add-hook 'magit-mode-hook #'set-buffer-fixed-pitch-face)
(add-hook 'prog-mode-hook #'set-buffer-fixed-pitch-face)
#+end_src
#+caption[Setup ~visual-line-mode~]:
#+caption: Setup ~visual-line-mode~.
#+name: lst:setup-visual-line-mode
#+begin_src emacs-lisp -n :results silent
(when (fboundp 'visual-wrap-prefix-mode)
(add-hook 'visual-line-mode-hook 'visual-wrap-prefix-mode))
#+end_src
* [[info:emacs#Windows][Window management (info)]]
:PROPERTIES:
:CUSTOM_ID: sec:window-management
:END:
Mickey Peterson's post [[https://www.masteringemacs.org/article/demystifying-emacs-window-manager][Demystifying Emacs's Window Manager]] invites to improve
window placement. Listing [[lst:1st-window-management]], [[lst:2nd-window-management]]
and [[lst:3rd-window-management]] implement a selection of his recommendations.
#+caption[Window management functions and key bindings]:
#+caption: Window management functions and key bindings.
#+name: lst:1st-window-management
#+begin_src emacs-lisp -n
(with-eval-after-load 'emacs
;; https://www.masteringemacs.org/article/demystifying-emacs-window-manager
(defun split-root-below (arg)
"Split window below from the root or from the parent with ARG."
(interactive "P")
(split-window (if arg
(window-parent (selected-window))
(frame-root-window))
nil 'below nil))
(defun split-root-right (arg)
"Split window right from the root or from the parent with ARG."
(interactive "P")
(split-window (if arg
(window-parent (selected-window))
(frame-root-window))
nil 'right nil))
(defun toggle-window-dedication ()
"Toggles window dedication in the selected window."
(interactive)
(set-window-dedicated-p
(selected-window) (not (window-dedicated-p (selected-window)))))
(defun make-display-buffer-matcher-function (major-modes)
"Return a lambda function to match a list of MAJOR-MODES."
(lambda (buffer-name action)
(with-current-buffer buffer-name (apply #'derived-mode-p major-modes))))
(keymap-global-set "M-o" #'other-window))
#+end_src
#+caption[Window management modes]:
#+caption: Window management modes.
#+name: lst:2nd-window-management
#+begin_src emacs-lisp -n
(with-eval-after-load 'emacs
(winner-mode +1))
#+end_src
#+caption[Window management variables]:
#+caption: Window management variables.
#+name: lst:3rd-window-management
#+begin_src emacs-lisp -n
(with-eval-after-load 'emacs
;; https://www.masteringemacs.org/article/demystifying-emacs-window-manager
(add-to-list 'display-buffer-alist
`(,(rx (or "*Apropos*"
"*Help*"
"*info"))
(display-buffer-reuse-window display-buffer-pop-up-window)
(inhibit-same-window . nil)))
(add-to-list 'display-buffer-alist
`(,(rx (or "*Occur*"
"*grep*"
"*xref*"))
display-buffer-reuse-window
(inhibit-same-window . nil)))
(add-to-list 'display-buffer-alist
'("\\*compilation\\*"
display-buffer-no-window
(allow-no-window . t)))
(add-to-list 'display-buffer-alist
'("^\\*Dictionary\\*" display-buffer-in-side-window
(side . left)
(window-width . 70))))
#+end_src
* [[info:elisp#Advising Functions][Advising functions (info)]]
:PROPERTIES:
:CUSTOM_ID: sec:advising-function
:END:
#+caption[Toggle any advice]:
#+caption: Toggle any advice.
#+name: lst:toggle-any-advice
#+begin_src emacs-lisp -n
(with-eval-after-load 'emacs
(defun advice-toggle (symbol where function &optional props)
"Toggle between states after `advice-remove' and `advice-add'."
(let ((how "%s `%s' advice `%s' %s `%s'"))
(if (advice-member-p function symbol)
(progn
(message how "Removal of" where function "from" symbol)
(advice-remove symbol function))
(message how "Addition of" where function "to" symbol)
(advice-add symbol where function props)))))
#+end_src
#+caption[Toggle specific advice]:
#+caption: Toggle specific advice.
#+name: lst:toggle-specific-advice
#+begin_src emacs-lisp -n
(with-eval-after-load 'emacs
(defun toggle-eww-display-pdf-around ()
"Toggle `eww-display-advice' advice."
(interactive)
(advice-toggle 'eww-display-pdf :around #'eww-display-pdf-as-binary))
(defun toggle-ilog-timer-function-after ()
"Toggle `ilog-timer-function' advice."
(interactive)
(advice-toggle 'ilog-timer-function :after #'ilog-ensure-ilog-buffer-window))
(defun toggle-keycast-log-update-buffer-override ()
"Toggle `keycast-log-update-buffer' advice."
(interactive)
(advice-toggle 'keycast-log-update-buffer
:override #'keycast-log-update-buffer-plain))
(defun toggle-org-babel-python-format-session-value-override ()
"Toggle `org-babel-python-format-session-value' advice."
(interactive)
(advice-toggle 'org-babel-python-format-session-value
:override #'org-babel-python-format-session-value-override)))
#+end_src
* [[info:emacs#Bookmarks][Bookmarks (info)]]
:PROPERTIES:
:CUSTOM_ID: sec:bookmarks
:END:
Listing [[lst:setup-bookmark][bookmark setup]] preserves bookmark history in [[./var/bookmark-default.el][bookmark-default.el]]. Table
[[tab:bookmark-commands-and-bindings]] lists all =bookmark= commands with working
key bindings. Not all key bindings in src_emacs-lisp[:results
silent]{(describe-variable 'bookmark-map)} are operational for different
reasons.
BUG: The virtual buffers of the ~consult-buffer~ command do not seem to work.
Therefore, ~consult-buffer~ is no means to access the bookmark history (contrary
to claims in [[info:consult#Top][consult (info)]]).
#+caption[Let Emacs save bookmarks when killed]:
#+caption: Let Emacs save bookmarks when killed.
#+name: lst:setup-bookmark
#+begin_src emacs-lisp -n :results silent
(with-eval-after-load 'bookmark
(setopt bookmark-save-flag 1)
(setf bookmark-exit-hook 'bookmark-unload-function))
#+end_src
#+attr_latex: :booktabs yes :float table
#+caption[Bookmark commands with key bindings]:
#+caption: Bookmark commands with key bindings.
#+name: tab:bookmark-commands-and-bindings
|------------------------------+--------------------|
| command | key binding |
|------------------------------+--------------------|
| *bookmark-delete* | |
| *bookmark-delete-all* | |
| *bookmark-insert* | {{{kbd(C-x r i)}}} |
| *bookmark-insert-location* | |
| *bookmark-jump* | {{{kbd(C-x r j)}}} |
| *bookmark-jump-other-frame* | |
| *bookmark-jump-other-window* | |
| *bookmark-load* | |
| *bookmark-rename* | |
| *bookmark-save* | |
| *bookmark-set* | {{{kbd(C-x r m)}}} |
| *bookmark-set-no-overwrite* | {{{kbd(C-x r M)}}} |
| *bookmark-write* | |
| *consult-bookmark* | {{{kbd(C-x r b)}}} |
| *list-bookmarks* | |
|------------------------------+--------------------|
* [[info:emacs#Registers][Registers (info)]]
:PROPERTIES:
:CUSTOM_ID: sec:registers
:END:
Listing [[lst:register-separator-usage-example][register separator usage example]] shows how to use a register
{{{kbd(+)}}} as register separator when appending regions to text registers.
Listing [[lst:setup-register][setup register usage]] prepares my preferred register usage. Table
[[tab:register-commands-and-bindings]] lists all =register= commands with working
key bindings. The =desktop= library allows to preserve the =register= history
between Emacs sessions.
#+caption[Register separator usage example]:
#+caption: Register separator usage example.
#+name: lst:register-separator-usage-example
#+begin_src text -n :exports code
When I set register ?+ to
(set-register ?+ "\n*REGISTER SEPARATOR*\n")
to test append-to-register on two regions like
Mark this line without new-line as region 1
Mark this line without new-line as region 2
The correct result of
"C-x r s T" with region 1 marked
"C-x r + T" with region 2 marked
"C-x r i T"
is:
Mark this line without new-line as region 1
*REGISTER SEPARATOR*
Mark this line without new-line as region 2
#+end_src
#+caption[Setup register usage]:
#+caption: Setup register usage.
#+name: lst:setup-register
#+begin_src emacs-lisp -n :results silent
(with-eval-after-load 'register
;; Register `?+' serves as register separator when using `append-to-register':
(set-register ?+ "\n")
;; https://emacs.stackexchange.com/a/52290
(defun delete-all-registers ()
"Delete all registers."
(interactive)
(setq register-alist nil)))
#+end_src
#+attr_latex: :booktabs yes :float table
#+caption[Register commands with key bindings]:
#+caption: Register commands with key bindings.
#+name: tab:register-commands-and-bindings
|------------------------------------+------------------------|
| command | key binding |
|------------------------------------+------------------------|
| *append-to-register* | |
| *copy-rectangle-to-register* | {{{kbd(C-x r r)}}} |
| *copy-to-register* | {{{kbd(C-x r s)}}} |
| *frameset-to-register* | {{{kbd(C-x r f)}}} |
| *increment-register* | {{{kbd(C-x r +)}}} |
| *insert-register* | {{{kbd(C-x r i)}}} |
| *jump-to-register* | {{{kbd(C-x r j)}}} |
| *kmacro-to-register* | {{{kbd(C-x C-k x)}}} |
| *number-to-register* | {{{kbd(C-x r n)}}} |
| *point-to-register* | {{{kbd(C-x r <SPC>)}}} |
| *prepend-to-register* | |
| *set-register* | |
| *window-configuration-to-register* | {{{kbd(C-x r w)}}} |
|------------------------------------+------------------------|
* [[info:emacs#Dired][Dired: directory editor as file manager (info)]]
:PROPERTIES:
:CUSTOM_ID: sec:file-manager
:END:
[[info:emacs#Dired][Dired (info)]] and the [[https://github.com/ranger/ranger#readme][ranger]] file manager offer similar capabilities for copying,
deleting, opening, renaming, and viewing files and directories, but the
integration of [[info:emacs#Dired][dired (info)]] in Emacs is obviously much better than the
integration of [[https://github.com/ranger/ranger#readme][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 [[info:emacs#Dired][dired (info)]] as follows:
1. Type {{{kbd(C-x d)}}} 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 {{{kbd(v)}}} 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:
- {{{kbd(n)}}} to view the next image in the =dired-mode= buffer,
- {{{kbd(p)}}} to view the previous image in the =dired-mode= buffer, and
- {{{kbd(q)}}} to quit the =image-mode= buffer and to go back to the =dired-mode= buffer.
5. After finding the specific image, use {{{kbd(C-c l)}}} in the =image-mode=
buffer or the =dired-mode= buffer to store the =org-link=.
6. Switch to the =org-mode= buffer and use {{{kbd(C-c C-l)}}} to insert the =org-link= into the buffer.
Listing [[lst:set-dired-options]] makes the directory editor sort entries
alphabetically after grouping the directories before grouping the files by
extension.
#+caption[Set =dired= options]:
#+caption: Set =dired= options.
#+name: lst:set-dired-options
#+begin_src emacs-lisp -n :results silent
(with-eval-after-load 'dired
(setopt dired-dwim-target t
;; | switch | action |
;; |--------+----------------------------------------|
;; | -a | also list hidden entries |
;; | -l | use a long listing format |
;; | -X | sort alphabetically by entry extension |
;; | -G | skip long listing format group names |
;; | -1 | list one entry per line |
dired-listing-switches "-alGX1 --group-directories-first"
dired-recursive-copies 'always
dired-recursive-deletes 'always))
(with-eval-after-load 'wdired
(setopt wdired-allow-to-change-permissions t))
#+end_src
Listing [[lst:extra-dired-key-bindings]] adds a new key binding to =dired-mode-map=
to open files with the [[https://en.wikipedia.org/wiki/Eww_(web_browser)][Emacs Web Wowser]] inside Emacs and implements asynchronous
file copying using the src_emacs-lisp{(find-library "dired-async")} library
under the hood. *NOTE:* [[https://github.com/stsquad/dired-rsync#dired-rsync--asynchronous-rsync-from-dired][asynchronous rsync from dired]] may be a better
alternative.
#+caption[Extra =dired= key bindings]:
#+caption: Extra =dired= key bindings.
#+name: lst:extra-dired-key-bindings
#+begin_src emacs-lisp -n :results silent
(with-eval-after-load 'dired
(defun dired-eww-open-file ()
"In Dired, open the regular file named on this line with eww"
(interactive)
(let ((file (dired-get-file-for-visit)))
(if (file-regular-p file)
(eww-open-file file)
(error "Eww rejects `%s', since it is not a regular file" file))))
(keymap-set dired-mode-map "E" #'dired-eww-open-file))
(when (require 'dired-async nil 'noerror)
;; Does this fail? Or is it too fast?
;; Fix this later, but rsync works from zsh.
;; https://www.emacs.dyerdwelling.family/emacs/
;; https://www.reddit.com/r/emacs/comments/g0jkkj/using_dired_asynchronously/
(setopt dired-async-small-file-max 5000000))
#+end_src
* [[info:emacs#Completion Styles][Minibuffer completion styles (info)]]
:PROPERTIES:
:CUSTOM_ID: sec:minibuffer-completion-styles
:END:
Listing [[lst:set-minibuffer-options][set minibuffer options]] implements ideas of the post [[https://www.masteringemacs.org/article/understanding-minibuffer-completion][Understanding
minibuffer completion]].
#+caption[Set =minibuffer= options]:
#+caption: Set =minibuffer= options.
#+name: lst:set-minibuffer-options
#+begin_src emacs-lisp -n :results silent
(when (require 'minibuffer nil 'noerror)
;; https://www.masteringemacs.org/article/understanding-minibuffer-completion
(setopt
completion-category-overrides '((file (styles basic substring)))
completion-ignore-case nil
completion-styles '(basic flex partial-completion substring)))
#+end_src
* [[info:elisp#Processes][Processes (info)]]
:PROPERTIES:
:CUSTOM_ID: sec:processes
:END:
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=.
#+caption[Process utilities]:
#+caption: Process utilities.
#+name: lst:process-utilities
#+begin_src emacs-lisp -n :results silent
;; https://gitlab.com/howardabrams/spacemacs.d/blob/master/layers/ha-org/funcs.el#L418
(defun shell-command-with-exit-code (program &rest args)
"Run PROGRAM with ARGS and return exit-code and output in a list."
(with-temp-buffer
(list (apply 'call-process program nil (current-buffer) nil args)
(buffer-substring-no-properties (point-min) (point-max)))))
#+end_src
* [[info:emacs#Help][Help (info)]]
:PROPERTIES:
:CUSTOM_ID: sec:help
:END:
Table [[tab:help-key-bindings]] lists a number of key bindings to start playing with
the help facilities of Emacs. Try {{{kbd(C-h o)}}} instead of {{{kbd(C-h f)}}}
or {{{kbd(C-h v)}}}.
#+attr_latex: :booktabs yes :float table
#+caption[Help key bindings]:
#+caption: Help key bindings.
#+name: tab:help-key-bindings
|------------------------------+----------+------------------|
| command | key map | keys |
|------------------------------+----------+------------------|
| Info-goto-emacs-command-node | help-map | {{{kbd(C-h F)}}} |
| describe-function | help-map | {{{kbd(C-h f)}}} |
| describe-key | help-map | {{{kbd(C-h k)}}} |
| describe-symbol | help-map | {{{kbd(C-h o)}}} |
| describe-variable | help-map | {{{kbd(C-h v)}}} |
| info | help-map | {{{kbd(C-h i)}}} |
|------------------------------+----------+------------------|
#+begin_src emacs-lisp -n :exports none :results silent :tangle no
(defun org-key-binding-macro (cmd)
(let* ((sym (intern-soft cmd))
(keys (and (commandp sym) (where-is-internal sym nil 'first-only)))
(prefix (seq-take keys (1- (length keys))))
(keymap (key-binding prefix 'accept-default)))
(format "%s %s %s {{{kbd(%s)}}}"
cmd keymap (help--binding-locus keys nil) (key-description keys))))
(org-key-binding-macro 'consult-buffer)
#+end_src
** [[info:emacs#Name Help][Shortdoc-display-group (info)]]
:PROPERTIES:
:CUSTOM_ID: sec:shortdoc-display-group
:END:
Listing [[lst:configure-shortdoc]] binds {{{kbd(C-h y)}}} to
=short-doc-display-group= and defines a short documentation group for functions
defined in this Org file.
#+caption[Configure =shortdoc=]:
#+caption: Configure =shortdoc=.
#+name: lst:configure-shortdoc
#+begin_src emacs-lisp -n :results silent
(when (fboundp 'shortdoc-display-group)
(keymap-set help-map "y" #'shortdoc-display-group)
(with-eval-after-load 'shortdoc
;; Ensure defining the functions before documenting them.
(define-short-documentation-group init
"Advice"
(advice-toggle :no-manual t)
(toggle-eww-display-pdf-around :no-manual t)
(toggle-ilog-timer-function-after :no-manual t)
(toggle-keycast-log-update-buffer-override :no-manual t)
(toggle-org-babel-python-format-session-value-override :no-manual t)
"Face"
(invert-default-face :no-manual t)
(set-default-face-height :no-manual t)
"Interaction"
(enable-this-command :no-manual t)
(narrow-or-widen-dwim :no-manual t)
(org-narrow-to-table :no-manual t)
"LaTeX"
(biber-delete-cache :no-manual t)
(update-lualatex-opentype-font-name-database :no-manual t)
"Org"
(by-backend :no-manual t)
(by-backend-kbd-org-macro :no-manual t)
(org-active-current-time-stamp :nomanual t)
(org-babel-execute:latex-extra-header :no-manual t)
(org-babel-execute:latex-header :no-manual t)
(org-electric-dollar :no-manual t)
(org-eval-emacs-lisp-setup-blocks :no-manual t)
(org-eval-python-setup-blocks :no-manual t)
(org-eval-infixed-blocks :no-manual t)
(org-latex-engraved-source-block-filter :no-manual t)
(org-inactive-current-time-stamp :nomanual t)
(org-syntax-convert-keyword-case-to-lower :no-manual t)
(smart-latex-engrave-org-source-blocks :no-manual t)
(zero-all-org-src-blocks-indentation :no-manual t))))
#+end_src
** [[info:info#Top][Info (info)]]
:PROPERTIES:
:CUSTOM_ID: sec:info
:END:
Listing [[lst:configure-info]] adds the required paths to the places where =info=
looks for files.
#+caption[Configure =info=]:
#+caption: Configure =info=.
#+name: lst:configure-info
#+begin_src emacs-lisp -n :results silent
(with-eval-after-load 'info
;; Make Emacs find ALL "*.info" files in `package-user-dir' on Gentoo Linux.
(when (eq system-type 'gnu/linux)
(dolist (path
(nreverse
(mapcar
(lambda (name)
(expand-file-name (file-name-directory name)))
(directory-files-recursively package-user-dir "\\.info\\'"))))
(add-to-list 'Info-directory-list path nil #'file-equal-p)))
;; Make Emacs find the git Org info files.
(add-to-list 'Info-directory-list
(expand-file-name "~/VCS/org-mode/doc")))
#+end_src
* [[info:emacs#Key Bindings][Key bindings (info)]]
:PROPERTIES:
:CUSTOM_ID: sec:key-bindings
:END:
#+attr_latex: :booktabs yes :float table
#+caption[Basic key bindings]:
#+caption: Basic key bindings.
#+name: tab:basic-key-bindings
|--------------------+------------+------------------------|
| command | key map | keys |
|--------------------+------------+------------------------|
| undo | global-map | {{{kbd(s-z)}}} |
| backward-kill-word | global-map | {{{kbd(C-backspace)}}} |
| backward-char | global-map | {{{kbd(C-b)}}} |
| forward-char | global-map | {{{kbd(C-f)}}} |
| backward-word | global-map | {{{kbd(M-b)}}} |
| forward-word | global-map | {{{kbd(M-f)}}} |
| backward-sentence | global-map | {{{kbd(M-a)}}} |
| forward-sentence | global-map | {{{kbd(M-e)}}} |
|--------------------+------------+------------------------|
** [[info:emacs#Disabling][Disabling Commands (info)]]
:PROPERTIES:
:CUSTOM_ID: sec:enable-disabled-commands
:END:
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 [[https://www.emacswiki.org/emacs/DisabledCommands][disabled commands on the fly]].
#+caption[Configure the =disabled-command-function=]:
#+caption: Configure the =disabled-command-function=.
#+name: lst:configure-disabled-command-function
#+begin_src emacs-lisp -n :results silent
(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))))
#+end_src
** [[https://github.com/michael-heerdegen/interaction-log.el#readme][Interaction-log]]
:PROPERTIES:
:CUSTOM_ID: sec:interaction-log
:END:
#+caption[Configure =interaction-log=]:
#+caption: Configure =interaction-log=.
#+name: lst:configure-interaction-log
#+begin_src emacs-lisp -n :results silent
(when (and (ensure-package-installation 'interaction-log)
(require 'interaction-log nil 'noerror))
;; https://www.skybert.net/emacs/show-shortcuts-when-giving-presentations/
(defun ilog-ensure-ilog-buffer-window ()
"Ensure the visibility of the `ilog' buffer, when it exists."
(when (and (get-buffer ilog-buffer-name)
(not (get-buffer-window ilog-buffer-name)))
(display-buffer (get-buffer ilog-buffer-name))))
(keymap-set help-map "C-l" #'interaction-log-mode)
(advice-add 'ilog-timer-function :after #'ilog-ensure-ilog-buffer-window))
#+end_src
** [[https://github.com/tarsius/keycast#readme][Keycast]]
:PROPERTIES:
:CUSTOM_ID: sec:keycast
:END:
Listing [[lst:configure-keycast]] configures =keycast=.
#+caption[Configure =keycast=]:
#+caption: Configure =keycast=.
#+name: lst:configure-keycast
#+begin_src emacs-lisp -n :results silent
;; Make `keycast-log-update-buffer' use a buffer similar to the
;; control buffer `ediff-setup-windows-plain' returns.
(when (ensure-package-installation 'keycast)
(setopt keycast-log-newest-first t
keycast-mode-line-window-predicate #'keycast-bottom-right-window-p)
(defun keycast-log-update-buffer-plain ()
(let ((buffer (get-buffer keycast-log-buffer-name)))
(unless (buffer-live-p buffer)
(setq buffer (get-buffer-create keycast-log-buffer-name))
(with-current-buffer buffer
(setq buffer-read-only t)))
(unless (get-buffer-window buffer)
(display-buffer buffer '(display-buffer-at-bottom
(dedicated . t)
(window-height . 10))))
(when-let ((output (keycast--format keycast-log-format)))
(with-current-buffer buffer
(goto-char (if keycast-log-newest-first (point-min) (point-max)))
(let ((inhibit-read-only t))
(when (and (> keycast--command-repetitions 0)
(string-match-p "%[rR]" keycast-log-format))
(unless keycast-log-newest-first
(backward-char))
(ignore-errors
(delete-region (line-beginning-position)
(1+ (line-end-position)))))
(insert output))
(goto-char (if keycast-log-newest-first (point-min) (point-max)))))))
(advice-add 'keycast-log-update-buffer
:override #'keycast-log-update-buffer-plain))
#+end_src
* [[info:emacs#Emacs Server][Using Emacs as a server (info)]]
:PROPERTIES:
:CUSTOM_ID: sec:using-emacs-server
:END:
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 [[https://qutebrowser.org][qutebrowser]] send html links with document titles to Emacs.
The code in listing [[lst:start-emacs-server]] starts the Emacs server.
#+caption[Start the Emacs server]:
#+caption: Start the Emacs server.
#+name: lst:start-emacs-server
#+begin_src emacs-lisp -n :results silent
(when window-system
(unless (or noninteractive (daemonp))
(add-hook 'after-init-hook #'server-start)))
#+end_src
** Latexmk save-compile-display loop
:PROPERTIES:
:CUSTOM_ID: sec:latexmk-save-compile-display-loop
:END:
The =latexmk= resource file in the next source code block shows how to use
=emacsclient= to (re)display the PDF file in Emacs after each successful
(re)compilation on condition that the local ~compile-command~ variable is
correct. The local ~compile-command~ variable in the [[#sec:local-variables][local variables]] section
(only visible in =org= files, but not in =html= and =pdf= files) shows how to
make use of the =latexmkrc= file.
#+attr_latex: :options breaklines
#+caption[Tangle the =Latexmk= resource file]:
#+caption: Tangle the =Latexmk= resource file.
#+name: lst:latexmkrc
#+begin_src perl -n :tangle latexmkrc :comments no
# pdf creator
$pdf_mode = 4; # 4 means lualatex
# pdf previewer and update pdf previewer
$pdf_previewer = "emacsclient -e '(find-file-other-window %S)'";
$pdf_update_method = 4; # 4 runs $pdf_update_command to force the update
$pdf_update_command = "emacsclient -e '(with-current-buffer (find-buffer-visiting %S) (pdf-view-revert-buffer nil t))'";
# see for instance glossary.latexmkrc
add_cus_dep( 'acn', 'acr', 0, 'makeglossaries' );
add_cus_dep( 'glo', 'gls', 0, 'makeglossaries' );
# use ".=" to append to $clean_ext
$clean_ext .= " acr acn alg bbl dvi glo gls glg ist lol lst";
$clean_ext .= " nav run.xml snm synctex.gz";
sub makeglossaries {
my ($name, $path) = fileparse( $$Psource );
return system "makeglossaries -d '$path' '$name'";
}
# Emacs looks for "Local variables:" after the last "newline-formfeed".
# Local Variables:
# mode: perl
# End:
#+end_src
** [[https://qutebrowser.org/doc/userscripts.html][Qutebrowser userscript]]
:PROPERTIES:
:CUSTOM_ID: sec:qutebrowser-userscript
:END:
The next block contains an userscript that sends a [[info:org#The store-link protocol][store-link org-protocol]]
message with the url and the title from [[https://qutebrowser.org][qutebrowser]] to =emacsclient=. The
function =urlencode= translates the url and the title for the message. The
[[info:python#Examples<22>][Python urllib examples]] show how to use =urlencode=. The final =execvp= call
deals with a [[https://qutebrowser.org][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 [[https://qutebrowser.org][qutebrowser]].
On a [[https://en.wikipedia.org/wiki/POSIX][POSIX]] system, you can run the userscript from [[https://qutebrowser.org][qutebrowser]] or from a
terminal to see whether it works. In case you try to run it from Emacs, Emacs
may hang or die.
#+caption[Tangle a qutebrowser userscript]:
#+caption: Tangle a qutebrowser userscript.
#+header: :comments no
#+header: :tangle-mode (identity #o755)
#+name: lst:qutebrowser-userscript
#+begin_src python -n :eval never :tangle org-store-link
#!/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))
#+end_src
** TODO Look into: org-protocol handling with other browser on Darwin
:PROPERTIES:
:CUSTOM_ID: sec:org-protocol-darwin
:END:
1. [[http://rwx.io/posts/osx-uri-protocol-handler/][Patrick Goddi: macOS URI protocol handler]] and his [[https://github.com/fooqri/uri-handler][URI-Handler]].
2. [[https://www.hammerspoon.org/][Hammerspoon]]
3. [[https://vritser.github.io/posts/capture-anywhere/][Emacs Capture Anywhere]]
** [[https://en.wikipedia.org/wiki/AppleScript][AppleScript]] :noexport:
:PROPERTIES:
:CUSTOM_ID: sec:applescript
:header-args:emacs-lisp: :tangle no
:END:
#+caption[Ensure =applescript-mode= installation]:
#+caption: Ensure =applescript-mode= installation.
#+name: lst:ensure-applescript-mode-installation
#+begin_src emacs-lisp -n :results silent
(when (ensure-package-installation 'applescript-mode))
#+end_src
#+caption[Applescript code to register =org-protocol= on Darwin]:
#+caption: Applescript code to register =org-protocol= on Darwin.
#+name: lst:applescript-org-protocol
#+begin_src applescript :tangle no
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
#+end_src
* Tools to handle buffers and modes
:PROPERTIES:
:CUSTOM_ID: sec:buffer-mode-tools
:END:
#+caption[Tools to handle buffers and modes]:
#+caption: Tools to handl buffers and modes.
#+name: lst:buffer-mode-tools
#+begin_src emacs-lisp -n :results silent
(defun set-all-buffers-with-modes (minor-mode value modes)
"Set MINOR-MODE to VALUE for all buffers with a mode derived from MODES."
(dolist (buffer (buffer-list))
(with-current-buffer buffer
(when (and (derived-mode-p modes) (buffer-file-name))
(funcall minor-mode value)))))
(defun enable-ro-all-org-mode-buffers ()
"Enable `buffer-read-only' of all `org-mode' buffers."
(interactive)
(set-all-buffers-with-modes #'read-only-mode +1 '(org-mode))
(message "Enabled `buffer-read-only' of all `org-mode' buffers."))
(defun disable-ro-all-org-mode-buffers ()
"Disable `buffer-read-only' of all `org-mode' buffers."
(interactive)
(set-all-buffers-with-modes #'read-only-mode -1 '(org-mode))
(message "Disabled `buffer-read-only' of all `org-mode' buffers."))
(defun active-major-modes ()
"Return a list of active major modes found by iterating over all buffers."
(let (result)
(dolist (buffer (buffer-list))
(with-current-buffer buffer
(let ((mode major-mode))
(push mode result))))
(cl-sort (cl-remove-duplicates result) #'string-lessp)))
#+end_src
* Completion
:PROPERTIES:
:CUSTOM_ID: sec:completion
:END:
[[info:vertico#Top][Vertico (info)]] provides a performant and minimalistic vertical completion UI
based on the default completion system and behaves therefore correctly under all
circumstances. [[https://cestlaz.github.io/post/using-emacs-80-vertico/][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. [[info:embark#Top][Embark (info)]] for minibuffer actions with context menus,
2. [[info:marginalia#Top][Marginalia (info)]] for rich annotations in the minibuffer, and
3. [[info:consult#Top][Consult (info)]] for useful search and navigation commands,
where the order is that of [[https://github.com/bdarcus/citar#installation][enhancing citar's experience]] and the configuration
steps below.
Finally, [[https://company-mode.github.io/][company: a modular complete-anything framework for Emacs]] provides
completion in any buffer.
** [[info:vertico#Top][Vertico (info)]]
:PROPERTIES:
:CUSTOM_ID: sec:vertico-configuration
:END:
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. BUG: Adding ~eww-history~, ~register-alist~, ~regexp-search-string~, or
~search-string~ to ~savehist-additional-variables~ fails to save the relevant
histories.
#+caption[Enable =savehist-mode= and =vertico-mode=]:
#+caption: Enable =savehist-mode= and =vertico-mode=.
#+name: lst:enable-vertico-mode
#+begin_src emacs-lisp -n :results silent
(when (require 'savehist nil 'noerror)
(setopt savehist-additional-variables '(kill-ring search-ring))
(savehist-mode +1))
(when (and (ensure-package-installation 'vertico)
(fboundp 'vertico-mode))
(vertico-mode +1))
(with-eval-after-load 'vertico
(keymap-set vertico-map "RET" #'vertico-directory-enter)
(keymap-set vertico-map "DEL" #'vertico-directory-delete-char)
(keymap-set vertico-map "M-DEL" #'vertico-directory-delete-word))
#+end_src
#+attr_latex: :booktabs yes :float table
#+caption[Vertico key map bindings]:
#+caption: Vertico key map bindings.
#+name: tab:vertico-keymap-bindings
|-------------------------------+----------------------------------+---------------------|
| command | remap | keys |
|-------------------------------+----------------------------------+---------------------|
| vertico-directory-delete-char | | {{{kbd(DEL)}}} |
| vertico-directory-delete-word | | {{{kbd(M-DEL)}}} |
| vertico-directory-enter | | {{{kbd(RET)}}} |
| vertico-exit | exit-minibuffer | {{{kbd(C-j)}}} |
| vertico-exit-input | | {{{kbd(C-RET)}}} |
| vertico-first | beginning-of-buffer | {{{kbd(M-<)}}} |
| vertico-first | minibuffer-beginning-of-buffer | {{{kbd(M-<)}}} |
| vertico-insert | | {{{kbd(TAB)}}} |
| vertico-last | end-of-buffer | {{{kbd(M->)}}} |
| vertico-next-group | forward-paragraph | {{{kbd(C-<down>)}}} |
| vertico-next | next-line-or-history-element | {{{kbd(<down>)}}} |
| vertico-next | next-line | {{{kbd(C-n)}}} |
| vertico-previous-group | backward-paragraph | {{{kbd(C-<up>)}}} |
| vertico-previous | previous-line-or-history-element | {{{kbd(<up>)}}} |
| vertico-previous | previous-line | {{{kbd(C-p)}}} |
| vertico-save | kill-ring-save | {{{kbd(M-w)}}} |
| vertico-scroll-down | scroll-down-command | {{{kbd(M-v)}}} |
| vertico-scroll-up | scroll-up-command | {{{kbd(C-v)}}} |
|-------------------------------+----------------------------------+---------------------|
** [[info:orderless#Top][Orderless (info)]] :noexport:
:PROPERTIES:
:CUSTOM_ID: sec:orderless-configuration
:header-args:emacs-lisp: :tangle no
:END:
Listing [[lst:configure-orderless]] configures [[info:orderless#Company][orderless for company (info)]]. Note:
*Python editing is orders of magnitude faster after removal of Orderless*.
#+caption[Configure =orderless=]:
#+caption: Configure =orderless=.
#+name: lst:configure-orderless
#+begin_src emacs-lisp -n :results silent
(when (ensure-package-installation 'marginalia)
(with-eval-after-load 'orderless
(setopt 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)))
#+end_src
** [[info:embark#Top][Embark (info)]]
:PROPERTIES:
:CUSTOM_ID: sec:embark-configuration
:END:
Listing [[lst:bind-embark-commands][bind =embark= commands]] binds =embark= commands to the global key map:
1. =embark-act= prompts the user for an action and performs it.
2. Except for highlighting of email and web URLs, =embark-dwim= englobes the
functionality of src_emacs-lisp{(find-library "goto-addr")} that activates
mail and web URLs to turn them into clickable buttons. Since =embark-dwim=
acts on a superset of target types, it renders =goto-addr= superfluous. See:
1. [[info:emacs#Goto Address mode][Goto Address mode (info)]].
2. [[info:embark#The default action on a target][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 of =prefix-help-command= enables minibuffer help after a
prefix key (for instance {{{kbd(C-x)}}} or {{{kbd(C-c)}}}) as typing
{{{kbd(C-x C-h)}}} or {{{kbd(C-c C-h)}}} shows.
#+caption[Bind =embark= commands globally]:
#+caption: Bind =embark= commands globally.
#+name: lst:bind-embark-commands
#+begin_src emacs-lisp -n :results silent
(when (ensure-package-installation 'embark 'embark-consult)
(when (fboundp 'embark-act)
(keymap-global-set "C-," #'embark-act))
(when (fboundp 'embark-dwim)
(keymap-global-set "C-:" #'embark-dwim))
(when (fboundp 'embark-bindings)
(keymap-global-set "C-h B" #'embark-bindings))
(when (fboundp 'embark-prefix-help-command)
(setq prefix-help-command #'embark-prefix-help-command)))
#+end_src
** [[info:marginalia#Top][Marginalia (info)]]
:PROPERTIES:
:CUSTOM_ID: sec:marginalia-configuration
:END:
Listing [[lst:enable-marginalia-mode]] enables =marginalia-mode=.
#+caption[Enable =marginalia-mode=]:
#+caption: Enable =marginalia-mode=.
#+name: lst:enable-marginalia-mode
#+begin_src emacs-lisp -n :results silent
(when (and (ensure-package-installation 'marginalia)
(fboundp 'marginalia-mode))
(marginalia-mode +1))
#+end_src
** [[info:consult#Top][Consult (info)]]
:PROPERTIES:
:CUSTOM_ID: sec:consult-setup
:END:
[[info:consult#Top][Consult (info)]] provides practical commands based on the Emacs minibuffer
completion function [[info:elisp#Minibuffer Completion][completing-read]]. Listing [[lst:bind-consult-commands][bind =consult= commands]] binds
=consult= commands to different key maps.
#+attr_latex: :booktabs yes :float table
#+caption[Configuration specific key bindings]:
#+caption: Configuration specific key-bindings.
#+name: tab:configuration-specific-key-bindings
|-----------------------------+----------------------+--------------------|
| command | key map | keys |
|-----------------------------+----------------------+--------------------|
| consult-bookmark | ctl-x-r-keymap | {{{kbd(C-x r b)}}} |
| consult-buffer-other-frame | ctl-x-5-keymap | {{{kbd(C-x 5 b)}}} |
| consult-buffer-other-window | ctl-x-4-keymap | {{{kbd(C-x 4 b)}}} |
| consult-buffer | ctl-x-keymap | {{{kbd(C-x b)}}} |
| consult-compile-error | goto-map | {{{kbd(M-g e)}}} |
| consult-complex-command | ctl-x-keymap | {{{kbd(C-x M-:)}}} |
| consult-find | search-map | {{{kbd(M-s f)}}} |
| consult-focus-lines | search-map | {{{kbd(M-s u)}}} |
| consult-git-grep | search-map | {{{kbd(M-s g)}}} |
| consult-global-mark | goto-map | {{{kbd(M-g k)}}} |
| consult-goto-line | goto-map | {{{kbd(M-g M-g)}}} |
| consult-goto-line | goto-map | {{{kbd(M-g g)}}} |
| consult-history | global-map | {{{kbd(C-c h)}}} |
| consult-imenu | goto-map | {{{kbd(M-g i)}}} |
| consult-keep-lines | search-map | {{{kbd(M-s k)}}} |
| consult-line | search-map | {{{kbd(M-s l)}}} |
| consult-mark | goto-map | {{{kbd(M-g m)}}} |
| consult-mode-command | global-map | {{{kbd(C-c m)}}} |
| consult-multi-occur | search-map | {{{kbd(M-s m)}}} |
| consult-org-heading | org-mode-map | {{{kbd(C-c C-h)}}} |
| consult-outline | goto-map | {{{kbd(M-g o)}}} |
| consult-register | ctl-x-r-keymap | {{{kbd(C-x r x)}}} |
| consult-yank-pop | global-map | {{{kbd(M-y)}}} |
|-----------------------------+----------------------+--------------------|
| elfeed | global-map | {{{kbd(C-x w)}}} |
| embark-act | global-map | {{{kbd(C-\,)}}} |
| embark-bindings | global-map | {{{kbd(C-h B)}}} |
| embark-dwim | global-map | {{{kbd(C-:)}}} |
| iedit-mode | global-map | {{{kbd(C-;)}}} |
| minibuffer-complete-history | minibuffer-local-map | {{{kbd(C-<tab>)}}} |
| narrow-or-widen-dwim | ctl-x-keymap | {{{kbd(C-x C-n)}}} |
| org-agenda | global-map | {{{kbd(C-c a)}}} |
| org-capture | global-map | {{{kbd(C-c c)}}} |
| org-cite | org-mode-map | {{{kbd(C-c b)}}} |
| org-goto | org-goto | {{{kbd(C-c C-j)}}} |
| org-insert-link-global | global-map | {{{kbd(C-c C-l)}}} |
| org-narrow-to-table | ctl-x-keymap | {{{kbd(C-x n t)}}} |
| org-store-link | global-map | {{{kbd(C-c l)}}} |
|-----------------------------+----------------------+--------------------|
#+caption[Bind =consult= commands]:
#+caption: Bind =consult= commands.
#+name: lst:bind-consult-commands
#+begin_src emacs-lisp -n :results silent
(when (ensure-package-installation 'consult)
;; C-c bindings (current-global-map)
(keymap-global-set "C-c h" #'consult-history)
(keymap-global-set "C-c m" #'consult-mode-command)
;; C-x bindings (ctl-x-map)
(keymap-set ctl-x-map "M-:" #'consult-complex-command)
(keymap-set ctl-x-map "b" #'consult-buffer)
(keymap-set ctl-x-4-map "b" #'consult-buffer-other-window)
(keymap-set ctl-x-5-map "b" #'consult-buffer-other-frame)
(keymap-set ctl-x-r-map "x" #'consult-register)
(keymap-set ctl-x-r-map "b" #'consult-bookmark)
;; M-g bindings (goto-map)
(keymap-set goto-map "g" #'consult-goto-line)
(keymap-set goto-map "M-g" #'consult-goto-line)
(keymap-set goto-map "o" #'consult-outline)
(keymap-set goto-map "m" #'consult-mark)
(keymap-set goto-map "k" #'consult-global-mark)
(keymap-set goto-map "i" #'consult-imenu)
(keymap-set goto-map "e" #'consult-compile-error)
(with-eval-after-load 'org
(keymap-set org-mode-map "C-c C-h" #'consult-org-heading))
;; M-s bindings (search-map)
(keymap-set search-map "g" #'consult-git-grep)
(keymap-set search-map "f" #'consult-find)
(keymap-set search-map "k" #'consult-keep-lines)
(keymap-set search-map "l" #'consult-line)
(keymap-set search-map "m" #'consult-multi-occur)
(keymap-set search-map "u" #'consult-focus-lines)
;; Other bindings (current-global-map)
(keymap-global-set "M-y" #'consult-yank-pop))
#+end_src
*** TODO Explore ~org-goto~ versus ~consult-org-heading~
:PROPERTIES:
:CUSTOM_ID: sec:todo-org-goto
:END:
Explore in particular ~auto-isearch~ (looks powerfull) and ~org-occur~ (looks
mysterious) in the ~org-goto~ user interface.
** [[https://company-mode.github.io/][Company (info)]]
:PROPERTIES:
:CUSTOM_ID: sec:company-setup
:END:
[[info:company#Top][Company (info)]] is a modular completion framework and listing [[lst:setup-company]]
configures =company= after ensuring the =company= installation.
#+caption[Setup =company=]:
#+caption: Setup =company=.
#+name: lst:setup-company
#+begin_src emacs-lisp -n :results silent
(when (ensure-package-installation 'company)
;; https://github.com/purcell/emacs.d/issues/778
(setopt company-transformers '(company-sort-by-occurrence))
(dolist (hook '(LaTeX-mode-hook
org-mode-hook
emacs-lisp-mode-hook
lisp-interaction-mode-hook
lisp-mode-hook
python-mode-hook
ielm-mode-hook
sly-mrepl-mode-hook))
(add-hook hook #'company-mode)))
#+end_src
* [[info:emacs#Search][Search and replace (info)]]
:PROPERTIES:
:CUSTOM_ID: sec:search-replace
:END:
** [[info:emacs#Regexp Replace][Regexp Replace (info)]]
:PROPERTIES:
:CUSTOM_ID: sec:regexp-replace
:END:
Executing {{{kbd(M-x replace-regexp)}}} prompts for a regular expression
=REGEXP= and a substitution =NEWSTRING= to replace =REGEXP= with =NEWSTRING=.
The tools in section [[#sec:narrowing][Narrowing]] allow to limit the scope of {{{kbd(M-x
replace-regexp)}}}. A list pointing to relevant documentation is:
1. [[info:emacs#Regexp Replace][Regexp Replace (info)]] explains how the substitution handles references to
regular expression groups and lisp expressions.
2. [[info:emacs#Regexps][Syntax of Regular Expressions (info)]] describes regular expression features
for users.
3. [[info:elisp#Regular Expressions][Emacs Lisp Regular Expressions (info)]] describes regular expression features
for programmers.
4. [[https://www.emacswiki.org/][Emacs Wiki]] has an article on [[https://www.emacswiki.org/emacs/ReplaceRegexpWithLispExpressions][replacing regexp with lisp expressions]].
Table [[tab:replace-regexp-tranform]] tabulates how to perform example tasks by
means of {{{kbd(M-x replace-regexp)}}} using =(regular-expression transform)=
pairs.
#+attr_latex: :booktabs yes :float table
#+caption[Example =replace-regexp= (regular-expression substitution) pairs]:
#+caption: Example =replace-regexp= (regular-expression substitution) pairs.
#+name: tab:replace-regexp-tranform
| task | regular expression | substitution |
|-----------------------------+---------------------------------+---------------|
| move dot two digits forward | =0\.\([[:digit:]][[:digit:]]\)= | =0\1.= |
| move dot two digits forward | =0\.\([0-9][0-9]\)= | =0\1.= |
| renumber grep/occur matches | =\(.+:\)= | =\,(1+ \#)._= |
|-----------------------------+---------------------------------+---------------|
** [[https://github.com/Wilfred/deadgrep#readme][Deadgrep]] :noexport:
:PROPERTIES:
:CUSTOM_ID: sec:deadgrep
:header-args:emacs-lisp: :tangle no :eval never-export
:END:
[[https://github.com/Wilfred/deadgrep#readme][Deadgrep]] uses [[https://github.com/BurntSushi/ripgrep#readme][ripgrep]] for superfast text searching in the default directory or
the current [[https://en.wikipedia.org/wiki/Version_control][VCS]] directory tree. Listing [[lst:bind-deadgrep-commands][setup =deadgrep= commands]] binds
=deadgrep= globally to {{{kbd(M-s d)}}} and =deadgrep-edit-mode= locally to
{{{kbd(C-c C-w)}}}.
#+caption[Bind =deadgrep= commands]:
#+caption: Bind =deadgrep= commands.
#+name: lst:bind-deadgrep-commands
#+begin_src emacs-lisp -n :results silent
(when (and (ensure-package-installation 'deadgrep)
(fboundp 'deadgrep))
(keymap-set search-map "d" #'deadgrep)
(with-eval-after-load 'deadgrep
(keymap-set deadgrep-mode-map "C-c C-w" #'deadgrep-edit-mode)))
#+end_src
** [[https://www.masteringemacs.org/article/searching-buffers-occur-mode][Searching and Editing in Buffers with Occur Mode]]
:PROPERTIES:
:CUSTOM_ID: sec:occur
:END:
#+caption[Define =multi-occur-in-this-mode=]:
#+caption: Define =multi-occur-in-this-mode=.
#+name: lst:multi-occur-in-this-mode
#+begin_src emacs-lisp -n
(defun get-buffers-matching-mode (mode)
"Return a list of buffers whose major-mode is equal to MODE."
(let ((buffer-mode-matches '()))
(dolist (buf (buffer-list))
(with-current-buffer buf
(when (eq mode major-mode)
(push buf buffer-mode-matches))))
buffer-mode-matches))
(defun multi-occur-in-this-mode ()
"Show all lines matching REGEXP in buffers with this major mode."
(interactive)
(multi-occur
(get-buffers-matching-mode major-mode)
(car (occur-read-primary-args))))
#+end_src
** [[https://www.genivia.com/get-ugrep.html][Ugrep]]
:PROPERTIES:
:CUSTOM_ID: sec:ugrep
:END:
[[https://www.genivia.com/get-ugrep.html][Ugrep]] is an ultra fast grep with interactive query UI and fuzzy search. The
pages [[https://github.com/Genivia/ugrep#emacs][Using ugrep within Emacs]] and [[https://manueluberti.eu/emacs/2022/08/07/emacs-ugrep/][Ugrep in Emacs]] show how to make Emacs use it
with [[info:emacs#Grep Searching][lgrep]] and [[info:emacs#Xref][xref]]. Listing [[lst:set-grep+xref-options][set =grep= and =xref= options]] shows my
implementation of the suggestions on those pages. *To do*:
1. None of the commands listed in [[info:emacs#Grep Searching][Searching with Grep under Emacs (info)]]
works on my system with the exception of ~lgrep~ when using [[https://www.genivia.com/get-ugrep.html][ugrep]].
2. None of the ~grep~, ~agrep~, ~egrep~, ~fgrep~, and ~glimpse~ builtins listed
in [[info:eshell#Built-ins][Eshell builtins (info)]] works.
#+caption[Set =grep= and =xref= options to use =ugrep= when available]:
#+caption: Set =grep= and =xref= options to use =ugrep= when available.
#+name: lst:set-grep+xref-options
#+begin_src emacs-lisp -n :results silent
(when (executable-find "ugrep")
(with-eval-after-load 'grep
(setopt grep-template (string-join '("ugrep"
"--color=always"
"--decompress"
"--ignore-binary"
"--ignore-case"
"--include=\"<F>\""
"--line-number"
"--null"
"--recursive"
"--regexp=<R>")
" ")
grep-command "ugrep --color=always --null -nH -e "
grep-use-null-device nil))
(with-eval-after-load 'xref
(setopt
xref-search-program 'ugrep
xref-search-program-alist
`((grep . "xargs -0 grep <C> --null -snHE -e <R>")
(ripgrep . ,(concat "xargs -0 rg <C> --null -nH"
" --no-heading --no-messages -g '!*/' -e <R>"))
(ugrep . "xargs -0 ugrep <C> --null -ns -e <R>")))))
#+end_src
** [[https://github.com/mattiase/xr#readme][XR - Emacs regexp parser and analyzer]]
:PROPERTIES:
:CUSTOM_ID: sec:ensure-xr-installation
:END:
[[https://github.com/mattiase/xr#readme][XR]] converts Emacs regular expressions to the structured =rx= form, thus being an
inverse of =rx=. It can also find mistakes and questionable constructs inside
regexp strings.
#+caption[Ensure =xr= installation]:
#+caption: Ensure =xr= installation.
#+name: lst:ensure-xr-installation
#+begin_src emacs-lisp -n :results silent
(when (ensure-package-installation 'xr))
#+end_src
* [[info:emacs#Version Control][Version Control (info)]]
:PROPERTIES:
:CUSTOM_ID: sec:version-control
:END:
** [[info:ediff#Top][Ediff (info)]]
:PROPERTIES:
:CUSTOM_ID: sec:ediff
:END:
Video links to complete the [[info:ediff#Top][ediff (info)]] manual are:
1. [[https://www.youtube.com/watch?v=6ZWp05OW1c0][Emergency Emacs]].
2. [[https://protesilaos.com/codelog/2020-04-10-emacs-smerge-ediff/][Use =smerge= and =ediff= to resolve file conflicts]].
Listing [[lst:setup-ediff]] configures =ediff= to display all its buffers in a
single frame and to make all text visible prior to ediffing Org buffers.
#+caption[Setup =ediff=]:
#+caption: Setup =ediff=.
#+name: lst:setup-ediff
#+begin_src emacs-lisp -n :results silent
(with-eval-after-load 'emacs
(setopt ediff-merge-split-window-function #'split-window-horizontally
ediff-split-window-function #'split-window-horizontally
ediff-window-setup-function #'ediff-setup-windows-plain))
(with-eval-after-load 'org
;; https://github.com/oantolin/emacs-config#readme
(defun ediff-with-org-show-all ()
"Expand all headings prior to ediffing org buffers."
(add-hook 'ediff-prepare-buffer-hook #'org-fold-show-all nil t))
(add-hook 'org-mode-hook #'ediff-with-org-show-all))
#+end_src
** [[https://git-scm.com/book/en/v2][Git]]
:PROPERTIES:
:CUSTOM_ID: sec:git
:END:
[[https://www.theserverside.com/blog/Coffee-Talk-Java-News-Stories-and-Opinions/git-push-new-branch-remote-github-gitlab-upstream-example][How to push new branches to remote repositories on Github or Gitlab]] explains how
to do this from the command line. Listing
[[lst:forking-and-branching-remote-repositories]] shows an example work flow of
command line =git= and =nano= invocations. I did yet not figure out how to do
this by means of [[info:magit#Top][Magit (info)]]: I still have to understand [[info:magit#Pushing][Magit pushing (info)]]
and to figure out how to use src_emacs-lisp{(magit-push-current-to-pushremote)}
or src_emacs-lisp{(magit-push-current-to-upstream)}.
#+caption[Forking and branching on Github or Gitlab]:
#+caption: Forking and branching on Github or Gitlab.
#+name: lst:forking-and-branching-remote-repositories
#+begin_src shell -n :eval never
# Clone the repository
git clone git@github.com:gav451/engrave-faces engrave-faces-fork
cd engrave-face-fork
# Handle 1st branch:
# - Create and switch to a new branch with either:
git switch -c latex-engrave-symbolic-color-names
# or: git checkout -b latex-engrave-symbolic-color-names
# - Use "--set-upstream origin" as a 1st-time branch push:
git push --set-upstream origin latex-engrave-symbolic-color-names
# - Create, add, and commit, and push changes in this branch:
nano engrave-faces-latex.el
git add engrave-faces-latex.el
git commit -m 'Commit latex-engrave-dont-symbolic-color-names changes'
git push
# - Switch to master branch:
git checkout master
# Handle 2nd branch:
# - Create and switch to a new branch with either:
git switch -c latex-engrave-dont-wrap-white-space
# - or: git checkout -b latex-engrave-dont-wrap-white-space
# - Use "--set-upstream origin" as a 1st-time branch push:
git push --set-upstream origin latex-engrave-dont-wrap-white-space
# - Create, add, commit, and push changes in this branch:
nano engrave-faces-latex.el
git add engrave-faces-latex.el
git commit -m 'Commit latex-engrave-dont-wrap-white-space changes'
git push
# - Switch to master branch:
git checkout master
#+end_src
*** Github quick setup
:PROPERTIES:
:CUSTOM_ID: sec:github-quick-setup
:END:
#+caption[Make a new repository on the command line]:
#+caption: Make a new repository on the command line.
#+name: lst:make-new-git-repo
#+begin_src shell -n :eval never
echo "# org-oxx" >> README.md
git init
git add README.md
git commit -m "first commit"
git branch -M main
git remote add origin git@github.com:gav451/org-oxx.git
git push -u origin main
#+end_src
#+caption[Push an old git repository on the command line]:
#+caption: Push an old git repository on the command line.
#+name: lst:push-old-git-repo
#+begin_src shell -n :eval never
git remote add origin git@github.com:gav451/org-oxx.git
git branch -M main
git push -u origin main
#+end_src
*** Learn git
:PROPERTIES:
:CUSTOM_ID: sec:learn-git
:END:
Useful [[https://git-scm.com/book/en/v2][Git]] links are:
- [[https://sentry.io/answers/abort-a-merge-in-git/][Abort a merge in Git]]
- [[https://sentry.io/answers/change-the-uri-for-a-git-remote/][Change the URI for a Git remote]]
- [[https://sentry.io/answers/check-out-a-remote-branch-in-git/][Check out a remote branch in Git]]
- [[https://sentry.io/answers/clone-a-git-repository-to-a-specific-folder/][Clone a Git repository to a specific folder]]
- [[https://sentry.io/answers/clone-a-specific-git-repository-branch/][Clone a specific Git repository branch]]
- [[https://sentry.io/answers/create-a-new-git-branch-from-an-existing-branch/][Create a new Git branch from an existing branch]]
- [[https://sentry.io/answers/create-a-remote-branch-in-git/][Create a remote branch in Git]]
- [[https://sentry.io/answers/delete-a-commit-from-a-branch-in-git/][Delete a commit from a branch in Git]]
- [[https://sentry.io/answers/delete-a-file-from-a-git-repository/][Delete a file from a Git repository]]
- [[https://sentry.io/answers/delete-a-git-branch-locally-and-remotely/][Delete a Git branch locally and remotely]]
- [[https://sentry.io/answers/determine-the-origin-of-a-cloned-git-repository/][Determine the origin of a cloned Git repository]]
- [[https://sentry.io/answers/undo-the-most-recent-local-git-commits/][Undo the most recent local Git commits]]
*** [[https://www.theserverside.com/blog/Coffee-Talk-Java-News-Stories-and-Opinions/git-push-new-branch-remote-github-gitlab-upstream-example][List all Git branches]]
:PROPERTIES:
:CUSTOM_ID: sec:list-all-git-branches
:END:
#+caption[List all =git= branches]:
#+caption: List all =git= branches.
#+name: lst:list-all-git-branches
#+begin_src shell -n :exports both :results verbatim
git -C ~/VCS/engrave-faces-fork branch -a
#+end_src
#+RESULTS: lst:list-all-git-branches
: * gav
: latex-engrave-dont-wrap-white-space
: latex-engrave-symbolic-color-names
: master
: remotes/origin/HEAD -> origin/master
: remotes/origin/gav
: remotes/origin/latex-engrave-dont-wrap-white-space
: remotes/origin/latex-engrave-symbolic-color-names
: remotes/origin/master
*** [[info:magit#Top][Magit (info)]]
:PROPERTIES:
:CUSTOM_ID: sec:magit
:END:
[[info:magit#Top][Magit (info)]] is an Emacs interface to the version control system [[https://git-scm.com/book/en/v2][Git]] and the
snippet [[lst:ensure-magit-installation][below]] ensures its installation. Magit usage tips are:
1. [[https://stackoverflow.com/questions/tagged/magit?tab=Votes][Most upvoted Magit questions on Stack Overflow]].
2. [[https://stackoverflow.com/a/33644270][Abort an active rebase]] by pressing {{{kbd(r a)}}}.
#+caption[Ensure =Magit= installation]:
#+caption: Ensure =Magit= installation
#+name: lst:ensure-magit-installation
#+begin_src emacs-lisp -n :results silent
(ensure-package-installation 'magit)
#+end_src
* Reading
:PROPERTIES:
:CUSTOM_ID: sec:reading
:END:
** Reading [[https://en.wikipedia.org/wiki/DjVu][DjVu]] files
:PROPERTIES:
:CUSTOM_ID: sec:reading-djvu-files
:END:
[[info:emacs#Document View][Document View (info)]] allows to read [[https://en.wikipedia.org/wiki/DjVu][DjVu]] files on condition that it can use the
command line [[https://en.wikipedia.org/wiki/DjVu][DjVu]] decoder =ddjvu= from the [[http://djvu.sourceforge.net/][DjVuLibre]] utilities as shown by the
code of src_emacs-lisp{(find-function 'doc-view-djvu->tiff-converter-ddjvu)}.
Use {{{kbd(+)}}} or {{{kbd(-)}}} to enlarge or shrink what =doc-view-mode=
displays and use {{{kbd(SPC)}}} or {{{kbd(S-SPC)}}} to scroll down or up through
when pages are larger than the =doc-view-mode= window.
** Reading EPUB files
:PROPERTIES:
:CUSTOM_ID: sec:reading-epub-files
:END:
The package [[https://depp.brause.cc/nov.el/][nov.el]] provides a major mode for reading EPUB files in Emacs.
Listing [[lst:configure-nov]] configures [[https://depp.brause.cc/nov.el/][nov.el]].
#+caption[Configure =nov=]:
#+caption: Configure =nov=.
#+name: lst:configure-nov
#+begin_src emacs-lisp -n :results silent
(when (and (ensure-package-installation 'nov)
(fboundp 'nov-mode))
(add-to-list 'auto-mode-alist `(,(rx ".epub" eos) . nov-mode)))
#+end_src
** Reading PDF files
:PROPERTIES:
:CUSTOM_ID: sec:reading-pdf-files
:END:
The [[https://github.com/vedang/pdf-tools][pdf-tools]] package exploits the [[https://github.com/freedesktop/poppler][poppler]] library to render and to let you
annotate [[https://en.wikipedia.org/wiki/PDF][PDF]] files. It also exploits the [[https://wiki.contextgarden.net/SyncTeX][SyncTeX]] library to link anchors in [[https://en.wikipedia.org/wiki/PDF][PDF]]
files produced with LaTeX to the original LaTeX sources. In order to use
[[https://github.com/vedang/pdf-tools][pdf-tools]], you have to type {{{kbd(M-x pdf-tools-install)}}} after installation
of [[https://github.com/vedang/pdf-tools][pdf-tools]] from [[https://melpa.org/][MELPA]] or after each update of [[https://github.com/freedesktop/poppler][poppler]] to build or rebuild the
=epdfinfo= executable that serves the [[https://en.wikipedia.org/wiki/PDF][PDF]] files to Emacs. Table
[[tab:pdf-tools-commands-and-bindings]] lists important local map ~pdf-tools~ key
bindings.
#+caption[Setup =pdf-tools=]:
#+caption: Setup =pdf-tools=.
#+name: lst:setup-pdf-tools
#+begin_src emacs-lisp -n :results silent
(when (and (ensure-package-installation 'pdf-tools)
(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
;; The `image-save' key binding is now "i o".
(keymap-set pdf-outline-minor-mode-map "O" #'pdf-outline))
(with-eval-after-load 'pdf-view
(setopt pdf-view-display-size 'fit-page
pdf-view-use-scaling (eq system-type 'darwin))))
#+end_src
#+attr_latex: :booktabs yes :float table
#+caption[Pdf-tools commands with key bindings]:
#+caption: Pdf-tools commands with key bindings.
#+name: tab:pdf-tools-commands-and-bindings
|---------------------------------+--------------|
| command | key binding |
|---------------------------------+--------------|
| *pdf-outline* | {{{kbd(o)}}} |
| *pdf-outline* | {{{kbd(O)}}} |
| *pdf-view-fit-height-to-window* | {{{kbd(H)}}} |
| *pdf-view-fit-width-to-window* | {{{kbd(W)}}} |
|---------------------------------+--------------|
* Writing
:PROPERTIES:
:CUSTOM_ID: sec:writing
:END:
** Writing LaTeX files
:PROPERTIES:
:CUSTOM_ID: sec:writing-latex-files
:END:
Loading =tex.el= immediately instead of lazily ensures proper initialization of
[[https://en.wikipedia.org/wiki/AUCTeX][AUCTeX]]. In particular, the ~TeX-master~ safe local variable in the =tex.el=
elisp library file has no autoload cookie. Without prior loading of =tex.el=,
Emacs will complain that ~TeX-master~ is no safe local variable in case it reads
a LaTeX file that sets ~TeX-master~. The list below summarizes the main [[https://en.wikipedia.org/wiki/AUCTeX][AUCTeX]]
configuration objectives:
1. Listing [[lst:set-tex-options]] ensures installation of [[https://en.wikipedia.org/wiki/AUCTeX][AUCTeX]] and initializes
[[https://en.wikipedia.org/wiki/AUCTeX][AUCTeX]] properly for =LuaTeX= or =LuaLaTeX=.
2. Listing [[lst:set-tex-options]] also enables *indenting between square brackets*
which is a recent feature.
3. Listing [[lst:update-lualatex-opentype-font-name-database]] defines a function to
update the =OpenType Font= name database for =LuaLaTeX= when the output of =LuaLaTeX= shows missing fonts.
4. Listing [[lst:set-bibtex-options]] configures the Emacs =bibtex= library to use
the LaTeX =BiBTeX= dialect for backwards compatibility.
5. Listing [[lst:set-font-latex-options]] disables font scaling of section titles.
6. Listing [[lst:set-latex-options]] configures =latex= for a full featured =LaTeX-section-command=.
#+caption[Ensure =AUCTeX= installation and set =TeX= option]:
#+caption: Ensure =AUCTeX= installation and set =TeX= option.
#+name: lst:set-tex-options
#+begin_src emacs-lisp -n :results silent
(when (ensure-package-installation 'auctex)
;; Use `require' to make `TeX-master' a safe local variable.
(when (require 'tex nil 'noerror)
(setopt
TeX-auto-save t
TeX-engine 'luatex
TeX-indent-close-delimiters "]"
TeX-indent-open-delimiters "["
TeX-install-font-lock #'font-latex-setup
TeX-parse-self t
;; Disable `TeX-electric-math' to prevent collisions with `smartparens'.
TeX-electric-math nil)))
#+end_src
#+caption[Update the =LuaLaTeX OpenType Font= name database]:
#+caption: Update the =LuaLaTeX= =OpenType Font= name database.
#+name: lst:update-lualatex-opentype-font-name-database
#+begin_src emacs-lisp -n :results silent
(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))))))
#+end_src
#+caption[Set =bibtex= options]:
#+caption: Set =bibtex= options.
#+name: lst:set-bibtex-options
#+begin_src emacs-lisp -n :results silent
(with-eval-after-load 'bibtex
(setopt bibtex-dialect 'BibTeX))
#+end_src
#+caption[Set =font-latex= options]:
#+caption: Set =font-latex= options.
#+name: lst:set-font-latex-options
#+begin_src emacs-lisp -n :results silent
(with-eval-after-load 'font-latex
(setopt font-latex-fontify-sectioning 1.0))
#+end_src
#+caption[Set =latex= options]:
#+caption: Set =latex= options.
#+name: lst:set-latex-options
#+begin_src emacs-lisp -n :results silent
(with-eval-after-load 'latex
(setopt LaTeX-electric-left-right-brace t
LaTeX-section-hook '(LaTeX-section-heading
LaTeX-section-title
LaTeX-section-toc
LaTeX-section-section
LaTeX-section-label)))
#+end_src
*** [[https://gitlab.com/matsievskiysv/math-preview/-/blob/master/README.md][Math-preview]] :noexport:
:PROPERTIES:
:CUSTOM_ID: sec:math-preview
:header-args:emacs-lisp: :tangle no
:END:
[[https://gitlab.com/matsievskiysv/math-preview/-/blob/master/README.md][Math-preview]] uses [[https://docs.mathjax.org/en/latest/][Mathjax-3]] to display [[https://www.latex-project.org/][LaTeX]], [[https://www.w3.org/Math/][MathML]],and [[http://asciimath.org/][asciimath]] in Emacs
buffers with help of an external [[https://nodejs.org][node.js]] program.
#+caption[Setup =math-preview=]:
#+caption: Setup =math-preview=.
#+name: lst:setup-math-preview
#+begin_src emacs-lisp -n :results silent
(when (ensure-package-installation 'math-preview)
(with-eval-after-load 'math-preview
;; https://docs.mathjax.org/en/latest/input/tex/extensions/physics.html
(let ((physics (split-string "
abs absolutevalue acomm acos acosecant acosine acot acotangent
acsc admat anticommutator antidiagonalmatrix arccos arccosecant
arccosine arccot arccotangent arccsc arcsec arcsecant arcsin
arcsine arctan arctangent asec asecant asin asine atan atangent
bmqty bqty Bqty bra braket comm commutator cos cosecant cosh
cosine cot cotangent coth cp cross crossproduct csc csch curl dd
derivative det determinant diagonalmatrix diffd differential div
divergence dmat dotproduct dv dyad erf ev eval evaluated exp
expectationvalue exponential expval fderivative fdv flatfrac
functionalderivative grad gradient gradientnabla hypcosecant
hypcosine hypcotangent hypsecant hypsine hyptangent
identitymatrix Im imaginary imat innerproduct ip ket ketbra
laplacian ln log logarithm matrixdeterminant matrixel
matrixelement matrixquantity mdet mel mqty naturallogarithm norm
op order outerproduct partialderivative paulimatrix pb
pderivative pdv pmat pmqty Pmqty poissonbracket pqty Pr
principalvalue Probability pv PV qall qand qas qassume qc qcc
qcomma qelse qeven qfor qgiven qif qin qinteger qlet qodd qor
qotherwise qq qqtext qsince qthen qty quantity qunless qusing
rank Re real Res Residue sbmqty sec secant sech sin sine sinh
smallmatrixquantity smdet smqty spmqty sPmqty svmqty tan tangent
tanh tr Tr trace Trace va var variation vb vdot vectorarrow
vectorbold vectorunit vmqty vnabla vqty vu xmat xmatrix
zeromatrix zmat")))
(cl-pushnew `("physics" . ,physics)
math-preview-tex-packages-autoload-packages))
(setq math-preview-raise 0.5
math-preview-scale 1
math-preview-tex-default-packages '("autoload" "ams" "physics"))
(let ((command (executable-find "~/node_modules/.bin/math-preview")))
(if command
(setq math-preview-command command)
;; https://stackoverflow.com/a/17509764 answers:
;; How to install an npm package from github directly?
(unless (file-directory-p "~/node_modules")
(make-directory "~/node_modules"))
(cl-destructuring-bind (exit-code output)
(shell-command-with-exit-code
"npm" "install"
"git+https://gitlab.com/matsievskiysv/math-preview.git")
(if (= 0 exit-code)
(message "%s" (string-trim output))
(error "%s" (string-trim output))))))))
#+end_src
*** TODO Improve the AUCTeX configuration slowly
:PROPERTIES:
:CUSTOM_ID: sec:setup-auctex-slowly
:END:
[[https://github.com/thisirs/dotemacs/blob/master/lisp/init-auctex.el][AUCTeX setup of an experienced user]]
** Writing [[https://www.markdownguide.org/][Markdown]] files
:PROPERTIES:
:CUSTOM_ID: sec:writing-markdown-files
:END:
#+caption[Configure =markdown-mode=]:
#+caption: Configure =markdown-mode=.
#+name: lst:configure-markdown-mode
#+begin_src emacs-lisp -n :results silent
(when (ensure-package-installation 'markdown-mode))
#+end_src
** Writing [[info:org#Top][Org (info)]] files
:PROPERTIES:
:CUSTOM_ID: sec:writing-org-files
:END:
The [[https://org-babel.readthedocs.io/en/latest/][Org Babel reference card]] complements section [[info:org#Working with Source Code][Working with Source Code (info)]]
of the [[info:org#Top][Org (info)]] manual.
*** [[info:org#Activation][Org activation (info)]]
:PROPERTIES:
:CUSTOM_ID: sec:activate-org
:END:
#+caption[Bind =Org= commands globally]:
#+caption: Bind =Org= commands globally.
#+name: lst:bind-org-commands
#+begin_src emacs-lisp -n :results silent
(with-eval-after-load 'emacs
(global-set-key (kbd "C-c a") #'org-agenda)
(global-set-key (kbd "C-c c") #'org-capture)
(with-eval-after-load 'ol
(global-set-key (kbd "C-c l") #'org-store-link)
(global-set-key (kbd "C-c C-l") #'org-insert-link-global)))
#+end_src
*** Setup Org
:PROPERTIES:
:CUSTOM_ID: sec:setup-org
:END:
I have split the initial [[https://orgmode.org/][Org-mode]] setup over fourteen listings. Here, follows a
list detailing and motivating each listing:
1. Listing [[lst:set-org-options]] handles basic [[https://orgmode.org/][Org-mode]] options.
2. Listing [[lst:undo-org-ctags]] undoes ~org-ctags~. See [[https://list.orgmode.org/87mt43agk6.fsf@localhost/][org-ctags land grab]] for
more information.
3. Listing [[lst:setup-org-babel]] sets ~ob-core~, ~ob-latex~, and ~ob-lisp~ options.
See [[info:org#Working with Source Code][working with source code (info)]] for Org Babel information.
4. Listing [[lst:fake-org-babel-functions]] adds =org-babel-execute:<LANGUAGE>=
functions to silence src_emacs-lisp[:results silent]{(find-library
"org-lint")}.
5. Listing [[lst:ob-tangle-plus]] extends [[info:org#Noweb Reference Syntax][source code export (info)]] with the
possibility to tangle of for instance Emacs Lisp files (or other programming
mode files) into a directory specified by ~org-babel-post-tangle-dir~ which ~toggle-post-tangle-hook-dir-usage~ may change.
6. Listing [[lst:set-org-link-options]] handles [[https://orgmode.org/][Org-mode]] options specific to
[[info:org#Hyperlinks][hyperlinks (info)]].
7. Listing [[lst:setup-org-mode-map-1]] and [[lst:setup-org-mode-map-2]] extend the =org-mode-map= with useful key-bindings.
8. Listing [[lst:setup-org-src]] facilitates [[info:org#Editing Source Code][editing source code blocks]], and it
provides functions to change the indentation of all =org-src-mode= blocks.
9. Listing [[lst:setup-ob-python]] allows to pretty-print Python session source
block values with [[https://github.com/psf/black#readme][black]] instead of [[https://docs.python.org/3/library/pprint.html][pprint]]. This snippet may break in the
future, because it sets =org-babel-python--def-format-value= which is a
constant declared "private" by two dashes in its name!
10. Listing [[lst:set-org-export-options]] selects the =non-intrusive= expert user
interface for export dispatching.
11. Listing [[lst:setup-org-for-lualatex-export]] and
[[lst:set-ox-latex-options-for-lualatex-export]] configure [[https://orgmode.org/][Org-mode]] to generate
output for the LuaLaTeX compiler.
12. Listing [[lst:setup-org-latex-classes]] defines [[info:org#LaTeXspecificexportsettings][org-latex-classes (info)]] for
backward compatibility. Type {{{kbd(C-h v org-latex-classes)}}} for an
explanation of the code in listing [[lst:setup-org-latex-classes]].
#+caption[Set basic =Org= options]:
#+caption: Set basic =Org= options.
#+name: lst:set-org-options
#+begin_src emacs-lisp -n :results silent
(with-eval-after-load 'org
(setopt
org-babel-load-languages
`((calc . t)
(dot . ,(fboundp 'grapviz-dot-mode))
(emacs-lisp . t)
(eshell . t)
(fortran . t)
(gnuplot . ,(fboundp 'gnuplot-mode))
(js . t)
(latex . t)
(lilypond . ,(fboundp 'lilypond-mode))
(lisp . t)
(lua . ,(fboundp 'lua-mode))
(org . t)
(perl . t)
;; Beware of circular Python dependencies.
(python . t)
(ruby . ,(fboundp 'ruby-mode))
(shell . t))
;; Removal of `svg4css' from `org-export-backends' to silence warnings.
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-use-property-inheritance t))
#+end_src
#+caption[Undo calling =org-ctags-enable= ]:
#+caption: Undo calling =org-ctags-enable=.
#+name: lst:undo-org-ctags
#+begin_src emacs-lisp -n :results silent
;; See also `org-link-search-must-match-exact-headline' for link searches.
;; Undo calling `org-ctags-enable'. For what may cause `org-ctags'
;; loading, see: https://list.orgmode.org/87mt43agk6.fsf@localhost/
(defun org-ctags-disable ()
"Undo calling `org-ctags-enable'."
(setq org-ctags-enabled-p nil)
;; Steal the options list from the `org-ctags-open-link-functions' code.
(setq org-open-link-functions
(seq-difference org-open-link-functions
'(org-ctags-append-topic
org-ctags-ask-append-topic
org-ctags-ask-rebuild-tags-file-then-find-tag
org-ctags-ask-visit-buffer-or-file
org-ctags-fail-silently
org-ctags-find-tag
org-ctags-rebuild-tags-file-then-find-tag
org-ctags-visit-buffer-or-file)))
(put 'org-mode 'find-tag-default-function nil))
(with-eval-after-load 'org-ctags
(org-ctags-disable))
#+end_src
#+caption[Set =org-babel= options]:
#+caption: Set =org-babel= options.
#+name: lst:setup-org-babel
#+begin_src emacs-lisp -n :results silent
(with-eval-after-load 'ob-core
(setopt org-confirm-babel-evaluate nil))
(with-eval-after-load 'ob-latex
(setopt org-babel-latex-preamble
(lambda (_)
"\\documentclass[preview]{standalone}\n")))
(with-eval-after-load 'ob-lisp
;; Default to `sly-eval' whenever feasible:
(when (package-installed-p 'sly)
(setopt org-babel-lisp-eval-fn #'sly-eval)))
#+end_src
#+caption[Fake =org-babel= functions]:
#+caption: Fake =org-babel= functions.
#+name: lst:fake-org-babel-functions
#+begin_src emacs-lisp -n :results silent
;; Modes are not executable:
(defun org-babel-execute:conf (_body _params)
"NO-OP to silence warnings." nil)
(defun org-babel-execute:text (_body _params)
"NO-OP to silence warnings." t)
(defun org-babel-execute:toml (_body _params)
"NO-OP to silence warnings." nil)
;; Sections not exported:
(defun org-babel-execute:applescript (_body _params)
"NO-OP to silence warnings." nil)
#+end_src
#+caption[Change the =org-babel-tangle= destination to a specific directory]:
#+caption: Change the =org-babel-tangle= destination to a specific directory.
#+name: lst:ob-tangle-plus
#+begin_src emacs-lisp -n :results silent
(with-eval-after-load 'emacs
;; Modified from https://emacs.stackexchange.com/a/61364 which replies to
;; https://emacs.stackexchange.com/questions/61359/
;; "How to specify a directory to tangle all code blocks into".
;; Org usage example, add a line: #+property: tangle-dir ./site-lisp/
(defcustom org-babel-post-tangle-dir nil
"Destination directory used by `dir+org-babel-post-tangle'.
Is subject to change by `toggle-post-tangle-hook-dir-usage'."
:group 'org-babel)
(defcustom org-babel-post-tangle-dir-modes '(emacs-lisp-mode)
"Modes for which `dir+org-babel-post-tangle' does what it has to do."
:group 'org-babel)
(defun org-babel-post-tangle-into ()
"Function to hook on `org-babel-post-tangle-hook'."
(when (and org-babel-post-tangle-dir
(derived-mode-p org-babel-post-tangle-dir-modes))
(let ((bfn (buffer-file-name)))
(unless (or (string-suffix-p "init.el" bfn)
(string-suffix-p "dir-locals.el" bfn))
(message "Tangle file `%S' into `%S'"
bfn org-babel-post-tangle-dir)
(rename-file bfn org-babel-post-tangle-dir t)))))
(defun org-babel-toggle-post-tangle-into ()
"Toggle tangling to `org-babel-post-tangle-dir'."
(interactive)
(if (member 'org-babel-post-tangle-into org-babel-post-tangle-hook)
(progn
(setq org-babel-post-tangle-dir nil)
(message "Disable tangling (into `%S')" org-babel-post-tangle-dir)
(remove-hook 'org-babel-post-tangle-hook #'org-babel-post-tangle-into))
(setq org-babel-post-tangle-dir
(expand-file-name (org-entry-get nil "tangle-dir" t)
user-emacs-directory))
(message "Enable tangling (into `%S')" org-babel-post-tangle-dir)
(add-hook 'org-babel-post-tangle-hook
#'org-babel-post-tangle-into))))
#+end_src
#+caption[Set =org-link= options]:
#+caption: Set =org-link= options.
#+name: lst:set-org-link-options
#+begin_src emacs-lisp -n :results silent
(with-eval-after-load 'ol
;; Setting `org-link-descriptive' to `t' has caused worse `isearch'
;; behavior. Set `org-link-descriptive' interactively by calling
;; `org-toggle-link-display'.
(setopt org-link-descriptive t
org-link-file-path-type 'relative
org-link-search-must-match-exact-headline nil))
#+end_src
#+caption[Setup =org-mode-map= 1]:
#+caption: Setup =org-mode-map= 1.
#+name: lst:setup-org-mode-map-1
#+begin_src emacs-lisp -n :results silent
(with-eval-after-load 'emacs
;; From: "Nicolas Richard" <theonewiththeevillook@yahoo.fr>
;; Date: Fri, 08 Mar 2013 16:23:02 +0100 [thread overview]
;; Message-ID: <87vc913oh5.fsf@yahoo.fr> (raw)
(defun org-electric-dollar ()
"When called once, insert \\(\\) and leave point in between.
When called twice, replace the previously inserted \\(\\) by one $."
(interactive)
(if (and (looking-at "\\\\)") (looking-back "\\\\(" (- (point) 2)))
(progn (delete-char 2)
(delete-char -2)
(insert "$"))
(insert "\\(\\)")
(backward-char 2)))
(defun org-active-current-time-stamp ()
"Insert an active date-time stamp of the current time."
(interactive)
(if (fboundp 'org-insert-time-stamp)
(org-insert-time-stamp (current-time) 'with-hm)
(let ((stamp (format-time-string "<%Y-%m-%d %a %H:%M>" (current-time))))
(insert stamp)
stamp)))
(defun org-inactive-current-time-stamp ()
"Insert an inactive date-time stamp of the current time."
(interactive)
(if (fboundp 'org-insert-time-stamp)
(org-insert-time-stamp (current-time) 'with-hm 'inactive)
(let ((stamp (format-time-string "[%Y-%m-%d %a %H:%M]" (current-time))))
(insert stamp)
stamp)))
(with-eval-after-load 'org
(setopt org-return-follows-link t)
(keymap-set org-mode-map "C-c <SPC>" #'org-inactive-current-time-stamp)
(keymap-set org-mode-map "C-c C-<SPC>" #'org-active-current-time-stamp)
(keymap-set org-mode-map "$" #'org-electric-dollar)
(keymap-set org-mode-map "M-q" #'org-fill-paragraph)))
#+end_src
#+caption[Setup =org-mode-map= 2]:
#+caption: Setup =org-mode-map= 2.
#+name: lst:setup-org-mode-map-2
#+begin_src emacs-lisp :results silent
(with-eval-after-load 'emacs
;; Stolen from `org-insert-structure-template'.
;; Note: `org-tempo' does not require `tempo' at all.
(defcustom org-insert-source-block-default '("emacs-lisp -n :results silent"
"python -i -n :results silent"
"org")
"Default value for `org-insert-source-block' (`M-n' and `M-p')."
:group 'org)
(defun org-insert-source-block ()
"Insert a source block #+begin_src/SPEC/BODY/#+end_src.
Prompt for the source block SPEC. With an active region, the
region becomes the block BODY. Otherwise, insert an empty block."
(interactive)
(let* ((spec (read-string "Block spec: " nil nil
org-insert-source-block-default))
(region? (use-region-p))
(region-start (and region? (region-beginning)))
(region-end (and region? (copy-marker (region-end)))))
(when region? (goto-char region-start))
(let ((column (current-indentation)))
(if (save-excursion (skip-chars-backward " \t") (bolp))
(forward-line 0)
(insert "\n"))
(save-excursion
(indent-to column)
(insert (format "#+begin_src %s\n" spec))
(when region?
(org-escape-code-in-region (point) region-end)
(goto-char region-end)
(skip-chars-backward " \n\t\r")
(end-of-line))
(unless (bolp) (insert "\n"))
(indent-to column)
(insert "#+end_src")
(if (looking-at "[ \t]*$") (replace-match "") (insert "\n"))
(when (and (eobp) (not (bolp))) (insert "\n")))
(end-of-line)))))
(with-eval-after-load 'org
(keymap-set org-mode-map "C-c C-;" #'org-insert-source-block))
#+end_src
#+caption[Setup =org-src=]:
#+caption: Setup =org-src=.
#+name: lst:setup-org-src
#+begin_src emacs-lisp -n :results silent
(with-eval-after-load 'emacs
(with-eval-after-load 'org-src
;; https://list.orgmode.org/c3cc4ff1fcfb3bc741df89a3f45be30e@posteo.net/
(setopt org-src-preserve-indentation nil
org-edit-src-content-indentation 0)
(defun org-left-shift-block ()
"Left-shift the block at point using `org-indent-block'."
(interactive)
(let ((org-adapt-indentation nil)
(org-edit-src-content-indentation 0)
(org-src-preserve-indentation nil))
(org-indent-block))))
(defun org-right-shift-block (&optional arg)
"Right-shift the block at point 4 spaces.
With prefix argument ARG, prompt for the number of spaces."
(interactive "P")
(let ((spaces 4)
(start (org-babel-where-is-src-block-head)))
(if (not start) (message "Not at block")
(when arg
(setq spaces (read-number "Number of spaces: ")))
;; The `SUBEXP' of the block body is 5.
(goto-char (match-end 5))
(forward-line -1)
(string-insert-rectangle
(match-beginning 5) (point) (make-string spaces ?\s)))))
(declare-function org-babel-map-src-blocks "ext:ob-core" (file &rest body))
(declare-function org-do-remove-indentation "ext:org-macs" (n skip-fl))
(defun zero-all-org-src-blocks-indentation ()
"Remove the indentation of all `org-src-mode' blocks without `-i' switch."
(interactive)
(when (derived-mode-p 'org-mode)
(save-excursion
(org-babel-map-src-blocks
nil
(unless (or (string-match "-i " switches)
(string-match (rx (or bos bol) graph) body))
(when-let ((new (with-temp-buffer
(insert body)
(org-do-remove-indentation)
(buffer-string))))
(goto-char beg-body)
(delete-region beg-body end-body)
(insert new))))))))
#+end_src
#+caption[Setup =ob-python=]:
#+caption: Setup =ob-python=. This snippet may break in the future!
#+name: lst:setup-ob-python
#+begin_src emacs-lisp -n :results silent
(with-eval-after-load 'ob-python
(setq org-babel-python--def-format-value "\
def __org_babel_python_format_value(result, result_file, result_params):
with open(result_file, 'w') as f:
if 'graphics' in result_params:
result.savefig(result_file)
elif 'pp' in result_params:
import black
f.write(black.format_str(repr(result), mode=black.Mode()))
elif 'list' in result_params and isinstance(result, dict):
f.write(str(['{} :: {}'.format(k, v) for k, v in result.items()]))
else:
if not set(result_params).intersection(\
['scalar', 'verbatim', 'raw']):
def dict2table(res):
if isinstance(res, dict):
return [(k, dict2table(v)) for k, v in res.items()]
elif isinstance(res, list) or isinstance(res, tuple):
return [dict2table(x) for x in res]
else:
return res
if 'table' in result_params:
result = dict2table(result)
try:
import pandas
except ImportError:
pass
else:
if isinstance(result, pandas.DataFrame) and 'table' in result_params:
result = [[result.index.name or ''] + list(result.columns)] + \
[None] + [[i] + list(row) for i, row in result.iterrows()]
elif isinstance(result, pandas.Series) and 'table' in result_params:
result = list(result.items())
try:
import numpy
except ImportError:
pass
else:
if isinstance(result, numpy.ndarray):
if 'table' in result_params:
result = result.tolist()
else:
result = repr(result)
f.write(str(result))"))
#+end_src
#+caption[Set =org-export= options]:
#+caption: Set =org-export= options.
#+name: lst:set-org-export-options
#+begin_src emacs-lisp -n :results silent
(with-eval-after-load 'ox
(setopt org-export-dispatch-use-expert-ui t))
#+end_src
#+caption[Setup =org= for export to LuaLaTeX]:
#+caption: Setup =org= for export to LuaLaTeX.
#+name: lst:setup-org-for-lualatex-export
#+begin_src emacs-lisp -n :results silent
(with-eval-after-load 'org
;; https://list.orgmode.org/87o84fd4oi.fsf@posteo.net/
(add-to-list
'org-preview-latex-process-alist
'(luamagick
:programs ("lualatex" "convert")
:description "pdf > png"
:message "you need to install lualatex and imagemagick."
:image-input-type "pdf"
:image-output-type "png"
:image-size-adjust (1.0 . 1.0)
:post-clean nil
:latex-header nil
:latex-compiler ("lualatex -interaction nonstopmode -output-directory %o %f")
:image-converter ("convert -density %D -trim -antialias %f -quality 100 %O")))
(setopt
org-preview-latex-default-process 'luamagick
org-latex-default-packages-alist '(("" "fontspec" t ("lualatex"))
("AUTO" "inputenc" t ("pdflatex"))
("T1" "fontenc" t ("pdflatex"))
("" "graphicx" t)
("" "longtable" nil)
("" "wrapfig" nil)
("" "rotating" nil)
("normalem" "ulem" t)
("" "amsmath" t)
("" "amssymb" t)
("" "capt-of" nil)
("" "hyperref" nil))))
#+end_src
#+caption[Set =ox-latex= options for export to LuaLaTeX]:
#+caption: Set =ox-latex= options for export to LuaLaTeX.
#+name: lst:set-ox-latex-options-for-lualatex-export
#+begin_src emacs-lisp -n :results silent
(when (require 'ox-latex nil 'noerror) ;; To use the safe file local variables!
(setopt
org-latex-compiler "lualatex"
org-latex-compiler-file-string
"%% -*- LaTeX -*-\n%% Intended LaTeX compiler: %s\n"
org-latex-hyperref-template "\\hypersetup{
pdfauthor={%a},
pdftitle={%t},
pdfkeywords={%k},
pdfsubject={%d},
pdfcreator={%c},
pdflang={%L},
citecolor=blue,
colorlinks=true,
filecolor=blue,
hyperfootnotes=false,
linkcolor=blue,
unicode=true,
urlcolor=blue,
}\n"
org-latex-minted-langs '((cc "c++")
(conf "text")
(cperl "perl")
(diff "diff")
(shell-script "bash")
(json "json")
(org "text")
(toml "toml"))
org-latex-minted-options '(("bgcolor" "LightGoldenrodYellow"))
org-latex-logfiles-extensions
(cl-union '("lof" "lot") org-latex-logfiles-extensions :test #'equal)
;; https://tecosaur.github.io/emacs-config/#compiling
org-latex-pdf-process (list (concat "latexmk -f -pdf -%latex"
" -interaction=nonstopmode"
" -shell-escape -outdir=%o %f"))
org-latex-prefer-user-labels t)
(put 'org-latex-toc-command 'safe-local-variable 'stringp))
#+end_src
#+caption[Setup =org-latex-classes= for backwards compatibility]:
#+caption: Setup =org-latex-classes= for backwards compatibility.
#+name: lst:setup-org-latex-classes
#+begin_src emacs-lisp -n :results silent
(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}")))))))
#+end_src
*** Org HTML style default
:PROPERTIES:
:CUSTOM_ID: sec:org-html-style-default
:END:
Set ~org-html-style-default~ option to clean up the selection of languages and
to add =HTML+CSS+JS= for ~mhtml-mode~:
#+begin_src emacs-lisp -n :eval never :results silent
(setopt org-html-style-default "<style>
#content { max-width: 60em; margin: auto; }
.title { text-align: center;
margin-bottom: .2em; }
.subtitle { text-align: center;
font-size: medium;
font-weight: bold;
margin-top:0; }
.todo { font-family: monospace; color: red; }
.done { font-family: monospace; color: green; }
.priority { font-family: monospace; color: orange; }
.tag { background-color: #eee; font-family: monospace;
padding: 2px; font-size: 80%; font-weight: normal; }
.timestamp { color: #bebebe; }
.timestamp-kwd { color: #5f9ea0; }
.org-right { margin-left: auto; margin-right: 0px; text-align: right; }
.org-left { margin-left: 0px; margin-right: auto; text-align: left; }
.org-center { margin-left: auto; margin-right: auto; text-align: center; }
.underline { text-decoration: underline; }
#postamble p, #preamble p { font-size: 90%; margin: .2em; }
p.verse { margin-left: 3%; }
pre {
border: 1px solid #e6e6e6;
border-radius: 3px;
background-color: #f2f2f2;
padding: 8pt;
font-family: monospace;
overflow: auto;
margin: 1.2em;
}
pre.src {
position: relative;
overflow: auto;
}
pre.src:before {
display: none;
position: absolute;
top: -8px;
right: 12px;
padding: 3px;
color: #555;
background-color: #f2f2f299;
}
pre.src:hover:before { display: inline; margin-top: 14px;}
/* Language selection start. */
pre.src-asm:before { content: 'Assembler'; }
pre.src-asymptote:before { content: 'Asymptote'; }
pre.src-awk:before { content: 'Awk'; }
pre.src-C:before { content: 'C'; }
/* pre.src-C++ doesn't work in CSS */
pre.src-calc:before { content: 'Emacs Calc'; }
pre.src-clojure:before { content: 'Clojure'; }
pre.src-conf:before { content: 'Configuration File'; }
pre.src-cpp:before { content: 'C++'; }
pre.src-css:before { content: 'CSS'; }
pre.src-D:before { content: 'D'; }
pre.src-delphi:before { content: 'Delphi'; }
pre.src-ditaa:before { content: 'ditaa'; }
pre.src-dot:before { content: 'Graphviz'; }
pre.src-emacs-lisp:before { content: 'Emacs Lisp'; }
pre.src-fortran:before { content: 'Fortran'; }
pre.src-gnuplot:before { content: 'gnuplot'; }
pre.src-haskell:before { content: 'Haskell'; }
pre.src-html:before { content: 'HTML'; }
pre.src-idl:before { content: 'IDL'; }
pre.src-J:before { content: 'J'; }
pre.src-java:before { content: 'Java'; }
pre.src-js:before { content: 'Javascript'; }
pre.src-latex:before { content: 'LaTeX'; }
pre.src-ledger:before { content: 'Ledger'; }
pre.src-lisp:before { content: 'Lisp'; }
pre.src-lilypond:before { content: 'Lilypond'; }
pre.src-lua:before { content: 'Lua'; }
pre.src-makefile:before { content: 'Makefile'; }
pre.src-matlab:before { content: 'MATLAB'; }
pre.src-maxima:before { content: 'Maxima'; }
pre.src-mercury:before { content: 'Mercury'; }
pre.src-metapost:before { content: 'MetaPost'; }
pre.src-mhtml:before { content: 'HTML+CSS+JS'; }
pre.src-modula-2:before { content: 'Modula-2'; }
pre.src-nxml:before { content: 'XML'; }
pre.src-ocaml:before { content: 'Objective Caml'; }
pre.src-octave:before { content: 'Octave'; }
pre.src-org:before { content: 'Org mode'; }
pre.src-pascal:before { content: 'Pascal'; }
pre.src-perl:before { content: 'Perl'; }
pre.src-plain-tex:before { content: 'Plain TeX'; }
pre.src-plantuml:before { content: 'Plantuml'; }
pre.src-processing:before { content: 'Processing.js'; }
pre.src-prolog:before { content: 'Prolog'; }
pre.src-ps:before { content: 'PostScript'; }
pre.src-python:before { content: 'Python'; }
pre.src-R:before { content: 'R'; }
pre.src-ruby:before { content: 'Ruby'; }
pre.src-sass:before { content: 'Sass'; }
pre.src-scala:before { content: 'Scala'; }
pre.src-scheme:before { content: 'Scheme'; }
pre.src-screen:before { content: 'Gnu Screen'; }
pre.src-sed:before { content: 'Sed'; }
pre.src-sh:before { content: 'shell'; }
pre.src-simula:before { content: 'Simula'; }
pre.src-sql:before { content: 'SQL'; }
pre.src-sqlite:before { content: 'SQLite'; }
pre.src-tcl:before { content: 'tcl'; }
pre.src-tex:before { content: 'TeX'; }
pre.src-verilog:before { content: 'Verilog'; }
pre.src-vhdl:before { content: 'VHDL'; }
pre.src-xml:before { content: 'XML'; }
/* ob-shell and sub-shells. */
pre.src-shell:before { content: 'Shell Script'; }
pre.src-bash:before { content: 'bash'; }
pre.src-csh:before { content: 'csh'; }
pre.src-ash:before { content: 'ash'; }
pre.src-dash:before { content: 'dash'; }
pre.src-ksh:before { content: 'ksh'; }
pre.src-mksh:before { content: 'mksh'; }
pre.src-posh:before { content: 'posh'; }
pre.src-zsh:before { content: 'zsh'; }
/* Language selection end. */
table { border-collapse:collapse; }
caption.t-above { caption-side: top; }
caption.t-bottom { caption-side: bottom; }
td, th { vertical-align:top; }
th.org-right { text-align: center; }
th.org-left { text-align: center; }
th.org-center { text-align: center; }
td.org-right { text-align: right; }
td.org-left { text-align: left; }
td.org-center { text-align: center; }
dt { font-weight: bold; }
.footpara { display: inline; }
.footdef { margin-bottom: 1em; }
.figure { padding: 1em; }
.figure p { text-align: center; }
.equation-container {
display: table;
text-align: center;
width: 100%;
}
.equation {
vertical-align: middle;
}
.equation-label {
display: table-cell;
text-align: right;
vertical-align: middle;
}
.inlinetask {
padding: 10px;
border: 2px solid gray;
margin: 10px;
background: #ffffcc;
}
#org-div-home-and-up
{ text-align: right; font-size: 70%; white-space: nowrap; }
textarea { overflow-x: auto; }
.linenr { font-size: smaller }
.code-highlighted { background-color: #ffff00; }
.org-info-js_info-navigation { border-style: none; }
#org-info-js_console-label
{ font-size: 10px; font-weight: bold; white-space: nowrap; }
.org-info-js_search-highlight
{ background-color: #ffff00; color: #000000; font-weight: bold; }
.org-svg { }
</style>"
)
#+end_src
*** [[info:org#In-buffer Settings][Buffer properties]]
:PROPERTIES:
:CUSTOM_ID: sec:buffer-properties
:END:
#+caption[Buffer properties]:
#+caption: Buffer properties.
#+name: lst:buffer-properties
#+begin_src emacs-lisp -n :results silent
(with-eval-after-load 'org
(defconst prop-comments-link-re
"\\( :comments link$\\)\\|\\( *$\\)"
"Regexp to find \"#+property:\" \":comments link\".")
(defconst prop-ha-re-template
(format "\\(?:^[[:blank:]]*#\\+%s:[[:blank:]]+%s:%s\\+*\\)+"
"property" "header-args" "%s")
"Regexp template to find `#+property:' header-args:any-language.")
(defconst prop-emacs-lisp-ha-re
(format prop-ha-re-template "emacs-lisp")
"Regexp to find `#+property:' Emacs Lisp header arguments.")
(defconst prop-python-ha-re
(format prop-ha-re-template "python")
"Regexp to find `#+property:' Python header arguments.")
(defun prop-python-link-comments-toggle ()
(interactive)
(save-excursion
(goto-char (point-min))
(if (re-search-forward prop-python-ha-re nil 'noerror)
(if (re-search-forward prop-comments-link-re nil t)
(if (match-string 1)
(replace-match " ")
(replace-match " :comments link"))
(user-error "Found no python link comment"))
(user-error "Found no python header arguments property")))
(move-beginning-of-line 1)
(org-ctrl-c-ctrl-c))
(defun org-get-buffer-properties ()
"Get all properties in buffer."
(org-element-map (org-element-parse-buffer) 'keyword
(lambda (el)
(and ;; Use (upcase "property"), else it fails.
(string= (org-element-property :key el) "PROPERTY")
(let* ((strings (split-string (org-element-property :value el)))
(value (string-join (cdr strings) " "))
(name (car strings)))
(cons name value)))))))
#+end_src
*** [[info:org#Column View][Column View (info)]] and [[info:org#Property Syntax][Property Syntax (info)]]
:PROPERTIES:
:CUSTOM_ID: sec:using-properties
:END:
Listing [[lst:column-view-demonstration][column view demonstration]] tangles to [[./column-view-demonstration.org][column-view-demonstration.org]] which
complements the [[https://orgmode.org/worg/org-tutorials/org-column-view-tutorial.html][Org view tutorial]] and the [[https://orgmode.org/worg/org-tutorials/org-column-screencast.html][Org view screencast]]. Listing [[lst:property-syntax-demonstration][property
syntax demonstration]] tangles to [[./property-syntax-demonstration.org][property-syntax-demonstration.org]] which
complements [[info:org#Property Syntax][property syntax (info)]].
#+caption[Column view demonstration]:
#+caption: Column view demonstration.
#+name: lst:column-view-demonstration
#+begin_src org -n :tangle column-view-demonstration.org
,#+TITLE: Column View Demonstration
,* Column view use
1. Type "M-x org-columns" in the "Columns view top" tree to turn on column view.
2. Press "tab" to (un-)fold the "Example 1", "Example 2", and "Example 3" rows.
3. Move point to the "OK?" cell of the "Example 1" row and type "e" to edit it.
- This changes the "Columns view top" row "OK?" field value.
4. Move point to the "DURATION" cell and type "e"" to edit it.
- This changes the "Columns view top" row "DURATION" field value.
5. Type "q" on one of the column view rows to turn off column view.
,* Column view top
SCHEDULED: <2023-06-30 Fri>
:PROPERTIES:
:COLUMNS: %17ITEM %OK(OK?){X} %OWNER %STATUS %DURATION{:} %TODO %16SCHEDULED
:OWNER_ALL: Ada Bob Carl
:STATUS_ALL: "Shopping" "Cooking" "Eating"
:OK_ALL: "[ ]" "[X]"
:END:
,** DONE Example 1
:PROPERTIES:
:OWNER: Ada
:STATUS: Shopping
:DURATION: 1:00
:OK: [X]
:END:
,** DONE Example 2
:PROPERTIES:
:OWNER: Bob
:STATUS: Cooking
:DURATION: 1:00
:OK: [X]
:END:
,** TODO Example 3
:PROPERTIES:
:OWNER: Carl
:STATUS: Eating
:DURATION: 0:00
:OK: [ ]
:END:
#+end_src
#+caption[Property syntax demonstration]:
#+caption: Property syntax demonstration.
#+name: lst:property-syntax-demonstration
#+begin_src org -n :tangle property-syntax-demonstration.org
,#+TITLE: Property Syntax Demonstration
,* Column view use
1. Type "M-x org-columns" in the "CD collection" tree to turn on column view.
- The column view appends the value of ":GENRES+:" to that of ":GENRES:".
2. Type "q" on one of the column view rows to turn off column view.
,* Dynamic block use
1. Type "M-x org-insert-columns-dblock" to capture a column view table.
- Type headline title of the tree to capture
- The block appends the value of ":GENRES+:" to that of ":GENRES:".
,* CD collection
:PROPERTIES:
:NDISK_ALL: 1 2 3 4
:PUBLISHER_ALL: "Deutsche Grammophon" Philips EMI
:COLUMNS: %TITLE %ARTIST %GENRES %NDISK(N) %PUBLISHER
:ID: CD collection
:END:
,** Classic
:PROPERTIES:
:GENRES: Classic
:END:
,*** Goldberg Variations
:PROPERTIES:
:TITLE: Goldberg Variations
:COMPOSER: J.S. Bach
:ARTIST: G. Gould
:GENRES+: Baroque
:NDISK: 1
:PUBLISHER: Deutsche Grammophon
:END:
,#+NAME: CD collection
,#+BEGIN: columnview :hlines 1 :id "CD collection"
| TITLE | ARTIST | GENRES | N | PUBLISHER |
|---------------------+----------+-----------------+---+---------------------|
| | | | | |
| | | Classic | | |
| Goldberg Variations | G. Gould | Classic Baroque | 1 | Deutsche Grammophon |
,#+END:
#+end_src
*** Org introspection
:PROPERTIES:
:CUSTOM_ID: sec:org-introspection
:END:
#+caption[Find valid entries for =org-babel-load-languages=]:
#+caption: Find valid entries for =org-babel-load-languages=.
#+name: lst:valid-org-babel-load-languages-entries
#+header: :wrap "src emacs-lisp -n :eval never :tangle no"
#+begin_src emacs-lisp -n :exports both :results value pp :tangle no
;; "Search failed:" with paren mismatching problems start here,
;; but the problems are not due to this source block.
(defun all-org-babel-execute-fns ()
"Find `ob-LANGUAGE' files in Org defining `org-babel-execute:LANGUAGE'.
Return a list of items where the filename is the `car' of each item and the
`cdr' of each item lists the `org-babel-execute:LANGUAGE' functions."
(let* ((dir (file-name-parent-directory (locate-library "org")))
(names (directory-files dir t (rx "ob-" (+ print) ".el" eos))))
(cl-loop for name in names
for found = (has-org-babel-execute-fn name)
when found collect found)))
(defun has-org-babel-execute-fn (name)
(let* ((buffer (find-file-noselect name))
(base (file-name-base (buffer-file-name buffer)))
(regexp (rx "defun" (+ blank) "org-babel-execute:" (group (+ graphic))))
(matches))
(save-match-data
(save-excursion
(with-current-buffer buffer
(save-restriction
(widen)
(goto-char 1)
(while (re-search-forward regexp nil t 1)
(push (match-string-no-properties 1) matches))))))
(when matches
`(,base ,@matches))))
(all-org-babel-execute-fns)
#+end_src
#+caption[Org Babel libraries with =org-babel-execute:language= functions]:
#+caption: Org Babel libraries with =org-babel-execute:language= functions.
#+RESULTS: lst:valid-org-babel-load-languages-entries
#+begin_src emacs-lisp -n :eval never :tangle no
(("ob-C" "C" "D" "C++" "cpp") ("ob-R" "R") ("ob-awk" "awk")
("ob-calc" "calc") ("ob-clojure" "clojurescript" "clojure")
("ob-css" "css") ("ob-ditaa" "ditaa") ("ob-dot" "dot")
("ob-emacs-lisp" "emacs-lisp") ("ob-eshell" "eshell")
("ob-forth" "forth") ("ob-fortran" "fortran")
("ob-gnuplot" "gnuplot") ("ob-groovy" "groovy")
("ob-haskell" "haskell") ("ob-java" "java") ("ob-js" "js")
("ob-julia" "julia") ("ob-latex" "latex") ("ob-lilypond" "lilypond")
("ob-lisp" "lisp") ("ob-lua" "lua") ("ob-makefile" "makefile")
("ob-maxima" "maxima") ("ob-ocaml" "ocaml")
("ob-octave" "octave" "matlab") ("ob-org" "org") ("ob-perl" "perl")
("ob-plantuml" "plantuml") ("ob-processing" "processing")
("ob-python" "python") ("ob-ruby" "ruby") ("ob-sass" "sass")
("ob-scheme" "scheme") ("ob-screen" "screen") ("ob-sed" "sed")
("ob-shell" "shell") ("ob-sql" "sql") ("ob-sqlite" "sqlite"))
#+end_src
#+caption[Find active Org Babel languages]:
#+caption: Find active Org Babel languages.
#+name: lst:org-babel-active-languages
#+header: :wrap "src emacs-lisp -n :eval never :tangle no"
#+begin_src emacs-lisp -n :exports both :results value pp :tangle no
(defun org-babel-active-languages ()
(let (result)
(mapatoms
(lambda (x)
(when (and
(string-prefix-p "org-babel-execute:" (symbol-name x))
;; Get rid of all sub-modes in `ob-shell':
(not (eq 'org-babel-shell-initialize (get x 'definition-name)))
;; Get rid of aliases:
(not (eq 'symbol (type-of (symbol-function x)))))
(when (symbol-file x)
(push (string-remove-prefix "org-babel-execute:" (symbol-name x))
result)))))
result))
(mapcar #'list (cl-sort (org-babel-active-languages) #'string<))
#+end_src
#+caption[Active Org Babel languages]:
#+caption: Active Org Babel languages.
#+RESULTS: lst:org-babel-active-languages
#+begin_src emacs-lisp -n :eval never :tangle no
(("applescript") ("calc") ("conf") ("emacs-lisp") ("eshell")
("fortran") ("js") ("latex") ("latex-extra-header") ("latex-header")
("lisp") ("org") ("perl") ("python") ("ruby") ("shell") ("text")
("toml"))
#+end_src
*** [[https://github.com/bdarcus/citar][Citar: citing bibliography]] with [[https://orgmode.org/][Org Mode]]
:PROPERTIES:
:CUSTOM_ID: sec:citing-bibliography
:END:
[[https://github.com/bdarcus/citar][Citar]] is a completing-read front-end to browse and act on BibTeX, BibLaTeX, as
well as CSL JSON bibliographic data with LaTeX, markdown, and org-cite editing
support. In combination with vertico, embark, marginalia, and consult, [[https://github.com/bdarcus/citar][Citar]]
provides quick filtering and selecting of bibliographic entries from the
minibuffer as well as the option to run different commands on those
selections. The article [[https://kristofferbalintona.me/posts/202206141852/][Citations in org-mode: Org-cite and Citar]] tries to walk
you from understanding the general context (bibliography producer, text
processor, text to product converter) to an Emacs setup. Listing
[[lst:set-oc+citar-options]] shows the =oc= and =citar= setup with binding of
{{{kbd(C-c b)}}} to src_emacs-lisp{(call-interactively 'org-cite-insert)} in
=org-mode-map=.
#+caption[Set =oc= and =citar= options]:
#+caption: Set =oc= and =citar= options.
#+name: lst:set-oc+citar-options
#+begin_src emacs-lisp -n :results silent
(when (ensure-package-installation 'citar 'citar-embark)
(with-eval-after-load 'embark
(when (fboundp 'citar-embark-mode)
(citar-embark-mode +1)))
(with-eval-after-load 'oc
(setopt org-cite-export-processors '((latex biblatex)
(t csl))
org-cite-global-bibliography '("~/VCS/research/refs.bib"))
(if (not (require 'citar nil 'noerror))
(user-error "Fails to require `citar' and to setup `citar' and `oc'")
;; Synchronize the two involved bibliographies.
(setopt citar-bibliography org-cite-global-bibliography
citar-library-file-extensions '("djvu" "pdf")
;; Here are the `.djvu' and `.pdf' files.
citar-library-paths '("~/VCS/research/papers/"))
(setopt org-cite-activate-processor 'citar
org-cite-follow-processor 'citar
org-cite-insert-processor 'citar)
(with-eval-after-load 'org
(keymap-set org-mode-map "C-c b" #'org-cite-insert)))))
#+end_src
Ref. [cite:@Schulte.MCSE.2011.41] shows that [[https://orgmode.org/][Org-mode]] is a simple, plain-text
markup language for hierarchical documents allowing intermingling of data, code,
and prose. It serves as an example link to show how to use [[https://github.com/bdarcus/citar][Citar]] within this
setup, provided that =citar-bibliography= and =citar-library-paths= point to
valid directories and files. In an [[https://orgmode.org/][Org-mode]] buffer this setup allows to:
1. Insert one or more [[https://orgmode.org/][Org-mode]] cite links:
1. Type {{{kbd(C-c b)}}} or {{{kbd(M-x org-cite-insert)}}}.
2. Choose one or multiple citations from the bibliography:
1. by navigating to an item and selecting it with {{{kbd(RET)}}}
2. by repeatingly navigating to an item and preselecting it with
{{{kbd(TAB)}}}. Select all preselected items with {{{kbd(RET)}}}.
2. View an electronic copy of an [[https://orgmode.org/][Org-mode]] cite link:
1. Move point to the [[https://orgmode.org/][Org-mode]] cite link.
2. Type {{{kbd(C-:)}}} or {{{kbd(M-x embark-dwim)}}}.
3. Navigate to the corresponding library file.
4. Select it.
3. Open the DOI of an [[https://orgmode.org/][Org-mode]] cite link:
1. Move point to the [[https://orgmode.org/][Org-mode]] cite link.
2. Type {{{kbd(C-:)}}} or {{{kbd(M-x embark-dwim)}}}.
3. Navigate to the corresponding link.
4. Select it.
4. The following =citar= functions may be useful too:
1. src_emacs-lisp{(call-interactively 'citar-insert-citation)}.
2. src_emacs-lisp[:results silent]{(call-interactively 'citar-open)}.
*** TODO Compare bibtex and biblatex
1. [[https://www.economics.utoronto.ca/osborne/latex/BIBTEX.HTM][Using bibtex: a short guide]]
2. [[https://www.tug.org/texlive//devsrc/Master/texmf-dist/doc/latex/biblatex/biblatex.pdf][The biblatex package]]
3. [[https://github.com/yuchenlin/rebiber][Rebiber: A tool for normalizing bibtex with official info]]
4. [[https://github.com/josephwright/biblatex-phys][A biblatex implementation of the AIP and APS bibliography style]]
#+caption[Delete =Biber= cache when =Biber= fails to make a bibliography]:
#+caption: Delete =Biber= cache when =Biber= fails to make a bibliography.
#+name: lst:delete-biber-cache
#+begin_src emacs-lisp -n :results silent
(with-eval-after-load 'emacs
;; https://tex.stackexchange.com/a/579356 answers
;; "How to solve Biber exiting with error code 2 but no error messages?"
(defun biber-delete-cache ()
"Delete the `Biber' cache to get rid of `Biber' exit code 2."
(interactive)
(cl-destructuring-bind (exit-code output)
(shell-command-with-exit-code "rm" "-rf" "$(biber --cache)")
(if (= 0 exit-code)
(message "%s" (string-trim output))
(error "%s" (string-trim output))))))
#+end_src
*** [[https://github.com/tecosaur/engrave-faces#readme][Engrave Faces]]
:PROPERTIES:
:CUSTOM_ID: sec:engrave-faces
:END:
This package has a front-end that processes fontified buffers and pipes its
output into one of three back-ends for export to LaTeX, HTML and text with ANSI
escape codes. It is an alternative to =minted= in case of LaTeX export or to
=htmlized= in case of HTML export.
The listings below work around one issue with spacing in the =pdf= output
resulting from use of the =engraved= source block back-end during =LaTeX=
export. They increase the quality of the export results of this [[file:README.org][README.org]] and
the user-friendliness of this facility to a level acceptable for my workflow:
1. The default Org export configuration for engraving listings produces =pdf=
output with a non-equidistant interline spacing in floating listings (those
with captions) and equidistant interline spacing in non-floating =tcolorbox=
environments (those without captions). I have traced the non-equidistant
interline spacing back to the =breakable= option of =Code= environments
inside =listing= environments. However, =tcolorbox= requires =breakable= for
listings that do not fit on a page. The filter =org-latex-engraved-source-block-filter= in listing
[[lst:org-latex-engraved-source-block-filter]] and the =org-latex-engraved-preamble= customization in listing
[[lst:smart-latex-engrave-org-source-blocks]] allow to engrave =org-mode= source
blocks to "floating unbreakable with caption" or "non-floating breakable
without caption" LaTeX environments. This is a partial work-around, but part
of my workflow.
2. Listing [[lst:ox-engraved-emacs-lisp-setup]] shows how to use this configuration.
#+caption[Engrave to floating unbreakable or non-floating breakable environments]:
#+caption: Define an =org-export= filter function to engrave =org-src-mode=
#+caption: blocks to floating unbreakable LaTeX environments or non-floating
#+caption: breakable LaTeX environments.
#+name: lst:org-latex-engraved-source-block-filter
#+begin_src emacs-lisp -n :results silent
(ensure-package-installation 'engrave-faces)
(defun org-latex-engraved-source-block-filter (data _backend _info)
"Replace \"Code\" with \"Breakable\" in non-floating DATA environments.
Set `org-latex-engraved-preamble' to define a Breakable (non-floating)
environment and an unbreakable Code (floating) environment."
(unless (string-match "^\\\\DeclareTColorBox\\[\\]{Breakable}"
org-latex-engraved-preamble)
(user-error
"`org-latex-engraved-preamble' defines no `Breakable' environment"))
(when (eq org-latex-src-block-backend 'engraved)
;; Transform only blocks matching at position 0. Therefore, do
;; not transform blocks that are listing environments.
(when (string-match "\\`\\\\begin{Code}\n" data)
(setq data (replace-match "\\begin{Breakable}\n" t 'literal data))
(if (string-match "^\\\\end{Code}\n" data)
(setq data (replace-match "\\end{Breakable}\n" t 'literal data))
(error "Match `^\\\\end{Code}' failure")))))
#+end_src
#+caption[Smart LaTeX engraving of =org-src-mode= blocks]:
#+caption: Smart LaTeX engraving of =org-src-mode= blocks.
#+name: lst:smart-latex-engrave-org-source-blocks
#+begin_src emacs-lisp -n :results silent
(defun smart-latex-engrave-org-source-blocks ()
"Enable smart LaTeX engraving of `org-src-mode' blocks.
Sets backend and preamble locally to support floating unbreakable LaTeX
environments and non-floating breakable LaTeX environments by means of
`org-latex-engraved-source-block-filter'."
(interactive)
(when (require 'ox-latex nil 'noerror)
(setq-local org-latex-src-block-backend 'engraved
org-latex-engraved-preamble "\\usepackage{fvextra}
[FVEXTRA-SETUP]
% Make code and line numbers normalsize. Make line numbers grey.
\\renewcommand\\theFancyVerbLine{
\\normalsize\\color{black!40!white}\\arabic{FancyVerbLine}}
% Do not rely on an eventual call to `engrave-faces-latex-gen-preamble'.
\\usepackage{xcolor}
\\providecolor{EfD}{HTML}{f7f7f7}
\\providecolor{EFD}{HTML}{28292e}
% To use \\DeclareTColorBox from the tcolorbox package:
\\usepackage[breakable,xparse]{tcolorbox}
% Define a Breakable environment to fontify code outside floats.
\\DeclareTColorBox[]{Breakable}{o}{
colback=EfD, colframe=EFD, colupper=EFD,
fontupper=\\normalsize\\setlength{\\fboxsep}{0pt},
IfNoValueTF={#1}{
boxsep=1pt, arc=1pt, outer arc=1pt, boxrule=0.5pt,
}{
boxsep=1pt, arc=0pt, outer arc=0pt, boxrule=0pt, leftrule=1pt
},
left=2pt, right=2pt, top=1pt, bottom=1pt, breakable
}
% Define an unbreakable Code environment to fontify code inside floats.
\\DeclareTColorBox[]{Code}{o}{
colback=EfD, colframe=EFD, colupper=EFD,
fontupper=\\normalsize\\setlength{\\fboxsep}{0pt},
IfNoValueTF={#1}{
boxsep=1pt, arc=1pt, outer arc=1pt, boxrule=0.5pt
}{
boxsep=1pt, arc=0pt, outer arc=0pt, boxrule=0pt, leftrule=1pt
},
left=2pt, right=2pt, top=1pt, bottom=1pt
}
[LISTINGS-SETUP]")))
#+end_src
#+caption[Emacs setup to use =engrave-faces-latex= in =org-mode= smartly]:
#+caption: Emacs setup to use =engrave-faces-latex= in =org-mode= smartly.
#+name: lst:ox-engraved-emacs-lisp-setup
#+begin_src emacs-lisp -n :exports code :results none :tangle no
(with-eval-after-load 'ox-latex
(make-local-variable 'org-export-filter-src-block-functions)
(add-to-list 'org-export-filter-src-block-functions
'org-latex-engraved-source-block-filter)
(when (fboundp 'smart-latex-engrave-org-source-blocks)
(smart-latex-engrave-org-source-blocks)))
#+end_src
*** [[https://oer.gitlab.io/org-re-reveal/Readme.html][Org-re-reveal]] :noexport:
:PROPERTIES:
:CUSTOM_ID: sec:org-re-reveal
:header-args:emacs-lisp: :tangle no
:END:
I have used the [[https://revealjs.com/installation/#full-setup][recommended full setup method]] to install ~reveal.js~ as an
[[https://oer.gitlab.io/org-re-reveal/Readme.html][Org-re-reveal]] requirement. Listing [[lst:use-org-re-reveal]] provides {{{kbd(M-x
use-org-re-reveal)}}} to setup and load ~org-re-reveal~ taking into account the
expanded file name of the ~reveal.js~ directory.
#+caption[Specify the =reveal.js= location and load =org-re-reveal=]:
#+caption: Specify the =reveal.js= location and load =org-re-reveal=.
#+name: lst:use-org-re-reveal
#+begin_src emacs-lisp -n :results silent
(when (ensure-package-installation 'org-re-reveal)
(defun use-org-re-reveal ()
"Load `org-re-reveal' after setting the \"reveal.js\" root."
(interactive)
(setq org-re-reveal-root
(format "file:///%s" (expand-file-name "~/VCS/reveal.js")))
(require 'org-re-reveal)))
#+end_src
*** [[https://github.com/flexibeast/org-vcard#org-vcard---org-mode-support-for-vcard-export-and-import][Org-mode support for vCard import and export]] :noexport:
:PROPERTIES:
:CUSTOM_ID: sec:org-vcard
:header-args:emacs-lisp: :tangle no
:END:
Import/export =vcf= files (Android phones) from/to Org-mode linear (level 1)
headline lists with the data in property drawers.
#+caption[Install =org-vcard= to read =vcf= files=]:
#+caption: Install =org-vcard= to read =vcf= files=.
#+name: lst:org-vcarc
#+begin_src emacs-lisp -n :results silent
(ensure-package-installation 'org-vcard)
#+end_src
*** [[info:org#Adding Hyperlink Types][Making Org hyperlink types (info)]]
:PROPERTIES:
:CUSTOM_ID: sec:make-org-hyperlink-types
:END:
The listings below implement or reimplement three groups of =org-link= types:
1. Listing [[lst:org-ref-like-org-link-types]] defines =org-link= types for
backwards compatibility with [[https://github.com/jkitchin/org-ref][org-ref]].
2. Listing [[lst:define-org-pdfview-link-type]] uses ideas from the definition of
=docview-org-link= and =doi-org-link= types to define an =pdfview-org-link=
type for use with [[https://github.com/vedang/pdf-tools][pdf-tools]].
3. Listing [[lst:patch-org-info-link-type]] patches the function =org-info-export=
and listing [[lst:emacs-lisp-setup-buffer-local-ol-info]] sets the constants ~org-info-emacs-documents~ and ~org-info-other-documents~ as buffer local
variables in order to export the =info-org-link= types in this document to =html= and LaTeX correctly.
#+caption[Define =org-link= types for backwards compatibility with =org-ref=]:
#+caption: Define =org-link= types for backwards compatibility with =org-ref=.
#+name: lst:org-ref-like-org-link-types
#+begin_src emacs-lisp -n :results silent
(with-eval-after-load 'ol
(org-link-set-parameters "ac*" :export #'org-ref-ac*-export)
(org-link-set-parameters "cite" :export #'org-ref-cite-export)
(org-link-set-parameters "eqref" :export #'org-ref-eqref-export)
(org-link-set-parameters "hyperlink" :export #'org-ref-hyperlink-export)
(org-link-set-parameters "label" :export #'org-ref-label-export)
(org-link-set-parameters "ref" :export #'org-ref-ref-export)
(defun org-ref-ac*-export (path _desc backend _info)
(pcase backend
(`latex (format "\\gls*{%s}" path))
(_ path)))
(defun org-ref-cite-export (path _desc backend _info)
(pcase backend
(`latex (format "\\cite{%s}" path))
(_ path)))
(defun org-ref-eqref-export (path _desc backend _info)
(pcase backend
(`latex (format "\\eqref{%s}" path))
(_ path)))
(defun org-ref-hyperlink-export (path desc backend _info)
(pcase backend
(`latex (format "\\hyperlink{%s}{%s}" path desc))
(_ path)))
(defun org-ref-label-export (path _desc backend _info)
(pcase backend
(`latex (format "\\label{%s}" path))
(_ path)))
(defun org-ref-ref-export (path _desc backend _info)
(pcase backend
(`latex (format "\\ref{%s}" path))
(_ path))))
#+end_src
#+caption[Define an =org-link= type for =pdf-tools=]:
#+caption: Define an =org-link= type for =pdf-tools=.
#+name: lst:define-org-pdfview-link-type
#+begin_src emacs-lisp -n :results silent
(with-eval-after-load 'ol
(autoload 'pdf-view-goto-page
"Go to PAGE in PDF with optional WINDOW to go to PAGE in all windows."
nil t)
(org-link-set-parameters "pdfview"
:follow #'org-pdfview-open
:export #'org-pdfview-export
:store #'org-pdfview-store-link)
(defun org-pdfview-export (link description backend _)
"Export a \"pdfview\" type link.
The paths of the links export as file relative paths in order to
facilate moving single directories or whole directory trees."
(let ((path (if (string-match "\\(.+\\)::.+" link)
(match-string 1 link)
link))
(desc (or description link)))
(when (stringp path)
(setq path (file-relative-name path))
(pcase backend
(`html (format "<a href=\"%s\">%s</a>" path desc))
(`latex (format "\\href{%s}{%s}" path desc))
(`ascii (format "%s (%s)" desc path))
(_ path)))))
(defun org-pdfview-open (link _)
"Open a \"pdfview\" type link."
(string-match "\\(.*?\\)\\(?:::\\([0-9]+\\)\\)?$" link)
(let ((path (match-string 1 link))
(page (and (match-beginning 2)
(string-to-number (match-string 2 link)))))
;; Let Org mode open the file to respect org-link-frame-setup.
(org-open-file path 1)
(when page (pdf-view-goto-page page))))
(defun org-pdfview-store-link ()
"Store a \"pdfview\" type link."
(when (eq major-mode 'pdf-view-mode)
(let* ((path buffer-file-name)
(page (pdf-view-current-page))
(link (concat "pdfview:" path "::" (number-to-string page))))
(org-link-store-props
:type "pdfview"
:link link
:description path)))))
#+end_src
#+caption[Patch =ol-info=]:
#+caption: Patch =ol-info=.
#+name: lst:patch-org-info-link-type
#+begin_src emacs-lisp -n :exports code :results silent
(with-eval-after-load 'ol-info
(defun org-info-export (path desc format)
"Export an info link.
See `org-link-parameters' for details about PATH, DESC and FORMAT."
(let* ((parts (split-string path "#\\|::"))
(manual (car parts))
(node (or (nth 1 parts) "Top")))
(pcase format
(`html
(format "<a href=\"%s#%s\">%s</a>"
(org-info-map-html-url manual)
(org-info--expand-node-name node)
(or desc path)))
(`latex
(format "\\href{%s\\#%s}{%s}"
(org-info-map-html-url manual)
(org-info--expand-node-name node)
(or desc path)))
(`texinfo
(let ((title (or desc "")))
(format "@ref{%s,%s,,%s,}" node title manual)))
(_ nil)))))
#+end_src
#+attr_latex: :options breaklines
#+caption[Patch buffer local =ol-info=]:
#+caption: Patch buffer local =ol-info=.
#+name: lst:emacs-lisp-setup-buffer-local-ol-info
#+begin_src emacs-lisp -n :exports code :results none :tangle no
(with-eval-after-load 'ol-info
(make-variable-buffer-local 'org-info-emacs-documents)
(setq org-info-emacs-documents (delete "org" org-info-emacs-documents))
(make-variable-buffer-local 'org-info-other-documents)
(setq org-info-other-documents
(cl-union
'(("consult" . "https://github.com/minad/consult")
("embark" . "https://github.com/oantolin/embark")
("magit" . "https://magit.vc/manual/magit.html")
("marginalia" . "https://github.com/minad/marginalia")
("org" . "https://orgmode.org/org.html")
("orderless" . "https://github.com/oantolin/orderless")
("sly" . "https://joaotavora.github.io/sly/")
("vertico" . "https://github.com/minad/vertico"))
org-info-other-documents :test #'equal)))
#+end_src
*** [[https://bitspook.in/blog/extending-org-mode-to-handle-youtube-links/][Extending org-mode to handle YouTube links]]
:PROPERTIES:
:CUSTOM_ID: sec:make-org-yt-link-type
:END:
Listing [[lst:define-org-yt-link-type][define org-yt-link type]] implements code to open the link to the
following YouTube video [[yt:eaZUZCzaIgw][Extending org-mode to handle YouTube links]] and the
following [[https://raw.githubusercontent.com/bitspook/spookmax.d/master/readme.org][Emacs setup]].
Opening [[yt:eaZUZCzaIgw][Extending org-mode to handle YouTube links]] may fail due to a bug in the
interface between =mpv= and =yt-dlp=. Listing [[lst:set-emms-options][set EMMS options]] indicates how to
work around this bug at the cost of making the user interface less clean.
However, the link [[yt:48JlgiBpw_I][Absolute Beginner's Guide to Emacs]] works always.
The following posts provide programming information:
1. [[https://developers.google.com/youtube/v3/docs][YouTube API reference]].
2. [[https://developers.google.com/youtube/iframe_api_reference][YouTube Player API reference for iframe embeds]].
#+caption[Define an =org-link= type for =YouTube=]:
#+caption: Define an =org-link= type for =YouTube=.
#+name: lst:define-org-yt-link-type
#+begin_src emacs-lisp -n :results silent
;; https://bitspook.in/blog/extending-org-mode-to-handle-youtube-paths/
(defun org-yt-emms-open (path)
"Open an \"YouTube\" PATH link with `emms' using \"mpv\"."
(let ((url (format "https://www.youtube.com/watch?v=%s" path)))
(require 'emms nil 'noerror)
(emms-add-url url)
(with-current-emms-playlist
(save-excursion
(emms-playlist-last)
(emms-playlist-mode-play-current-track)))))
(defun org-yt-url-browse-open (path)
"Open an \"YouTube\" PATH link with `url-browse'."
(let ((url (format "https://www.youtube.com/watch?v=%s" path)))
(browse-url url)))
(defun org-yt-export (path desc backend _)
"Export an \"YouTube\" PATH link according to DESC and BACKEND."
(pcase backend
(`ascii
(format "[%s] <https://www.youtube.com/watch?v=%s>" desc path))
(`html
(format "<iframe
frameborder=\"0\" width=\"600\" height=\"450\"
src=\"https://www.youtube.com/embed/%s\"
title=\"%s\"
allowfullscreen></iframe>" path desc))
(`latex
(format "\\href{https://www.youtube.com/watch?v=%s}{%s}" path desc))
(_ path)))
(with-eval-after-load 'ol
(org-link-set-parameters
"yt" :follow #'org-yt-emms-open :export #'org-yt-export))
#+end_src
*** [[https://tecosaur.github.io/emacs-config/#translate-capital-keywords][Translate capital keywords (old) to lower case (new)]]
:PROPERTIES:
:CUSTOM_ID: sec:convert-upper-to-lower-case-keywords
:END:
#+caption[Convert upper to lower case keywords]:
#+caption: Convert upper to lower case keywords.
#+name: lst:convert-upper-to-lower-case-keywords
#+begin_src emacs-lisp -n :results silent
(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))))))
#+end_src
*** [[https://lists.gnu.org/archive/html/emacs-orgmode/2016-07/msg00394.html][Evaluate specific source blocks at load-time]]
:PROPERTIES:
:CUSTOM_ID: sec:load-time-specific-source-block-evaluation
:END:
[[https://emacs.stackexchange.com/questions/12938/how-can-i-evaluate-elisp-in-an-orgmode-file-when-it-is-opened][How to do load time source block evaluation]]
#+caption[Evaluate specific source blocks at load-time]:
#+caption: Evaluate specific source blocks at load-time.
#+name: lst:load-time-specific-source-block-evaluation
#+begin_src emacs-lisp -n :results silent
(with-eval-after-load 'emacs
(defun org-eval-named-blocks-with-infix (infix)
"Evaluate all source blocks having INFIX in their name."
(when (eq major-mode 'org-mode)
(let ((blocks
(org-element-map
(org-element-parse-buffer 'greater-element nil) 'src-block
(lambda (block)
(when-let ((name (org-element-property :name block)))
(when (string-match-p infix name) block))))))
(dolist (block blocks)
(goto-char (org-element-property :begin block))
(org-babel-execute-src-block)))))
(defun org-eval-emacs-lisp-setup-blocks ()
"Evaluate all source blocks having \"emacs-lisp-setup\" in their name."
(interactive)
(org-eval-named-blocks-with-infix "emacs-lisp-setup"))
(defun org-eval-python-setup-blocks ()
"Evaluate all source blocks having \"python-setup\" in their name."
(interactive)
(org-eval-named-blocks-with-infix "python-setup"))
;; Emacs looks for "Local variables:" after the last "newline-formfeed".
(add-to-list 'safe-local-eval-forms '(org-eval-emacs-lisp-setup-blocks))
(add-to-list 'safe-local-eval-forms '(org-eval-python-setup-blocks)))
#+end_src
*** [[info:org#Macro Replacement][Org-mode macro utilities]]
:PROPERTIES:
:CUSTOM_ID: sec:org-macro-utilities
:END:
Listing [[lst:by-backend-kbd-org-macro]] defines the Emacs Lisp utilities to
define the [[https://orgmode.org/][Org mode]] =kbd= macro in listing
[[lst:source-file-export-keyword-settings]].
#+attr_latex: :options breaklines
#+caption[Define Emacs Lisp utilities to define the =Org-mode= =kbd= macro]:
#+caption: Define Emacs Lisp utilities to define the =Org-mode= =kbd= macro.
#+name: lst:by-backend-kbd-org-macro
#+begin_src emacs-lisp -n :results silent
(with-eval-after-load 'emacs
(when (ensure-package-installation 'htmlize)
(autoload 'htmlize-protect-string "htmlize" nil t))
;; https://orgmode.org/worg/org-contrib/babel/languages/ob-doc-LaTeX.html
(defmacro by-backend (&rest body)
"Help for org-export backend dependent execution."
`(cl-case ',(bound-and-true-p org-export-current-backend) ,@body))
(defun by-backend-kbd-org-macro (keys)
"Help for an org-export backend dependent \"#+macro: kbd\"."
(by-backend
(ascii (format "`kbd(%s)'" keys))
(html (format "@@html:<kbd>%s</kbd>@@" (htmlize-protect-string keys)))
(latex (format "@@latex:\\colorbox{PowderBlue}{\\texttt{%s}}@@" keys)))))
#+end_src
*** [[https://orgmode.org/worg/org-tests/index.html][Testing Org]]
:PROPERTIES:
:CUSTOM_ID: sec:testing-org
:END:
Listing [[lst:batch-org-testing]] shows the recommended way: use =make test= in a
clean Org git repository. Listing [[lst:setup-org-mode-test-1]] and
[[lst:setup-org-mode-test-2]] provide facilities for [[https://orgmode.org/worg/org-tests/index.html][Testing Org]] interactively.
#+caption[Batch Org testing]:
#+caption: Batch Org testing: obligatory before mailing a patch.
#+name: lst:batch-org-testing
#+begin_src shell -n :eval never :tangle no
make test > out.txt 2>&1
#+end_src
#+caption[Testing Org facilities 1]:
#+caption: Testing Org facilities 1: for use during development only.
#+name: lst:setup-org-mode-test-1
#+begin_src emacs-lisp -n :results silent
(with-eval-after-load 'emacs
(defvar org-testing-dir nil)
(defvar org-testing-lisp-files nil)
(defun setup-org-mode-test ()
"Initialize an `org-mode' test."
(interactive)
(unless (and org-testing-dir org-testing-lisp-files)
(find-library "org.el")
(quit-window)
(setq org-testing-dir
(file-name-concat
(file-name-directory
(directory-file-name
(file-name-directory
(buffer-file-name (get-buffer "org.el")))))
(file-name-as-directory "testing")))
(setq org-testing-lisp-files
(seq-filter
(lambda (f)
(string-suffix-p ".el" f))
(directory-files
(file-name-concat org-testing-dir
(file-name-as-directory "lisp"))))))
(unless (fboundp 'org-test-with-temp-text)
(find-file (file-name-concat org-testing-dir "org-test.el"))
(eval-buffer)
(quit-window))
(let* ((test
(completing-read "Org-mode test: "
org-testing-lisp-files nil 'require-match)))
(find-file (file-name-concat
org-testing-dir (file-name-as-directory "lisp") test))
(eval-buffer)
(message "Evaluated '%s'"
(file-name-nondirectory (buffer-file-name (get-buffer test))))
(quit-window))))
#+end_src
#+caption[Testing Org facilities 2]:
#+caption: Testing Org facilities 2: for use during development only.
#+name: lst:setup-org-mode-test-2
#+begin_src emacs-lisp -n :results silent
(with-eval-after-load 'emacs
;; Stolen from `org-test-run-all-tests'.
(defun org-test-run-some-tests ()
"Run some defined tests.
Load all test files first."
(interactive)
(org-test-touch-all-examples)
(org-test-update-id-locations)
(org-test-load)
(let (;; Catch errors in diary sexps better.
(calendar-debug-sexp t))
;; test-org-element/normalize-contents fails (can't fix).
;; test-ob-exp/noweb-on-export fails due to spacing (can't fix).
;; test-ob-exp/noweb-on-export-with-exports-results due to spacing (idem).
;; ob-python hangs sometimes, but seldom.
;; ob-tangle/continued-code-blocks-w-noweb-ref fails.
(ert (rx (or "org/" "org-fold" "org-element" "org-macro" "org-src"
"property-inheritance"
"ob/" "ob-emacs-lisp" "ob-eshell"
"ob-python" "ob-tangle")))
(org-test-kill-all-examples)))
(defun org-test-run-ob-tests ()
"Run all \"ob\" tests.
Load all test files first."
(interactive)
(org-test-load)
(let (;; Catch errors in diary sexps better.
(calendar-debug-sexp t))
(ert "ob/")))
(defun org-test-run-a-test-file ()
"Run all tests in an Org test file (trailing `/' matters).
Examples: `ob/' or `ob-python'."
(interactive)
(let ((text (read-string "Org test file (without leading test-): "
nil nil "ob-python")))
(org-test-load)
(let (;; Catch errors in diary sexps better.
(calendar-debug-sexp t))
(ert text)))))
#+end_src
*** LaTex preamble editing using [[info:org#Noweb Reference Syntax][Noweb (info)]]
:PROPERTIES:
:CUSTOM_ID: sec:file-inclusion-and-noweb
:END:
Note: [[https://list.orgmode.org/87sfisf31o.fsf@posteo.net/][How to include LaTeX packages in a LaTeX export block]].
Listing [[lst:source-file-export-keyword-settings]] shows the preamble lines of this
[[file:README.org]] file. It lists the export keyword settings, the definition of
the [[https://orgmode.org/][Org mode]] =kbd= macro and the source block that generates part of the LaTeX
preamble. Listing [[lst:latex-header-1]], [[lst:latex-header-2]], [[lst:latex-header-3]],
[[lst:latex-header-4]], and [[lst:latex-header-5]] show the LaTeX source blocks that
contain the LaTeX preamble. All LaTeX listings in this section *can be and are*
handled by [[info:org#Noweb Reference Syntax][noweb (info)]].
#+caption[Source file export keyword settings]:
#+caption: The preamble lines of README.org containing the export keyword
#+caption: settings, the definition of the Org-mode =kbd= macro, and the
#+caption: source block that generates part of the LaTeX preamble.
#+name: lst:source-file-export-keyword-settings
#+include: "README.org" src org -n :lines "1-17"
#+name: latex-header-1
#+begin_src latex -n :exports none
% Begin of the LaTeX preamble:
% See: https://list.orgmode.org/87o807r7fr.fsf@posteo.net/
% From: "Juan Manuel Macías" <maciaschain@posteo.net>
% To: orgmode <emacs-orgmode@gnu.org>
% Subject: [tip] Insert arbitrary LaTeX code at the beginning of any float environment
% Date: Sun, 08 May 2022 22:22:16 +0000
% Message-ID: <87o807r7fr.fsf@posteo.net>
% 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 of the 1st LaTeX header block.
#+end_src
#+caption[LaTeX preamble: replacing the =Org-mode= default packages]:
#+caption: LaTeX preamble: replacing the =Org-mode= default packages.
#+name: lst:latex-header-1
#+begin_src latex -n :exports code :noweb yes
<<latex-header-1>>
#+end_src
#+name: latex-header-2
#+begin_src latex -n :exports none
% 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 of the 2nd LaTeX header block.
#+end_src
#+caption[LaTeX preamble: language, lists and floats]:
#+caption: LaTeX preamble: language, lists and floats.
#+name: lst:latex-header-2
#+begin_src latex -n :exports code :noweb yes
<<latex-header-2>>
#+end_src
#+name: latex-header-3
#+begin_src latex -n :exports none
% PAGE LAYOUT:
\usepackage{fancyhdr}
\usepackage{lastpage}
\usepackage[
headheight=20mm,
top=40mm,
bottom=20mm,
left=0.1\paperwidth,
right=0.1\paperwidth,
heightrounded,
verbose,
]{geometry}
% TECHNICS:
\usepackage{siunitx}
\usepackage{tikz}
% End of the 3rd LaTeX header block.
#+end_src
#+caption[LaTeX preamble: page layout and technics]:
#+caption: LaTeX preamble: page layout.
#+name: lst:latex-header-3
#+begin_src latex -n :exports code :noweb yes
<<latex-header-3>>
#+end_src
#+name: latex-header-4
#+begin_src latex -n :exports none
% 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
}%
}
% End of the 4th LaTeX header block.
#+end_src
#+caption[LaTeX preamble: float barriers]:
#+caption: LaTeX preamble: float barriers.
#+name: lst:latex-header-4
#+begin_src latex -n :exports code :noweb yes
<<latex-header-4>>
#+end_src
#+name: latex-header-5
#+begin_src latex -n :exports none
% FANCY HEADERS AND FOOTERS:
% Add fancy headers and footers to normal pages.
\pagestyle{fancy}
\fancyhf{}
\renewcommand{\footrulewidth}{0.4pt}
\fancyfoot[C]{\emph{
Emacs for \LaTeX{}, Lisp, 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 for \LaTeX{}, Lisp, 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}}
}
% End of the 5th and last LaTeX header block.
#+end_src
#+caption[LaTeX preamble: fancy headers and footers]:
#+caption: LaTeX preamble: fancy headers and footers.
#+name: lst:latex-header-5
#+begin_src latex -n :exports code :noweb yes
<<latex-header-5>>
#+end_src
*** [[info:org#LaTeX header and sectioning][Deprecated LaTeX preamble editing methods]]
:PROPERTIES:
:CUSTOM_ID: sec:easy-latex-preamble-editing
:END:
*NOTE:* This [[info:org#Top][Org (info)]] file uses the recommended method of noweb trickery in
Section [[#sec:file-inclusion-and-noweb]].
There are at least two deprecated (old and historic) ways to edit the Org
=#+latex_header:= and =#+latex_extra_header:= keywords easily
for export to LaTeX, see [[info:org#LaTeX header and sectioning][LaTeX header and sectioning (info)]].
The old way -- exploiting an idea of [[https://www.matem.unam.mx/~omar/][Omar Antolin Camarena]] -- is to code new
[[info:org#Editing Source Code][<LANGUAGE>-modes]] allowing to edit in LaTeX mode and to export to LaTeX code with
[[info:org#LaTeX specific export settings][correct LaTeX preamble export setting prefixes]]. Here, are links to three posts
exposing his idea:
1. [[https://www.reddit.com/r/orgmode/comments/7u2n0h/tip_for_defining_latex_macros_for_use_in_both/][Export LaTeX macros to LaTeX and HTML/MathJax preambles (reddit)]],
2. [[https://www.reddit.com/r/orgmode/comments/5bi6ku/tip_for_exporting_javascript_source_block_to/][Export JavaScript source blocks to script tags in HTML (reddit)]],
3. [[https://emacs.stackexchange.com/questions/28301/export-javascript-source-block-to-script-tag-in-html-when-exporting-org-file-to][Export JavaScript source blocks to script tags in HTML (SX)]].
Listing [[lst:org-babel-latex-header-blocks]] implements this way by means of two
new [[info:org#Editing Source Code][<LANGUAGE>-modes]]: =latex-header= and =latex-extra-header=.
The old way is to define two Org Babel languages (=latex-extra-header= and
=latex-header=) as done in Listing [[lst:org-babel-latex-header-blocks]]. The
historic way is to use a special export attribute as in the function
=org-latex-header-blocks-filter= in [[https://git.sr.ht/~bzg/org-contrib/tree/master/item/lisp/ox-extra.el][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=. This setup keeps the deprecated ways for
backwards compatibility. A practical difference is that new way source blocks
(contrary to old way export blocks) do not work in [[info:org#Export Settings][#+SETUPFILE: <FILE>]], but only
in [[info:org#Export Settings][#+INCLUDE: <FILE>]] files.
#+caption[New =<LANGUAGE>-modes= to edit the LaTeX preamble easily]:
#+caption: Add =latex-header= and =latex-extra-header= language modes to edit
#+caption: LaTeX preamble =latex_header= and =latex_extra_header= export options
#+caption: easily.
#+name: lst:org-babel-latex-header-blocks
#+begin_src emacs-lisp -n :exports code :results silent
(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.
Calling `org-babel-execute-src-block' calls this function 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.
Calling `org-babel-execute-src-block' calls this function and
prefixes all lines with \"#+latex_header: \"."
(prefix-all-lines "#+latex_header: " body))
(defvar org-babel-default-header-args:latex-extra-header
'((:exports . "results") (:results . "raw")))
(defvar org-babel-default-header-args:latex-header
'((:exports . "results") (:results . "raw")))
(with-eval-after-load 'org-src
(setopt org-src-window-setup 'current-window)
(add-to-list 'org-src-lang-modes '("latex-header" . latex))
(add-to-list 'org-src-lang-modes '("latex-extra-header" . latex))))
#+end_src
#+caption[Convert marked LaTeX export blocks to LaTeX header lines]:
#+caption: Convert marked LaTeX export blocks to LaTeX header lines.
#+name: lst:org-latex-header-blocks-filter
#+begin_src emacs-lisp -n :results silent
(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-functions))
#+end_src
*** [[info:org#HTML Export][HTML export (info)]]
:PROPERTIES:
:CUSTOM_ID: sec:html-export
:END:
The code label and link feature in [[info:Org#Literal Examples][Literal Examples (info)]] does not integrate
with LaTeX export.
#+caption[Set HTML export options]:
#+caption: Set HTML export options.
#+name: lst:set-html-export-options
#+begin_src emacs-lisp -n :results silent
(with-eval-after-load 'ox-html
;; (Info-find-node "Org" "Literal Examples")
(setopt org-html-head-include-scripts t)
;; Force `org-index-capture' loading after lazy `ox-html' loading.
(require 'org-index-capture nil 'noerror))
#+end_src
*** [[info:org#LaTeX specific export settings][Advanced LaTeX export settings]]
:PROPERTIES:
:CUSTOM_ID: sec:advanced-latex-export-settings
:END:
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 [[lst/title-page]] is a template to initialize
=org-latex-title-command=. Type {{{kbd(C-h v org-latex-classes)}}}, {{{kbd(C-h
v org-latex-subtitle-format)}}}, {{{kbd(C-h v org-latex-title-command)}}}, and
{{{kbd(C-h v org-latex-toc-command)}}} to read how those variables control
exporting from Org-mode to LaTeX.
#+caption[Define buffer local =ox-latex= variables]:
#+caption: Define buffer local =ox-latex= variables.
#+header: :var title-page=lst/title-page
#+name: lst:ox-latex-emacs-lisp-setup
#+begin_src emacs-lisp -n :exports code :results none :tangle no
(when (require 'ox-latex nil 'noerror)
;; https://emacs.stackexchange.com/questions/47347/
;; customizing-org-latex-title-command-to-edit-title-page
(make-local-variable '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)
(setq-local org-latex-title-command (concat title-page))
(setq-local org-latex-toc-command "
\\tableofcontents\\label{toc}
\\listoflistings
\\listoftables
\\newpage
")
(setq-local org-latex-subtitle-format ""))
#+end_src
#+caption[Show a title-page example for =org-latex-title-command=]:
#+caption: Show a title-page example for =org-latex-title-command=.
#+name: lst/title-page
#+begin_src latex -n :exports code :results silent :tangle no
\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}
#+end_src
*** [[https://orgmode.org/worg/dev/org-syntax-edited.html][Org Syntax]]
:PROPERTIES:
:CUSTOM_ID: sec:org-syntax
:END:
Two tools to grok how [[https://orgmode.org/worg/dev/org-element-api.html][Org mode parsing]] works are the [[https://orgmode.org/worg/dev/org-syntax-edited.html][Org Syntax]] specification
and the [[http://xahlee.info/emacs/emacs/elisp_parse_org_mode.html][Org mode parser tutorial]]. The [[https://orgmode.org/worg/dev/org-element-api.html][Org element parsing API]] boils down to three
functions:
1. The function [[https://orgmode.org/worg/dev/org-element-api.html#global][~org-element-parse-buffer~]] implements a fully recursive buffer
parser that returns an abstract syntax tree.
2. The functions [[https://orgmode.org/worg/dev/org-element-api.html#local][~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 [[http://xahlee.info/emacs/emacs/elisp_parse_org_mode.html][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.
#+caption[Grok how =org-element= parses your document]:
#+caption: Grok how =org-element= parses your document.
#+name: lst:grok-org-element-tree
#+begin_src emacs-lisp -n :results silent
(with-eval-after-load 'org-element
(when (require 'pp nil 'noerror)
(defconst grok-org-output
"*Grok Org Element Output*"
"Grok Org output buffer name.")
(defun grok-org-element-at-point ()
"Call `org-element-at-point' interactively and pretty-print."
(interactive)
(pp-display-expression
(org-element-at-point) grok-org-output))
(defun grok-org-element-context ()
"Call `org-element-context' interactively and pretty-print."
(interactive)
(pp-display-expression
(org-element-context) grok-org-output))
(defun grok-org-element-parse-buffer ()
"Call `org-element-parse-buffer' interactively and pretty-print."
(interactive)
(let ((what (completing-read
"granularity: "
'(headline element greater-element object)
nil 'require-match)))
(pp-display-expression
(org-element-parse-buffer what) grok-org-output)))
(defun grok-org-element-parse-whole-buffer ()
"Like `grok-org-element-parse-buffer' from point 1 and sans granularity."
(interactive)
(org-with-point-at 1
(pp-display-expression (org-element-parse-buffer) grok-org-output)))
(defun grok-org-heading-components ()
"Call `org-heading-components' interactively and pretty-print."
(interactive)
(pp-display-expression
(org-heading-components) grok-org-output))
(defun grok-org-element-lineage ()
"Call `org-element-lineage' interactively and pretty-print."
(interactive)
(org-load-modules-maybe)
(pp-display-expression
(org-element-lineage (org-element-context) nil t) grok-org-output))))
#+end_src
** Grammar, spelling, and style tools
:PROPERTIES:
:CUSTOM_ID: sec:writing-tools
:END:
*** [[info:emacs#Abbrevs][Abbrevs (info)]]
:PROPERTIES:
:CUSTOM_ID: sec:writing-abbreviations
:END:
[[https://www.masteringemacs.org/][Mickey Peterson]] has posted [[https://www.masteringemacs.org/article/correcting-typos-misspellings-abbrev][Correcting Typos and Misspellings with Abbrev]] showing
how to use [[info:emacs#Keyboard Macros][Keyboard Macros (info)]] to exploit [[https://en.wikipedia.org/wiki/Wikipedia:Lists_of_common_misspellings/For_machines][Wikipedia's list of common
misspellings for machines]]. Listing [[lst:misspellings-abbrev]] defines his keyboard
macro under the name =misspellings-abrev=. I have used those directions to
define src_emacs-lisp[:results silent]{(describe-variable 'global-abbrev-table)}
and to store it in src_emacs-lisp[:results silent]{(describe-variable
'abbrev-file-name)} to manage its changes with git. I can change the
abbreviation definitions in this file by means of:
1. Execute src_emacs-lisp[:results silent]{(edit-abbrevs)} to alter abbreviation
definitions by editing an =*Abbrevs*= buffer.
2. Add, edit, or remove definitions of the form ="source" 1 "target"= under the
global or a mode-specific table.
3. Execute src_emacs-lisp{(abbrev-edit-save-buffer)} to save all user
abbreviation definitions in the current buffer.
#+caption[Definition of the =misspellings-abbrev= keyboard macro]:
#+caption: Definition of the =misspellings-abbrev= keyboard macro.
#+name: lst:misspellings-abbrev
#+begin_src emacs-lisp -n :results silent
(with-eval-after-load 'emacs
(defun browse-common-misspellings ()
"Open the Wikipedia page of common misspellings for machines in EWW."
(interactive)
(eww (concat "https://en.wikipedia.org/wiki/Wikipedia"
":Lists_of_common_misspellings/For_machines")))
(fset 'misspellings-abbrev
(kmacro-lambda-form
[?\C-s ?- ?> return backspace backspace ?\C-k ?\C-x ?a ?i ?g ?\C-y return]
0 "%d"))
(setq-default abbrev-mode t))
#+end_src
Listing [[lst:word-games]] defines the =anagram-p= function that might be used games.
#+caption[Word games]:
#+caption: Word games.
#+name: lst:word-games
#+begin_src emacs-lisp -n :results silent
;; https://funcall.blogspot.com/2022/07/lets-play-wordle.html
;; https://github.com/tabatkins/wordle-list
(defun anagram-p (evil vile)
"Check whether strings EVIL and VILE are anagrams of each other."
(if (string= evil vile)
nil
(string= (cl-sort evil #'<) (cl-sort vile #'<))))
;; https://funcall.blogspot.com/2020/01/palindromes-redux-and-sufficiently.html
;; https://irreal.org/blog/?p=8570
(defun palindrome-p (word)
"Check whether the string WORD is a palindrome."
(cl-do ((head 0 (1+ head))
(tail (1- (length word)) (1- tail))
(ok t))
((or (not ok) (>= head tail) ok)
(setq ok (= (aref word head) (aref word tail))))))
#+end_src
*** TODO [[https://dict.org/bin/Dict][DICT.org]] local server fails on Darwin
:PROPERTIES:
:CUSTOM_ID: sec:writing-dict
:END:
Evaluating src_emacs-lisp{(dictionary)} connects to a local or remote =dictd=
server. The following links explain how to configure and use =dictd=:
1. [[https://jpmens.net/2020/03/08/looking-up-words-with-dict/][Looking up words with DICT]]
2. [[https://www.rfc-editor.org/rfc/rfc2229.html][RFC 2229: A Dictionary Server Protocol]]
3. [[https://www.masteringemacs.org/article/wordsmithing-in-emacs][Wordsmithing in Emacs]]
I am using the following Debian Bullseye dictionaries on Darwin and Gentoo:
1. [[http://ftp.fr.debian.org/debian/pool/main/d/dict-devil/dict-devil_1.0-13.1_all.deb][dict-devil_1.0-13.1_all.deb]]
2. [[http://ftp.fr.debian.org/debian/pool/main/d/dict-foldoc/dict-foldoc_20201018-1_all.deb][dict-foldoc_20201018-1_all.deb]]
3. [[http://ftp.fr.debian.org/debian/pool/main/d/dict-gcide/dict-gcide_0.48.5+nmu1_all.deb][dict-gcide_0.48.5+nmu1_all.deb]]
4. [[http://ftp.fr.debian.org/debian/pool/main/d/dict-jargon/dict-jargon_4.4.7-3.1_all.deb][dict-jargon_4.4.7-3.1_all.deb]]
5. [[http://ftp.fr.debian.org/debian/pool/main/v/vera/dict-vera_1.24-1_all.deb][dict-vera_1.24-1_all.deb]]
6. [[http://ftp.fr.debian.org/debian/pool/main/w/wordnet/dict-wn_3.0-36_all.deb][dict-wn_3.0-36_all.deb]]
#+caption[Resource file for =dict= on =Darwin=]:
#+caption: Resource file for =dict= on =Darwin=.
#+name: lst:darwin-dict-resource-file
#+header: :tangle (if (eq 'darwin system-type) "~/.dictrc" "no")
#+begin_src conf -n
# https://jpmens.net/2020/03/08/looking-up-words-with-dict/
server 127.0.0.1 {
port 2628
}
# Local Variables:
# mode: conf-unix
# End:
#+end_src
#+caption[Make a configuration file for =dictd= on =Darwin=]:
#+caption: Make a configuration file for =dictd= on =Darwin=.
#+name: lst:dictd-configuration-file
#+begin_src shell -n :eval (if (eq 'darwin system-type) "yes" "never") :results silent
cat > ~/.dictd.conf <<EOF
# https://www.masteringemacs.org/article/wordsmithing-in-emacs
database devil {
data "${HOME}/.local/share/dictd/devil.dict.dz"
index "${HOME}/.local/share/dictd/devil.index"
}
database foldoc {
data "${HOME}/.local/share/dictd/foldoc.dict.dz"
index "${HOME}/.local/share/dictd/foldoc.index"
}
database gcide {
data "${HOME}/.local/share/dictd/gcide.dict.dz"
index "${HOME}/.local/share/dictd/gcide.index"
}
database jargon {
data "${HOME}/.local/share/dictd/jargon.dict.dz"
index "${HOME}/.local/share/dictd/jargon.index"
}
database vera {
data "${HOME}/.local/share/dictd/vera.dict.dz"
index "${HOME}/.local/share/dictd/vera.index"
}
database wn {
data "${HOME}/.local/share/dictd/wn.dict.dz"
index "${HOME}/.local/share/dictd/wn.index"
}
EOF
#+end_src
#+caption[Run =dictd= in debug mode on =Darwin=]:
#+caption: Run =dictd= in debug mode on =Darwin=.
#+name: lst:darwin-dictd-launch
#+begin_src shell -n :eval never :tangle no
# https://jpmens.net/2020/03/08/looking-up-words-with-dict/
/usr/local/sbin/dictd \
--config .dictd.conf \
--verbose \
--logfile .dictd.log \
-d nodetach
#+end_src
*** [[https://github.com/tecosaur/lexic#readme][Emacs LEXICal information viewer]] :noexport:
:PROPERTIES:
:CUSTOM_ID: sec:writing-lexic
:header-args:emacs-lisp: :tangle no
:END:
The [[https://github.com/tecosaur/lexic#readme][Emacs LEXICal information viewer]] is a front-end for offline dictionary,
etymology, or thesaurus back-ends (currently limited to [[https://en.wikipedia.org/wiki/Sdcv][sdcv]]). Listing
[[lst:configure-lexic]] configures [[https://github.com/tecosaur/lexic#readme][lexic]].
#+attr_latex: :options breaklines
#+caption[Configure =lexic=]:
#+caption: Configure =lexic=.
#+name: lst:configure-lexic
#+begin_src emacs-lisp -n :results silent
(when (ensure-package-installation 'lexic)
;; http://download.huzheng.org/fr/
;; https://polyglotte.tuxfamily.org/doku.php?id=donnees:dicos_bilingues
;; http://download.huzheng.org/dict.org/stardict-dictd-web1913-2.4.2.tar.bz2
;; http://download.huzheng.org/dict.org/stardict-dictd_www.dict.org_gcide-2.4.2.tar.bz2
(with-eval-after-load 'lexic
(if-let ((lpp (executable-find "sdcv")))
(setopt
lexic-program-path lpp
lexic-dictionary-alist '(("full" . t))
;; sdcv --data-dir .local/share -l
lexic-dictionary-list '("Webster's Revised Unabridged Dictionary (1913)"
"dictd_www.dict.org_gcide")
lexic-dictionary-path "~/.local/share")
(message "`lexic' fails to find the `sdcv' executable"))))
#+end_src
*** [[https://wordnet.princeton.edu/][Wordnet]] :noexport:
:PROPERTIES:
:CUSTOM_ID: sec:writing-wordnet
:END:
The [[https://github.com/gromnitsky/wordnut#readme][wordnut]] package is a major mode interface to [[https://wordnet.princeton.edu/][Wordnet]], a lexical database for
the English language. Listing [[lst:check-wordnut]] checks whether the system meets
the [[https://github.com/gromnitsky/wordnut#readme][wordnut]] prerequisites.
#+caption[System check for =wordnut=]:
#+caption: System check for =wordnut=.
#+name: lst:check-wordnut
#+begin_src emacs-lisp -n :results silent :tangle no
(when (ensure-package-installation 'wordnut)
(with-eval-after-load 'wordnut
(if-let ((wn (executable-find "wn")))
(setopt wordnut-cmd wn)
(message "`wordnut' fails to find the `wn' executable"))))
#+end_src
*** [[https://github.com/bnbeckwith/writegood-mode#readme][Writegood mode]]
:PROPERTIES:
:CUSTOM_ID: sec:writing-writegood-mode
:END:
[[https://github.com/bnbeckwith/writegood-mode#readme][Writegood mode]] is a minor mode to aid in finding common writing problems. The
post [[http://matt.might.net/articles/shell-scripts-for-passive-voice-weasel-words-duplicates/][Matt Might's "My Ph.D. advisor rewrote himself in bash" scripts]] inspired
this mode. Listing [[lst:configure-writegood-mode]] configures [[https://github.com/bnbeckwith/writegood-mode#readme][writegood mode]] and
enables =writegood-mode= for =text-mode= buffers. Therefore, the best way use
it for this buffer is by typing {{{kbd(C-c C-e t U)}}} to export the it to a
=text-mode= buffer where the =writegood-mode= faces are more visible.
#+caption[Configure =writegood-mode=]:
#+caption: Configure =writegood-mode=.
#+name: lst:configure-writegood-mode
#+begin_src emacs-lisp -n :results silent
(when (and (ensure-package-installation 'writegood-mode)
(fboundp 'writegood-mode))
(defcustom writegood-mode-for '(org-mode text-mode)
"List of modes for which to enable `writegood-mode'."
:group 'writegood
:type '(repeat symbol))
(add-hook 'after-init-hook
(defun on-after-change-mode-hook-enable-writegood-mode ()
(add-hook 'after-change-major-mode-hook
(defun enable-writegood-mode ()
(when (derived-mode-p writegood-mode-for)
(writegood-mode +1)))))
'depth)
(global-set-key (kbd "C-c g") #'writegood-mode))
#+end_src
** [[info:emacs#Which Function][Which-function-mode (info)]]
:PROPERTIES:
:CUSTOM_ID: sec:which-function-mode
:END:
Listing [[lst:setup-which-function-mode]] sets ~which-function-mode~ options. The
post [[https://codelearn.me/2024/02/02/emacs-which-function-mode.html][Emacs: which-function-mode]] claims that ~which-function-mode~ works in
~org-mode~. This is true in case all document headlines are simple, but is not
true in case document headlines contain links. The code in listing
[[lst:define-for-which-func-functions]] has stolen ideas from the following links:
- [[https://list.orgmode.org/20240205.141235.268481480563517065.teika@gmx.com/][Show current org-mode headline in frame header]].
- [[https://emacs.stackexchange.com/questions/30894/][Show current org-mode headline in modeline]].
#+caption[Setup ~which-function-mode~]:
#+caption: Setup ~which-function-mode~.
#+name: lst:setup-which-function-mode
#+begin_src emacs-lisp -n :results silent
(with-eval-after-load 'which-func
(setopt which-func-modes
'(emacs-lisp-mode org-mode pdf-view-mode)))
;; It looks like `python-mode' does nothing when they it is an element
;; of `which-func-modes'.
;; (setopt which-func-modes t)
;; (setopt which-func-display 'header) ;; AFAIU, fails on my system.
#+end_src
#+caption[Define functions for ~which-func-functions~]:
#+caption: Define functions for ~which-func-functions~.
#+name: lst:define-for-which-func-functions
#+begin_src emacs-lisp -n :results silent
;; https://emacs.stackexchange.com/questions/30894/
(defvar which-func-functions nil)
(defun which-func-org-function ()
"Return level and title of the current headline.
Return the document title when point is above the first headline."
(interactive) ;; Keep this function interactive for fixing.
(when (eq major-mode 'org-mode)
(let (text)
;; Handle point eventually above the first headline.
(unless (org-at-heading-p)
(save-excursion
(org-previous-visible-heading 1)
(let ((eap (org-element-at-point)))
(when (org-element-type-p eap 'keyword)
(setq text (format "0|%s" (org-element-property :value eap)))))))
;; Handle point anywhere else.
(unless text
(let* ((chain (org-get-outline-path t))
(count (length chain)))
(setq text (format "%s|%s" count (nth (1- count) chain)))))
text)))
(add-to-list 'which-func-functions 'which-func-org-function)
(defun which-func-pdf-view-function ()
"Return the title of the current headline.
Return \"Front Matter\" when current page is above the first headline."
(interactive) ;; Keep this function interactive for fixing.
(when (eq major-mode 'pdf-view-mode)
(let* ((current-page (pdf-view-current-page))
(outline (pdf-info-outline (current-buffer)))
(hl-count (length outline))
(hl-index 0)
(hl-page 0)
(old-title "Front Matter")
(new-title old-title))
;; Return the first headline on a page which is better than nothing.
;; I can't do better, since `pdf-view-mode' has no notion of point.
(while (and (< hl-index hl-count) (< hl-page current-page))
(setq old-title new-title)
(setq hl-page (alist-get 'page (nth hl-index outline)))
(setq new-title (alist-get 'title (nth hl-index outline)))
(cl-incf hl-index))
(if (< current-page hl-page) old-title new-title))))
(add-to-list 'which-func-functions 'which-func-pdf-view-function)
#+end_src
* [[info:emacs#Saving Emacs Sessions][Saving Emacs sessions (info)]]
:PROPERTIES:
:CUSTOM_ID: sec:desktop
:END:
Listing [[lst:desktop-setup][setup desktop]] makes Emacs save its state between closing its session to
opening the next session.
#+caption[Setup ~desktop~ to save Emacs sessions]:
#+caption: Setup ~desktop~ to save Emacs sessions.
#+name: lst:desktop-setup
#+begin_src emacs-lisp -n :results silent
(with-eval-after-load 'emacs
;; Load and configure `Org' before enabling `desktop-save-mode'.
(desktop-save-mode t)
(setopt desktop-buffers-not-to-save
(rx bos (or "*Apropos" "compilation*"))
desktop-modes-not-to-save
'(emacs-lisp-mode image-mode tags-table-mode)))
#+end_src
BUG: I fail to code a function to set the ~desktop-buffers-not-to-save-function~
option.
* Programming Tools
:PROPERTIES:
:CUSTOM_ID: sec:programming-tools
:END:
** [[info:eglot#Top][Eglot (info)]]
:PROPERTIES:
:CUSTOM_ID: sec:eglot
:END:
[[https://github.com/joaotavora/eglot#readme][Emacs polyGLOT (Eglot)]] is an Emacs language-server-protocol client that stays
out of your way. [[info:eglot#Top][Eglot (info)]] is a builtin since Emacs-29.1. The following
listings contribute to a programming language mode independent [[https://github.com/joaotavora/eglot][Eglot]]
configuration:
1. Listing [[lst:minimal-eglot-setup][minimal Eglot setup]] adds key bindings to =eglot-mode-keymap=.
2. Listing [[lst:help-setup-org-src-mode-for-eglot]] and
[[lst:setup-python-org-src-mode-for-eglot]] try to prepare any =org-src-mode=
buffers for use with [[https://github.com/joaotavora/eglot][Eglot]]. They are a refactored implementation of the post
[[https://www.reddit.com/r/emacs/comments/w4f4u3/using_rustic_eglot_and_orgbabel_for_literate/][Using rustic, eglot, and org-babel with LSP support in Emacs]] for my use with
Python.
3. Listing [[lst:eglot-maybe-ensure]] starts [[https://github.com/joaotavora/eglot][Eglot]] in case of proper programming
modes and proper directory local variables (meaning in presence of a proper
file [[info:emacs#Directory Variables][.dir-locals.el]] in the root directory of any project using proper
programming modes).
4. Listing [[lst:whiten-black]] defines Emacs Lisp functions to undo (whiten) some
output of [[https://black.readthedocs.io/en/stable/][Black]] after src_emacs-lisp[:results silent]{(org-babel-tangle)}.
#+caption[Ensure =eglot= installation with minimal setup]:
#+caption: Ensure =eglot= installation with minimal setup.
#+name: lst:minimal-eglot-setup
#+begin_src emacs-lisp -n :results silent
(with-eval-after-load 'eglot
(keymap-set eglot-mode-map "C-c n" 'flymake-goto-next-error)
(keymap-set eglot-mode-map "C-c p" 'flymake-goto-prev-error)
(keymap-set eglot-mode-map "C-c r" 'eglot-rename))
#+end_src
#+caption[Help to setup any =org-src-mode= buffers for =eglot=]:
#+caption: Help to setup any =org-src-mode= buffers for =eglot=.
#+name: lst:help-setup-org-src-mode-for-eglot
#+begin_src emacs-lisp -n :results silent
(with-eval-after-load 'emacs
(defcustom eglot-maybe-ensure-modes '(python-mode)
"Modes where maybe `eglot-ensure' should be or has been called.
This may be in the case of proper directory local variables or in
the case of proper `org-src-mode' buffers.")
;; https://www.reddit.com/r/emacs/comments/w4f4u3
;; /using_rustic_eglot_and_orgbabel_for_literate/
(defun eglot-org-babel-edit-prep (info)
"Try to setup an `org-mode-src' buffer to make `eglot-ensure' succeed.
INFO has a form similar to the return value of
`org-babel-get-src-block-info'. Try to load the tangled file
into the `org-src-mode' buffer as well as to narrow the region to
the Org-mode source block code before calling `eglot-ensure'."
(unless (bound-and-true-p org-src-mode)
(user-error "Buffer %s is no `org-src-mode' buffer" (buffer-name)))
(let ((mark (point))
(body (nth 1 info))
(filename (cdr (assq :tangle (nth 2 info)))))
(when (string= filename "no")
(user-error "Org source block has no tangled file"))
(setq filename (expand-file-name filename))
(unless (file-readable-p filename)
(user-error "Tangled file %s is not readable" filename))
(with-temp-buffer
(insert-file-contents filename 'visit nil nil 'replace)
(unless (search-forward body nil 'noerror)
(user-error "Org source block does not occur in tangled file %s"
filename))
(when (search-forward body nil 'noerror)
(user-error "Org source block occurs twice or more in tangled file %s"
filename)))
(goto-char (point-min))
(insert-file-contents filename 'visit nil nil 'replace)
(search-forward body)
(narrow-to-region (match-beginning 0) (match-end 0))
(goto-char mark)
(eglot-ensure)))
(defun org-babel-edit-prep:python (info)
(eglot-org-babel-edit-prep info)))
#+end_src
#+caption[Setup Python =org-src-mode= buffers for =eglot=]:
#+caption: Setup Python =org-src-mode= buffers for =eglot=.
#+name: lst:setup-python-org-src-mode-for-eglot
#+begin_src emacs-lisp -n :results silent
(with-eval-after-load 'emacs
;; https://www.reddit.com/r/emacs/comments/w4f4u3
;; /using_rustic_eglot_and_orgbabel_for_literate/
(defun undo-eglot-org-babel-edit-prep()
"Undo the `eglot' setup by deleting the text hidden by narrowing.
This is to advice `org-edit-src-exit' and `org-edit-src-save'."
(when (and (bound-and-true-p org-src-mode)
(buffer-file-name)
(apply #'derived-mode-p eglot-maybe-ensure-modes))
(save-excursion
(goto-char (point-min))
(save-restriction
(widen)
(delete-region (point-min) (point)))
(goto-char (point-max))
(save-restriction
(widen)
(delete-region (point) (point-max))))))
(advice-add 'org-edit-src-exit :before #'undo-eglot-org-babel-edit-prep)
(advice-add 'org-edit-src-save :before #'undo-eglot-org-babel-edit-prep))
#+end_src
#+caption[Start =eglot= in case of a proper =dir-local-variables-alist=]:
#+caption: Start =eglot= in case of a proper =dir-local-variables-alist=.
#+name: lst:eglot-maybe-ensure
#+begin_src emacs-lisp -n :results silent
(with-eval-after-load 'emacs
(defun eglot-maybe-ensure ()
(when (and (apply #'derived-mode-p eglot-maybe-ensure-modes)
(assoc 'eglot-workspace-configuration dir-local-variables-alist))
(eglot-ensure)))
;; The two hooks `after-change-major-mode-hook' and
;; `hack-local-variables-hook' are OK, but language mode hooks like
;; `python-mode-hook' are not.
(add-hook 'after-change-major-mode-hook #'eglot-maybe-ensure))
#+end_src
#+caption[Whiten Black]:
#+caption: Whiten Black
#+name: lst:whiten-black
#+begin_src emacs-lisp -n :results silent
(with-eval-after-load 'emacs
(defun black-python-sequence ()
"Try to find a Blackened Python sequence to whiten."
(interactive)
(if (derived-mode-p 'python-mode)
(re-search-forward "[[(][ \t]*$" nil t)
(org-narrow-to-block)
(re-search-forward "[[(][ \t]*$" nil t)
(widen)))
(defun whiten--python-sequence ()
"Whiten a Blackened Python sequence without narrowing.
Call `widen' after an `user-error'."
(if (not (and (looking-at-p "[ \t]*$")))
(user-error "Call `widen': not seeing `new-line'")
(skip-chars-backward " \t\\[\\(")
(if (not (looking-at-p "[ \t]*[[(][ \t]*$"))
(user-error "Call `widen': not seeing `[' or `(' before `new-line'")
(fixup-whitespace)
(skip-chars-forward " \\[\\(")
(kill-line)
(fixup-whitespace)
(move-end-of-line 1)
(while (and (eq ?\, (char-before)))
(kill-line)
(fixup-whitespace)
(move-end-of-line 1))
(save-excursion
(when (memq (char-before) '(?\) ?\]))
(goto-char (1- (point)))
(delete-char -1))))))
(defun whiten-python-sequence ()
"Whiten a Blackened Python sequence. Call `widen' after an `user-error'."
(interactive)
(if (derived-mode-p 'python-mode)
(whiten--python-sequences)
(org-narrow-to-block)
(whiten--python-sequence)
(widen))))
#+end_src
#+caption: Real sessions for ~eval-buffer~ in ~org-src~ buffers.
#+begin_src org -n :tangle eval-buffer-in-org-src-buffers.org
,#+title: Sessions for ~eval-buffer~ in ~org-src~ buffers
,#+property: :tangle none
,* Real sessions in ~org-src-mode~ buffers
,#+header: :wrap "src text"
,#+begin_src python :results output :session
print("Hello *Python*!")
,#+end_src
,#+RESULTS:
,#+begin_src text
Hello *Python*!
,#+end_src
,#+header: :wrap "src text"
,#+begin_src python :results output :session Python
print("Hello *Python*!")
,#+end_src
,#+RESULTS:
,#+begin_src text
Hello *Python*!
,#+end_src
,#+header: :wrap "src text"
,#+begin_src python :results output :session snake
print("Hello *snake*!")
,#+end_src
,#+RESULTS:
,#+begin_src text
Hello *snake*!
,#+end_src
#+end_src
#+caption: Fake sessions for ~eval-buffer~ in ~org-src~ buffers.
#+begin_src org -n :tangle eval-buffer-in-org-src-buffers.org
,* Fake sessions in ~org-src-mode~ buffers
,#+header: :wrap "src text"
,#+begin_src python :results output :session none
print("Hello *none*!")
,#+end_src
,#+RESULTS:
,#+begin_src text
Hello *none*!
,#+end_src
,#+header: :wrap "src text"
,#+begin_src python :results output :session :default
print("Hello *Python* with `:default' unseen!")
,#+end_src
,#+RESULTS:
,#+begin_src text
Hello *Python* with `:default' unseen!
,#+end_src
,#+header: :wrap "src text"
,#+begin_src python :results output :session ":default"
print("Hello *Python* with `*:default*' seen!")
,#+end_src
,#+RESULTS:
,#+begin_src text
Hello *Python* with `*:default*' seen!
,#+end_src
#+end_src
** [[https://github.com/lassik/emacs-format-all-the-code#readme][Format-all]]
:PROPERTIES:
:CUSTOM_ID: sec:format-all
:END:
Listing [[lst:configure-format-all]]:
1. Configures [[https://github.com/lassik/emacs-format-all-the-code#readme][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.
#+caption[Configure =format-all=]:
#+caption: Configure =format-all=.
#+name: lst:configure-format-all
#+begin_src emacs-lisp -n :results silent
;; https://github.com/lassik/emacs-format-all-the-code#readme
;; https://ianyepan.github.io/posts/format-all/
;; https://jamesaimonetti.com/posts/formatting-tangled-output-in-org-mode/
(when (ensure-package-installation 'format-all)
;; `format-all' defines `format-all-buffer' as an autoloaded command.
(with-eval-after-load 'ob-tangle
(add-hook
'org-babel-post-tangle-hook
(defun format-all-org-babel-post-tangle ()
(when (derived-mode-p 'python-mode)
(setq-local format-all-formatters '(("Python" black)))
(format-all-buffer)
(save-buffer)
(message "Saved reformatted tangled buffer `%s'"
(buffer-file-name)))))))
#+end_src
** [[info:flymake#Top][Flymake (info)]]
:PROPERTIES:
:CUSTOM_ID: sec:flymake
:END:
Flymake is an universal on-the-fly syntax checker for Emacs. It is a requirement
of [[https://github.com/joaotavora/eglot][eglot]], but you can use it without [[https://github.com/joaotavora/eglot][eglot]] for instance in [[https://git.savannah.gnu.org/cgit/emacs.git/tree/lisp/progmodes/python.el][python-mode]] buffers
that do not visit a file. Listing [[lst:configure-flymake]] aliases =list-errors=
(new) to =flymake-show-buffer-diagnostics= (old).
#+caption[Configure =Flymake=]:
#+caption: Configure =Flymake=
#+name: lst:configure-flymake
#+begin_src emacs-lisp -n :results silent
(with-eval-after-load 'flymake
(defalias 'list-errors #'flymake-show-buffer-diagnostics
"Show a list of Flymake diagnostics for current buffer."))
#+end_src
* Programming Modes
:PROPERTIES:
:CUSTOM_ID: sec:programming-languages
:END:
** [[https://dept-info.labri.fr/~strandh/Teaching/PFS/Common/Strandh-Tutorial/Dir-symbolic.html][Common Lisp programming]]
:PROPERTIES:
:CUSTOM_ID: sec:common-lisp-programming
:END:
Links to Common Lisp books and posts are:
1. [[https://lisp-lang.org/][Common Lisp]]
2. [[https://kvardek-du.kerno.org/][Kvardek Du - Luis Oliveira]]
3. [[https://redirect.cs.umbc.edu/courses/331/resources/lisp/][Lisp Resources]]
4. [[https://redirect.cs.umbc.edu/courses/331/resources/lisp/onLisp/][On Lisp - Paul Graham]]
5. [[https://dept-info.labri.fr/~strandh/Teaching/Programmation-Symbolique/Common/Book/HTML/programmation.html][Traité de Programmation en Common Lisp]]
*** [[https://slime.common-lisp.dev/doc/html/index.html][Slime]] :noexport:
:PROPERTIES:
:CUSTOM_ID: sec:slime
:header-args:emacs-lisp: :exports code :tangle no
:END:
#+caption[Configure =slime=]:
#+caption: Configure =slime=.
#+name: lst:configure-slime
#+begin_src emacs-lisp -n :results silent
(when (ensure-package-installation 'slime)
(with-eval-after-load 'slime
(setq slime-default-lisp 'sbcl
slime-lisp-implementations
`((sbcl (,(executable-find "sbcl")
"--core"
,(no-littering-expand-var-file-name "sbcl.core-for-sly")))))
;; (add-hook 'slime-mode-hook
;; (defun on-slime-mode-hook ()
;; (unless (slime-connected-p)
;; (save-excursion (slime)))))
(cond
((eq system-type 'darwin)
(setq common-lisp-hyperspec-root
"file:///usr/local/share/doc/hyperspec/HyperSpec/")
(setq common-lisp-hyperspec-symbol-table
(concat common-lisp-hyperspec-root "Data/Map_Sym.txt")))
((eq system-type 'gnu/linux)
(setq common-lisp-hyperspec-root
"file:///usr/share/doc/hyperspec-7.0/HyperSpec/"))
(t (message "Default Common Lisp HyperSpec access")))
(keymap-set slime-prefix-map "M-h" #'slime-documentation-lookup)))
#+end_src
*** [[https://joaotavora.github.io/sly/][Sly]]
:PROPERTIES:
:CUSTOM_ID: sec:sly
:END:
Listing [[lst:configure-sly]] configures the [[info:sly#Top][Sly (info)]] Common Lisp IDE for Emacs
for use with [[http://www.sbcl.org/][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 [[info:sly#Multiple Lisps][multiple lisps (info)]] manual.
2. It does not ensure the [[info:sly#Auto-SLY][automatic connection to the lisp server (info)]] when
opening a Common Lisp file because it does not play well with =ob-lisp=.
3. It configures searching documentation in the [[http://www.lispworks.com/documentation/HyperSpec/Front/][Common Lisp HyperSpec]] according
to the [[info:sly#Basic customization][basic customization (info)]] manual.
Listing [[lst:slink-sel]] allows to set or get the Sly string elision length.
Finally, listing [[lst:configure-sly]] uses a technique to [[info:sly#Loading Slynk faster][load Slynk faster (info)]]
by means of a custom core file in src_emacs-lisp[:results
silent]{(describe-variable 'no-littering-var-directory)}. Listing
[[lst:sbcl-core-for-sly]] tangles to a script to dump such a [[http://www.sbcl.org/][SBCL]] core.
#+caption[Configure =sly=]:
#+caption: Configure =sly=.
#+name: lst:configure-sly
#+begin_src emacs-lisp -n :results silent
(when (ensure-package-installation 'sly 'sly-macrostep 'sly-named-readtables)
(with-eval-after-load 'sly
;; Set `sly-default-lisp' instead of `inferior-lisp-program',
;; because `sly' uses `inferior-lisp-program' only as a backwards
;; compatibility fallback option.
;; The warning "Value sbcl does not match type function" is due to
;; the buggy `defcustom' type of `sbcl-default-lisp' in `sly.el'.
(setopt sly-db-initial-restart-limit 10
sly-default-lisp 'sbcl
sly-lisp-implementations
`((ccl (,(executable-find "ccl64")))
(sbcl (,(executable-find "sbcl")
"--core"
,(no-littering-expand-var-file-name "sbcl.core-for-sly")))))
;; (add-hook 'sly-mode-hook
;; (defun on-sly-mode-hook ()
;; (unless (sly-connected-p)
;; (save-excursion (sly)))))
(cond
((eq system-type 'darwin)
(setq common-lisp-hyperspec-root
"file:///usr/local/share/doc/hyperspec/HyperSpec/")
(setq common-lisp-hyperspec-symbol-table
(concat common-lisp-hyperspec-root "Data/Map_Sym.txt")))
((eq system-type 'gnu/linux)
(setq common-lisp-hyperspec-root
"file:///usr/share/doc/hyperspec-7.0/HyperSpec/"))
(t (message "Default Common Lisp HyperSpec access")))
(keymap-set sly-prefix-map "M-h" #'sly-documentation-lookup)))
#+end_src
#+caption[Get/set Sly ~slynk:*string-elision-length*~ functionality]:
#+caption: Get/set Sly ~slynk:*string-elision-length*~ functionality.
#+name: lst:slink-sel
#+begin_src emacs-lisp -n :results silent
(with-eval-after-load 'sly
(defconst slynk-command-get-sel
"(cdr (assoc 'slynk:*string-elision-length* slynk:*slynk-pprint-bindings*))"
"Slynk \"string elision length\" getter command.")
(defun slynk-command-set-sel (sel)
"Slynk SEL \"string elision length\" setter command.
SEL values must be positive integers to enable or `nil' to disable elision."
(format "(setf %s %s)" slynk-command-get-sel sel))
(defun slynk-get-sel ()
"Get the Slynk \"string-elision-length\"."
(cadr (sly-eval `(slynk:eval-and-grab-output ,slynk-command-get-sel))))
(defun slynk-set-sel (sel)
"Set the Slynk \"string-elision-length\" SEL.
SEL values must be positive integers to enable or `nil' to disable elision."
(cadr (sly-eval `(slynk:eval-and-grab-output ,(slynk-command-set-sel sel)))))
(defun slynk-eval-sel-command (string)
"Evaluate the Slynk \"string-elision-length\" command STRING."
(unless (sly-connected-p)
(user-error "Slynk connection is not open: \"M-x sly\"?"))
(message "Slynk string elision length is %S"
(read (cadr (sly-eval `(slynk:eval-and-grab-output ,string))))))
(defun slynk-get-string-elision-length ()
"Get the Slynk \"string-elision-length\"."
(interactive)
(slynk-eval-sel-command slynk-command-get-sel))
(defun slynk-set-string-elision-length (&optional sel)
"Set the Slynk \"string-elision-length\" by evaluating SEL.
Valid SEL values are positive integers to enable or `nil' to disable elision."
(interactive "XSlynk string-elision-length (nil or sel > 0): ")
(unless (or (null sel) (and (integerp sel) (> sel 0)))
(user-error
"Slynk `sel' must evaluate to `nil' or a positive integer (got `%S')" sel))
(slynk-eval-sel-command (slynk-command-set-sel sel))))
#+end_src
#+caption[Script to dump a SBCL core for the Sly Common Lisp IDE]:
#+caption: Script to dump a SBCL core for the Sly Common Lisp IDE.
#+header: :tangle-mode (identity #o755)
#+name: lst:sbcl-core-for-sly
#+begin_src shell -n :eval never :tangle ~/bin/sbcl.core-for-sly
#!/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:
#+end_src
*** [[https://courses.cs.northwestern.edu/325/][CS 325 AI Programming]]
:PROPERTIES:
:CUSTOM_ID: sec:cs-325-ai-programming
:END:
The [[https://courses.cs.northwestern.edu/325/][CS 325 AI Programming]] course allows to learn Common Lisp by self-study and
the page [[https://courses.cs.northwestern.edu/325/admin/lisp-setup.php][CS 325: Setting up Lisp]] gives instructions how to:
1. [[https://courses.cs.northwestern.edu/325/admin/lisp-setup.php#lisp][Download and install common lisp]].
2. [[https://courses.cs.northwestern.edu/325/admin/lisp-setup.php#quicklisp][Install Quicklisp]].
3. [[https://courses.cs.northwestern.edu/325/admin/lisp-setup.php#install-325][Install the CS325 library]].
*** [[https://www.quicklisp.org/][Quicklisp]]
:PROPERTIES:
:CUSTOM_ID: sec:quicklisp
:END:
[[https://www.quicklisp.org/][Quicklisp]] is a library manager for Common Lisp. Listing
[[lst:download+verify-quicklisp]] downloads the [[https://www.quicklisp.org/][Quicklisp]] installation file and
verifies its signature to prevent tampering. Listing [[lst:bootstrap-quicklisp]]
tangles to a shell script allowing to bootstrap [[https://www.quicklisp.org/][Quicklisp]] with [[http://www.sbcl.org/][SBCL]]. Listing
[[lst:quicklisp-sbclrc-file]] tangles to the a [[http://www.sbcl.org/][SBCL]] resource file with [[https://www.quicklisp.org/][Quicklisp]]
support. Listing [[lst:clone-local-projects]] shows how to make local projects out
of unpackaged projects and listing [[lst:register-load-local-projects]] shows how to
register local projects in order to load those unpackaged projects in the same
way as packaged projects.
#+caption[Download and verify =quicklisp=]:
#+caption: Download and verify =quicklisp=.
#+name: lst:download+verify-quicklisp
#+begin_src shell -n :dir ~ :results none :tangle no
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
#+end_src
#+caption[Bootstrap =quicklisp=]:
#+caption: Bootstrap =quicklisp=.
#+header: :tangle-mode (identity #o755)
#+name: lst:bootstrap-quicklisp
#+begin_src shell -n :eval never :tangle ~/bin/quicklisp-sbcl-bootstrap
#!/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:
#+end_src
#+caption[Clone local =quicklisp= projects]:
#+caption: Clone local =quicklisp= projects.
#+name: lst:clone-local-projects
#+begin_src shell -n :dir ~/quicklisp/local-projects :results none :tangle no
git clone https://gitlab.com/criesbeck/cs325.git
git clone git@github.com:ageldama/cl-state-machine.git
#+end_src
#+caption[Register and load local =quicklisp= projects with dependencies]:
#+caption: Register and load local =quicklisp= projects with dependencies.
#+name: lst:register-load-local-projects
#+begin_src lisp -n :eval never-export :results none :tangle no
;; SBCL on Darwin fails to run cl-cffi-gtk:
;; https://lisp-journey.gitlab.io/blog/gui-programming-in-common-lisp-part-3-of-5-gtk3/
;; https://www.crategus.com/books/cl-cffi-gtk/
;; https://www.crategus.com/books/cl-gtk/gtk-tutorial.html
(defun probe--local-project-directory (name)
(some #'identity (mapcar (lambda (x)
(let ((*default-pathname-defaults* x))
(probe-file name)))
ql:*local-project-directories*)))
(when (probe--local-project-directory "cs325")
(ql:register-local-projects)
(ql:quickload "cs325")
(ql:quickload "meta")
(ql:quickload '("named-readtables" "try")) ;; testing requires "try".
#-:clozure
(ql:quickload "nodgui") ;; requires https://www.tcl.tk/software/tklib/
(ql:quickload '("rutils" "rutilsx"))
(ql:quickload "ucons"))
(when (probe--local-project-directory "cl-state-machine")
(ql:quickload '("cl-state-machine" "cl-state-machine-examples"
"cl-state-machine-graphing" "cl-state-machine-test")))
#+end_src
#+caption: A =quicklisp= sbclrc file.
#+name: lst:quicklisp-sbclrc-file
#+begin_src lisp -n :eval never :tangle ~/.sbclrc
;;; Hey Emacs, this is my -*- lisp -*- .sbclrc file.
;;; The following lines added by ql:add-to-init-file:
#-quicklisp
(let ((quicklisp-init (merge-pathnames "quicklisp/setup.lisp"
(user-homedir-pathname))))
(when (probe-file quicklisp-init)
(load quicklisp-init)))
;;; Load cs325.lisp to create the cs325 package.
;; (eval-when (:compile-toplevel :load-toplevel :execute)
;; (ql:quickload "cs325")
;; (setq *package* (find-package :cs325-user)))
#+end_src
*** [[https://github.com/hellerve/sbcli][Better REPL for SBCL]] :noexport:
:PROPERTIES:
:CUSTOM_ID: sec:sbcli
:END:
#+caption[Install =sbcli=]:
#+caption: Install =sbcli=.
#+name: lst:install-sbcli
#+begin_src emacs-lisp -n :eval never-export :results silent :tangle no
(let ((url "https://raw.githubusercontent.com/hellerve/sbcli/master/repl.lisp")
(file "~/bin/sbcli"))
(url-copy-file url file 'ok-if-already-exists)
(set-file-modes file #o700))
#+end_src
#+caption[Tangle a =sbcli= resource file]:
#+caption: Tangle a =sbcli= resource file.
#+name: lst:write-sbslirc
#+begin_src lisp -n :eval never :tangle ~/.sbclirc
(setf *repl-name* "Gerard's custom REPL for SBCLI")
;; The style option fails:
;; (setf *pygmentize* (merge-pathnames ".pyenv/shims/pygmentize"
;; (user-homedir-pathname)))
;; (defvar *pygmentize-options* (list "-s" "-l" "lisp" "-O" "style=zenburn"))
#+end_src
** [[https://www.n16f.net/blog/custom-font-lock-configuration-in-emacs/][Lisp mode custom font locking for Common Lisp]]
:PROPERTIES:
:CUSTOM_ID: sec:lisp-custom-font-locking
:END:
This section implements the code described in the [[https://www.n16f.net/blog/custom-font-lock-configuration-in-emacs/][Emacs Common Lisp Font Locking]]
post.
*** [[https://www.n16f.net/blog/custom-font-lock-configuration-in-emacs/][Initialize Common Lisp custom font locking]]
:PROPERTIES:
:CUSTOM_ID: sec:cl-custom-font-locking-start
:END:
#+caption[Define faces for =Common Lisp= custom font locking]:
#+caption: Define faces for =Common Lisp= custom-font-locking.
#+name: lst:cl-custom-font-locking-faces
#+begin_src emacs-lisp -n :results silent
;;; Common Lisp custom font lock configuration.
;;; https://www.n16f.net/blog/custom-font-lock-configuration-in-emacs/
(defface cl-character-face
'((default :inherit font-lock-constant-face))
"The face used to highlight Common Lisp character literals.")
(defface cl-standard-function-face
'((default :inherit font-lock-keyword-face))
"The face used to highlight standard Common Lisp function symbols.")
(defface cl-standard-value-face
'((default :inherit font-lock-variable-name-face))
"The face used to highlight standard Common Lisp value symbols.")
#+end_src
#+caption[Define a =Common Lisp= function to find standard symbol names]:
#+caption: Define a =Common Lisp= function to find standard symbol names.
#+name: lst:standard-symbol-names
#+begin_src lisp -n :eval never-export :results silent :tangle no
(defun standard-symbol-names (predicate)
(let ((symbols nil))
(do-external-symbols (symbol :common-lisp)
(when (funcall predicate symbol)
(push (string-downcase (symbol-name symbol)) symbols)))
(sort symbols #'string<)))
#+end_src
#+caption[Set Sly ~slynk:*string-elision-length*~]:
#+caption: Set Sly ~slynk:*string-elision-length*~.
#+name: lst:set-sly-sel
#+header: :wrap "src text -n"
#+begin_src emacs-lisp -n :eval never-export :exports both :tangle no
(when (and (fboundp 'sly-connected-p) (sly-connected-p))
(and (fboundp 'slynk-set-sel) (slynk-set-sel (ash 1 14))))
#+end_src
#+caption[Set Sly ~slynk:*string-elision-length*~ result]:
#+caption: Set Sly ~slynk:*string-elision-length*~ result.
#+name: lst:set-sly-sel-result
#+RESULTS: lst:set-sly-sel
#+begin_src text -n
16384 (15 bits, #x4000)
#+end_src
*** [[https://www.n16f.net/blog/custom-font-lock-configuration-in-emacs/][Collect standard symbol function names]]
:PROPERTIES:
:CUSTOM_ID: sec:cl-function-names
:END:
#+caption[Define the =cl-function-names= variable in an =Emacs Lisp= block]:
#+caption: Use =Common Lisp= to define the =cl-function-names= variable in
#+caption: an =Emacs Lisp= source block for tangling into =user-init-file=.
#+name: lst:define-cl-function-names
#+header: :wrap "src emacs-lisp -n :results silent"
#+begin_src lisp -n :exports both :eval never-export
(format nil "(defvar~% cl-function-names~% '~S)~%"
(standard-symbol-names #'fboundp))
#+end_src
#+RESULTS: lst:define-cl-function-names
#+begin_src emacs-lisp -n :results silent
(defvar
cl-function-names
'("*" "+" "-" "/" "/=" "1+" "1-" "<" "<=" "=" ">" ">=" "abort" "abs" "acons"
"acos" "acosh" "add-method" "adjoin" "adjust-array" "adjustable-array-p"
"allocate-instance" "alpha-char-p" "alphanumericp" "and" "append" "apply"
"apropos" "apropos-list" "aref" "arithmetic-error-operands"
"arithmetic-error-operation" "array-dimension" "array-dimensions"
"array-displacement" "array-element-type" "array-has-fill-pointer-p"
"array-in-bounds-p" "array-rank" "array-row-major-index" "array-total-size"
"arrayp" "ash" "asin" "asinh" "assert" "assoc" "assoc-if" "assoc-if-not"
"atan" "atanh" "atom" "bit" "bit-and" "bit-andc1" "bit-andc2" "bit-eqv"
"bit-ior" "bit-nand" "bit-nor" "bit-not" "bit-orc1" "bit-orc2"
"bit-vector-p" "bit-xor" "block" "boole" "both-case-p" "boundp" "break"
"broadcast-stream-streams" "butlast" "byte" "byte-position" "byte-size"
"caaaar" "caaadr" "caaar" "caadar" "caaddr" "caadr" "caar" "cadaar"
"cadadr" "cadar" "caddar" "cadddr" "caddr" "cadr" "call-method" "car"
"case" "catch" "ccase" "cdaaar" "cdaadr" "cdaar" "cdadar" "cdaddr" "cdadr"
"cdar" "cddaar" "cddadr" "cddar" "cdddar" "cddddr" "cdddr" "cddr" "cdr"
"ceiling" "cell-error-name" "cerror" "change-class" "char" "char-code"
"char-downcase" "char-equal" "char-greaterp" "char-int" "char-lessp"
"char-name" "char-not-equal" "char-not-greaterp" "char-not-lessp"
"char-upcase" "char/=" "char<" "char<=" "char=" "char>" "char>="
"character" "characterp" "check-type" "cis" "class-name" "class-of"
"clear-input" "clear-output" "close" "clrhash" "code-char" "coerce"
"compile" "compile-file" "compile-file-pathname" "compiled-function-p"
"compiler-macro-function" "complement" "complex" "complexp"
"compute-applicable-methods" "compute-restarts" "concatenate"
"concatenated-stream-streams" "cond" "conjugate" "cons" "consp"
"constantly" "constantp" "continue" "copy-alist" "copy-list"
"copy-pprint-dispatch" "copy-readtable" "copy-seq" "copy-structure"
"copy-symbol" "copy-tree" "cos" "cosh" "count" "count-if" "count-if-not"
"ctypecase" "decf" "declaim" "decode-float" "decode-universal-time"
"defclass" "defconstant" "defgeneric" "define-compiler-macro"
"define-condition" "define-method-combination" "define-modify-macro"
"define-setf-expander" "define-symbol-macro" "defmacro" "defmethod"
"defpackage" "defparameter" "defsetf" "defstruct" "deftype" "defun"
"defvar" "delete" "delete-duplicates" "delete-file" "delete-if"
"delete-if-not" "delete-package" "denominator" "deposit-field" "describe"
"describe-object" "destructuring-bind" "digit-char" "digit-char-p"
"directory" "directory-namestring" "disassemble" "do" "do*"
"do-all-symbols" "do-external-symbols" "do-symbols" "documentation"
"dolist" "dotimes" "dpb" "dribble" "ecase" "echo-stream-input-stream"
"echo-stream-output-stream" "ed" "eighth" "elt" "encode-universal-time"
"endp" "enough-namestring" "ensure-directories-exist"
"ensure-generic-function" "eq" "eql" "equal" "equalp" "error" "etypecase"
"eval" "eval-when" "evenp" "every" "exp" "export" "expt" "fboundp"
"fceiling" "fdefinition" "ffloor" "fifth" "file-author"
"file-error-pathname" "file-length" "file-namestring" "file-position"
"file-string-length" "file-write-date" "fill" "fill-pointer" "find"
"find-all-symbols" "find-class" "find-if" "find-if-not" "find-method"
"find-package" "find-restart" "find-symbol" "finish-output" "first" "flet"
"float" "float-digits" "float-precision" "float-radix" "float-sign"
"floatp" "floor" "fmakunbound" "force-output" "format" "formatter" "fourth"
"fresh-line" "fround" "ftruncate" "funcall" "function" "function-keywords"
"function-lambda-expression" "functionp" "gcd" "gensym" "gentemp" "get"
"get-decoded-time" "get-dispatch-macro-character" "get-internal-real-time"
"get-internal-run-time" "get-macro-character" "get-output-stream-string"
"get-properties" "get-setf-expansion" "get-universal-time" "getf" "gethash"
"go" "graphic-char-p" "handler-bind" "handler-case" "hash-table-count"
"hash-table-p" "hash-table-rehash-size" "hash-table-rehash-threshold"
"hash-table-size" "hash-table-test" "host-namestring" "identity" "if"
"ignore-errors" "imagpart" "import" "in-package" "incf"
"initialize-instance" "input-stream-p" "inspect" "integer-decode-float"
"integer-length" "integerp" "interactive-stream-p" "intern" "intersection"
"invalid-method-error" "invoke-debugger" "invoke-restart"
"invoke-restart-interactively" "isqrt" "keywordp" "labels" "lambda" "last"
"lcm" "ldb" "ldb-test" "ldiff" "length" "let" "let*"
"lisp-implementation-type" "lisp-implementation-version" "list" "list*"
"list-all-packages" "list-length" "listen" "listp" "load"
"load-logical-pathname-translations" "load-time-value" "locally" "log"
"logand" "logandc1" "logandc2" "logbitp" "logcount" "logeqv"
"logical-pathname" "logical-pathname-translations" "logior" "lognand"
"lognor" "lognot" "logorc1" "logorc2" "logtest" "logxor" "long-site-name"
"loop" "loop-finish" "lower-case-p" "machine-instance" "machine-type"
"machine-version" "macro-function" "macroexpand" "macroexpand-1" "macrolet"
"make-array" "make-broadcast-stream" "make-concatenated-stream"
"make-condition" "make-dispatch-macro-character" "make-echo-stream"
"make-hash-table" "make-instance" "make-instances-obsolete" "make-list"
"make-load-form" "make-load-form-saving-slots" "make-package"
"make-pathname" "make-random-state" "make-sequence" "make-string"
"make-string-input-stream" "make-string-output-stream" "make-symbol"
"make-synonym-stream" "make-two-way-stream" "makunbound" "map" "map-into"
"mapc" "mapcan" "mapcar" "mapcon" "maphash" "mapl" "maplist" "mask-field"
"max" "member" "member-if" "member-if-not" "merge" "merge-pathnames"
"method-combination-error" "method-qualifiers" "min" "minusp" "mismatch"
"mod" "muffle-warning" "multiple-value-bind" "multiple-value-call"
"multiple-value-list" "multiple-value-prog1" "multiple-value-setq"
"name-char" "namestring" "nbutlast" "nconc" "nintersection" "ninth"
"no-applicable-method" "no-next-method" "not" "notany" "notevery" "nreconc"
"nreverse" "nset-difference" "nset-exclusive-or" "nstring-capitalize"
"nstring-downcase" "nstring-upcase" "nsublis" "nsubst" "nsubst-if"
"nsubst-if-not" "nsubstitute" "nsubstitute-if" "nsubstitute-if-not" "nth"
"nth-value" "nthcdr" "null" "numberp" "numerator" "nunion" "oddp" "open"
"open-stream-p" "or" "output-stream-p" "package-error-package"
"package-name" "package-nicknames" "package-shadowing-symbols"
"package-use-list" "package-used-by-list" "packagep" "pairlis"
"parse-integer" "parse-namestring" "pathname" "pathname-device"
"pathname-directory" "pathname-host" "pathname-match-p" "pathname-name"
"pathname-type" "pathname-version" "pathnamep" "peek-char" "phase" "plusp"
"pop" "position" "position-if" "position-if-not" "pprint" "pprint-dispatch"
"pprint-exit-if-list-exhausted" "pprint-fill" "pprint-indent"
"pprint-linear" "pprint-logical-block" "pprint-newline" "pprint-pop"
"pprint-tab" "pprint-tabular" "prin1" "prin1-to-string" "princ"
"princ-to-string" "print" "print-not-readable-object" "print-object"
"print-unreadable-object" "probe-file" "proclaim" "prog" "prog*" "prog1"
"prog2" "progn" "progv" "provide" "psetf" "psetq" "push" "pushnew" "quote"
"random" "random-state-p" "rassoc" "rassoc-if" "rassoc-if-not" "rational"
"rationalize" "rationalp" "read" "read-byte" "read-char"
"read-char-no-hang" "read-delimited-list" "read-from-string" "read-line"
"read-preserving-whitespace" "read-sequence" "readtable-case" "readtablep"
"realp" "realpart" "reduce" "reinitialize-instance" "rem" "remf" "remhash"
"remove" "remove-duplicates" "remove-if" "remove-if-not" "remove-method"
"remprop" "rename-file" "rename-package" "replace" "require" "rest"
"restart-bind" "restart-case" "restart-name" "return" "return-from"
"revappend" "reverse" "room" "rotatef" "round" "row-major-aref" "rplaca"
"rplacd" "sbit" "scale-float" "schar" "search" "second" "set"
"set-difference" "set-dispatch-macro-character" "set-exclusive-or"
"set-macro-character" "set-pprint-dispatch" "set-syntax-from-char" "setf"
"setq" "seventh" "shadow" "shadowing-import" "shared-initialize" "shiftf"
"short-site-name" "signal" "signum" "simple-bit-vector-p"
"simple-condition-format-arguments" "simple-condition-format-control"
"simple-string-p" "simple-vector-p" "sin" "sinh" "sixth" "sleep"
"slot-boundp" "slot-exists-p" "slot-makunbound" "slot-missing"
"slot-unbound" "slot-value" "software-type" "software-version" "some"
"sort" "special-operator-p" "sqrt" "stable-sort" "standard-char-p" "step"
"store-value" "stream-element-type" "stream-error-stream"
"stream-external-format" "streamp" "string" "string-capitalize"
"string-downcase" "string-equal" "string-greaterp" "string-left-trim"
"string-lessp" "string-not-equal" "string-not-greaterp" "string-not-lessp"
"string-right-trim" "string-trim" "string-upcase" "string/=" "string<"
"string<=" "string=" "string>" "string>=" "stringp" "sublis" "subseq"
"subsetp" "subst" "subst-if" "subst-if-not" "substitute" "substitute-if"
"substitute-if-not" "subtypep" "svref" "sxhash" "symbol-function"
"symbol-macrolet" "symbol-name" "symbol-package" "symbol-plist"
"symbol-value" "symbolp" "synonym-stream-symbol" "tagbody" "tailp" "tan"
"tanh" "tenth" "terpri" "the" "third" "throw" "time" "trace"
"translate-logical-pathname" "translate-pathname" "tree-equal" "truename"
"truncate" "two-way-stream-input-stream" "two-way-stream-output-stream"
"type-error-datum" "type-error-expected-type" "type-of" "typecase" "typep"
"unbound-slot-instance" "unexport" "unintern" "union" "unless"
"unread-char" "untrace" "unuse-package" "unwind-protect"
"update-instance-for-different-class" "update-instance-for-redefined-class"
"upgraded-array-element-type" "upgraded-complex-part-type" "upper-case-p"
"use-package" "use-value" "user-homedir-pathname" "values" "values-list"
"vector" "vector-pop" "vector-push" "vector-push-extend" "vectorp" "warn"
"when" "wild-pathname-p" "with-accessors" "with-compilation-unit"
"with-condition-restarts" "with-hash-table-iterator"
"with-input-from-string" "with-open-file" "with-open-stream"
"with-output-to-string" "with-package-iterator" "with-simple-restart"
"with-slots" "with-standard-io-syntax" "write" "write-byte" "write-char"
"write-line" "write-sequence" "write-string" "write-to-string" "y-or-n-p"
"yes-or-no-p" "zerop"))
#+end_src
*** [[https://www.n16f.net/blog/custom-font-lock-configuration-in-emacs/][Collect standard symbol value names]]
:PROPERTIES:
:CUSTOM_ID: sec:cl-value-names
:END:
#+caption[Define the =cl-variable-names= variable in an =Emacs Lisp= block]:
#+caption: Use =Common Lisp= to define the =cl-variable-names= variable in
#+caption: an =Emacs Lisp= source block for tangling into =user-init-file=.
#+name: lst:define-cl-value-names
#+header: :wrap "src emacs-lisp -n :results silent"
#+begin_src lisp -n :exports both :eval never-export
(format nil "(defvar~% cl-value-names~% '~S)~%"
(standard-symbol-names #'boundp))
#+end_src
#+RESULTS: lst:define-cl-value-names
#+begin_src emacs-lisp -n :results silent
(defvar
cl-value-names
'("*" "**" "***" "*break-on-signals*" "*compile-file-pathname*"
"*compile-file-truename*" "*compile-print*" "*compile-verbose*"
"*debug-io*" "*debugger-hook*" "*default-pathname-defaults*"
"*error-output*" "*features*" "*gensym-counter*" "*load-pathname*"
"*load-print*" "*load-truename*" "*load-verbose*" "*macroexpand-hook*"
"*modules*" "*package*" "*print-array*" "*print-base*" "*print-case*"
"*print-circle*" "*print-escape*" "*print-gensym*" "*print-length*"
"*print-level*" "*print-lines*" "*print-miser-width*"
"*print-pprint-dispatch*" "*print-pretty*" "*print-radix*"
"*print-readably*" "*print-right-margin*" "*query-io*" "*random-state*"
"*read-base*" "*read-default-float-format*" "*read-eval*" "*read-suppress*"
"*readtable*" "*standard-input*" "*standard-output*" "*terminal-io*"
"*trace-output*" "+" "++" "+++" "-" "/" "//" "///" "array-dimension-limit"
"array-rank-limit" "array-total-size-limit" "boole-1" "boole-2" "boole-and"
"boole-andc1" "boole-andc2" "boole-c1" "boole-c2" "boole-clr" "boole-eqv"
"boole-ior" "boole-nand" "boole-nor" "boole-orc1" "boole-orc2" "boole-set"
"boole-xor" "call-arguments-limit" "char-code-limit" "double-float-epsilon"
"double-float-negative-epsilon" "internal-time-units-per-second"
"lambda-list-keywords" "lambda-parameters-limit"
"least-negative-double-float" "least-negative-long-float"
"least-negative-normalized-double-float"
"least-negative-normalized-long-float"
"least-negative-normalized-short-float"
"least-negative-normalized-single-float" "least-negative-short-float"
"least-negative-single-float" "least-positive-double-float"
"least-positive-long-float" "least-positive-normalized-double-float"
"least-positive-normalized-long-float"
"least-positive-normalized-short-float"
"least-positive-normalized-single-float" "least-positive-short-float"
"least-positive-single-float" "long-float-epsilon"
"long-float-negative-epsilon" "most-negative-double-float"
"most-negative-fixnum" "most-negative-long-float"
"most-negative-short-float" "most-negative-single-float"
"most-positive-double-float" "most-positive-fixnum"
"most-positive-long-float" "most-positive-short-float"
"most-positive-single-float" "multiple-values-limit" "nil" "pi"
"short-float-epsilon" "short-float-negative-epsilon" "single-float-epsilon"
"single-float-negative-epsilon" "t"))
#+end_src
*** [[https://www.n16f.net/blog/custom-font-lock-configuration-in-emacs/][Finalize Common Lisp custom font locking]]
:PROPERTIES:
:CUSTOM_ID: sec:cl-custom-font-locking-final
:END:
#+caption[Finalize =Common Lisp= custom font locking]:
#+caption: Finalize =Common Lisp= custom font locking.
#+name: lst:finalize-cl-custom-font-locking
#+begin_src emacs-lisp -n :results silent
(defvar cl-font-lock-keywords
(let* ((character-re (concat "#\\\\" lisp-mode-symbol-regexp "\\_>"))
(function-re (concat "(" (regexp-opt cl-function-names t) "\\_>"))
(value-re (regexp-opt cl-value-names 'symbols)))
`((,character-re . 'cl-character-face)
(,function-re
(1 'cl-standard-function-face))
(,value-re . 'cl-standard-value-face))))
(defvar cl-font-lock-defaults
'((cl-font-lock-keywords)
nil ; enable syntaxic highlighting
t ; case insensitive highlighting
nil ; use the lisp-mode syntax table
(font-lock-mark-block-function . mark-defun)
(font-lock-extra-managed-props help-echo)
(font-lock-syntactic-face-function
. lisp-font-lock-syntactic-face-function)))
(defun cl-init-lisp-font-lock ()
"Hook to initialize Common Lisp font locking."
(setq font-lock-defaults cl-font-lock-defaults))
(add-hook 'lisp-mode-hook 'cl-init-lisp-font-lock)
#+end_src
** [[info:eintr#Top][Emacs Lisp Programming (info)]]
:PROPERTIES:
:CUSTOM_ID: sec:emacs-lisp-programming
:END:
Here is a list of links describing how to program and debug [[info:elisp#Top][Emacs Lisp (info)]] code:
1. [[https://www.masteringemacs.org/article/evaluating-elisp-emacs][Evaluating Elisp in Emacs]]
2. [[https://endlessparentheses.com/debugging-emacs-lisp-part-1-earn-your-independence.html][Debugging Elisp Part 1: Earn your independence]]
3. [[https://endlessparentheses.com/debugging-elisp-part-2-advanced-topics.html][Debugging Elisp Part 2: Advanced topics]]
4. [[http://xahlee.info/talk_show/xah_talk_show_2022-01-20.html][Xah talk show: Elisp coding: xah-add-space-after-comma]]
5. [[http://xahlee.info/talk_show/xah_talk_show_2022-01-22.html][Xah talk show: Elisp coding: narrow-to-region, sort-lines, hilight-unicode]]
Ref. [cite:@Monnier.ACM-PL.4.1] exposes the evolution of Emacs Lisp and explains
many Emacs Lisp idioms.
Listing [[lst:setup-ielm][setup ielm]] configures the [[https://wikemacs.org/wiki/IELM][Interactive Emacs Lisp Mode]] for better
interoperability with [[https://smartparens.readthedocs.io/en/latest/][Smartparens]]: get help on the key bindings by means of
src_emacs-lisp[:results silent]{(describe-function 'ielm)}.
#+caption[Setup =ielm=]:
#+caption: Setup =ielm=.
#+name: lst:setup-ielm
#+begin_src emacs-lisp -n :results silent
(with-eval-after-load 'ielm
(setopt ielm-dynamic-return nil))
#+end_src
*** [[https://lists.gnu.org/archive/html/emacs-devel/2023-11/msg00764.html][Benchmarking Emacs Lisp]]
:PROPERTIES:
:CUSTOM_ID: sec:benchmarking
:header-args:emacs-lisp: :exports code :tangle no
:END:
The following snippets present and use a [[https://lists.gnu.org/archive/html/emacs-devel/2023-11/msg00764.html][copy]] of João Távora's code to benchmark
Emacs Lisp forms.
#+caption[Define macro to benchmark groups of forms]:
#+caption: Define macro to benchmark groups of forms.
#+name: lst:benchmark-group
#+begin_src emacs-lisp -n :results silent
(defvar joaot/bench-group-name nil)
(defvar joaot/setup-form nil)
(defvar joaot/timings-alist nil)
(defvar joaot/repetitions nil)
(defmacro joaot/with-benchmark-group (bench-name
setup-form
repetitions
&rest body)
(declare (indent 1))
(let ((f `(alist-get ,bench-name joaot/timings-alist nil nil #'equal)))
`(let ((joaot/bench-group-name ,bench-name)
(joaot/setup-form ',setup-form)
(joaot/repetitions ,repetitions))
(setf ,f (list))
,@body
(cl-loop with sorted = (setf ,f (cl-sort ,f #'< :key #'cadr))
with fastest = (cadr (car sorted))
for i from 0
for res in sorted
for prev-time = time
for (_n time . more) = res
do (setcdr res
(cl-list* (if (eq time fastest) "FASTEST"
(format "%2.1fx %sSLOWER"
(/ time fastest)
(if (= i 1) ""
(format "(rel %2.1fx) "
(/ time prev-time)))))
time
more)))
joaot/timings-alist)))
#+end_src
#+caption[Define macro to benchmark forms]:
#+caption: Define macro to benchmark forms.
#+name: lst:benchmark-form
#+begin_src emacs-lisp -n :results silent
(defmacro joaot/bench (form)
`(cl-progv
(mapcar #'car joaot/setup-form)
(mapcar #'eval (mapcar #'cdr joaot/setup-form))
(let ((res
(benchmark-call (prog1
(,(if (native-comp-available-p)
'native-compile
'byte-compile)
'(lambda () ,form))
(garbage-collect))
joaot/repetitions))
(group-name joaot/bench-group-name)
(bench-name ',(car form)))
(push (cons bench-name res)
(alist-get group-name joaot/timings-alist nil nil #'equal)))))
#+end_src
#+caption[Define benchmark helper functions]:
#+caption: Define benchmark helper functions.
#+name: lst:benchmark-helpers
#+begin_src emacs-lisp -n :results silent
(defun bench-cl-loop-list (l)
(cl-loop for e in l thereis (identity e)))
(defun bench-cl-loop-vec (v)
(cl-loop for e across v thereis (identity e)))
(defun bench-cl-some (seq)
(cl-some #'identity seq))
(defun bench-seq-some (seq)
(seq-some #'identity seq))
#+end_src
#+caption[Benchmark =some= operations on big lists]:
#+caption: Benchmark =some= operations on big lists.
#+name: lst:benchmark-some-big-lists
#+begin_src emacs-lisp -n :eval no-export :results pp silent
(joaot/with-benchmark-group "\"some\" operation, big lists"
((list1 . (make-list 100000 nil)))
100
(joaot/bench (bench-cl-loop-list list1))
(joaot/bench (cl-some #'identity list1))
(joaot/bench (seq-some #'identity list1)))
#+end_src
#+caption[Benchmark =some= operations on small lists]:
#+caption: Benchmark =some= operations on small lists.
#+name: lst:benchmark-some-small-lists
#+begin_src emacs-lisp -n :eval no-export :results pp silent
(joaot/with-benchmark-group "\"some\" operation, small lists"
((list1 . (make-list 10 nil)))
100000
(joaot/bench (bench-cl-loop-list list1))
(joaot/bench (cl-some #'identity list1))
(joaot/bench (seq-some #'identity list1)))
#+end_src
#+caption[Accumulated results of benchmark examples]:
#+caption: Accumulated results of benchmark examples.
#+name: lst:benchmark-example
#+header: :wrap "src emacs-lisp -n"
#+begin_src emacs-lisp -n :eval no-export :exports both :results pp
(joaot/with-benchmark-group "destructuring"
((list1 . (make-list 3 0)))
100000
(joaot/bench (pcase-let ((`(,a ,b ,c) list1)) (+ a b c)))
(joaot/bench (cl-destructuring-bind (a b c) list1 (+ a b c)))
(joaot/bench (seq-let (a b c) list1 (+ a b c))))
#+end_src
#+caption[Accumulated results of benchmark examples]:
#+caption: Accumulated results of benchmark examples.
#+name: lst:benchmark-example-results
#+RESULTS: lst:benchmark-example
#+begin_src emacs-lisp -n
(("\"some\" operation, small lists" (cl-some "FASTEST" 0.027944 0 0.0)
(seq-some "2.6x SLOWER" 0.073602 0 0.0)
(bench-cl-loop-list "134.4x (rel 51.0x) SLOWER" 3.7544939999999998
12 2.701254000000006))
("\"some\" operation, big lists" (cl-some "FASTEST" 0.242062 0 0.0)
(seq-some "2.5x SLOWER" 0.614304 0 0.0)
(bench-cl-loop-list "14.5x (rel 5.7x) SLOWER" 3.505071 0 0.0))
("destructuring"
(cl-destructuring-bind "FASTEST" 0.013874999999999998 0 0.0)
(pcase-let "1.2x SLOWER" 0.016648 0 0.0)
(seq-let "2.8x (rel 2.4x) SLOWER" 0.039485 0 0.0)))
#+end_src
*** [[info:elisp#Debugging][Debugging Emacs Lisp (info)]]
:PROPERTIES:
:CUSTOM_ID: sec:debugging
:header-args:emacs-lisp: :exports code :tangle no
:END:
Listing [[lst:example-edebug][edebug example]] from the [[https://www.emacswiki.org/emacs/SourceLevelDebugger][source level debugger (edebug)]] wiki entry is a
minimal example of how to use =edebug=:
1. Type {{{kbd(C-c ')}}} to open its =org-src-mode= buffer.
2. Type {{{kbd(C-M-x)}}} with point in =foo=.
3. Type {{{kbd(C-M-x)}}} with point in =bar=.
4. Evaluate =(foo)=.
5. Type {{{kbd(C-u C-M-x)}}} with point in =bar=.
6. Evaluate =(foo)=.
7. Type the space bar to step through =bar=.
Note: I fail to instrument functions as in [[info:elisp#Edebug][Edebug (info)]].
#+caption[Example for =edebug=]:
#+caption: Example for =edebug=.
#+name: lst:example-edebug
#+begin_src emacs-lisp -n :exports code :results silent
(defun foo ()
(interactive)
(bar))
(defun bar ()
(let ((a 5)
(b 7))
(message "%d" (+ a b))))
#+end_src
*** [[https://emacs.stackexchange.com/questions/2129/why-is-let-faster-with-lexical-scope][Disassemble dynamical and lexical scope]]
:PROPERTIES:
:CUSTOM_ID: sec:disassemble-dynamical-lexical-scope
:header-args:emacs-lisp: :exports code :tangle no
:END:
Listing [[lst:disassemble-lexical-scope][lexical scope disassembly]] and [[lst:disassemble-dynamical-scope][dynamical scope disassembly]] disassemble
identical code in case of respectively lexical and dynamical scope. Listing
[[lst:disassemble-lexical-scope-results][lexical scope disassembly results]] and [[lst:disassemble-dynamical-scope-results][dynamical scope disassembly results]] show
the respective results.
The link [[https://emacs.stackexchange.com/questions/61754/how-can-i-enable-lexical-binding-for-elisp-code-in-org-mode][how to enable lexical scope for Emacs Lisp in org-mode source blocks]]
explains all possibilities of disabling or enabling lexical scope.
#+caption[Disassemble lexical scope]:
#+caption: Disassemble lexical scope.
#+name: lst:disassemble-lexical-scope
#+header: :wrap "src text -n"
#+begin_src emacs-lisp -n :exports both :lexical t :results value
(with-temp-buffer
(disassemble (lambda (n)
"Demonstrate lexical scope."
(let* ((lower most-negative-fixnum)
(upper most-positive-fixnum)
(sum (+ lower upper)))
(expt sum n)))
(current-buffer))
(buffer-substring-no-properties (point-min) (point-max)))
#+end_src
#+caption[Disassemble lexical scope results]:
#+caption: Disassemble lexical scope results.
#+name: lst:disassemble-lexical-scope-results
#+RESULTS: lst:disassemble-lexical-scope
#+begin_src text -n
byte code:
doc: Demonstrate lexical scope. ...
args: (arg1)
0 varref most-negative-fixnum
1 varref most-positive-fixnum
2 stack-ref 1
3 stack-ref 1
4 plus
5 constant expt
6 stack-ref 1
7 stack-ref 5
8 call 2
9 return
#+end_src
#+caption[Disassemble dynamical scope]:
#+caption: Disassemble dynamical scope.
#+name: lst:disassemble-dynamical-scope
#+header: :wrap "src text -n"
#+begin_src emacs-lisp -n :exports both :results value
(with-temp-buffer
(disassemble (lambda (n)
"Demonstrate lexical scope."
(let* ((lower most-negative-fixnum)
(upper most-positive-fixnum)
(sum (+ lower upper)))
(expt sum n)))
(current-buffer))
(buffer-substring-no-properties (point-min) (point-max)))
#+end_src
#+caption[Disassemble dynamical scope results]:
#+caption: Disassemble dynamical scope results.
#+name: lst:disassemble-dynamical-scope-results
#+RESULTS: lst:disassemble-dynamical-scope
#+begin_src text -n
byte code:
doc: Demonstrate lexical scope.
args: (n)
0 varref most-negative-fixnum
1 varbind lower
2 varref most-positive-fixnum
3 varbind upper
4 varref lower
5 varref upper
6 plus
7 varbind sum
8 constant expt
9 varref sum
10 varref n
11 call 2
12 unbind 3
13 return
#+end_src
** [[https://go.dev/][Go Programming]]
:PROPERTIES:
:CUSTOM_ID: sec:go-programming
:END:
1. [[https://gobyexample.com/][Go by example]]
#+caption[Setup Go programming]:
#+caption: Setup Go programming.
#+name: lst:setup-go
#+begin_src emacs-lisp -n :results silent
(when (featurep 'treesit)
;; Set `tab-width' in `after-change-major-mode-hook' function too.
(setopt go-ts-mode-indent-offset 2)
(add-hook 'after-change-major-mode-hook
(defun on-go-ts-mode-hook ()
(when (derived-mode-p 'go-ts-mode 'go-mod-ts-mode)
(setq-local tab-width 2))))
(add-to-list 'auto-mode-alist `(,(rx ".go" eos) . go-ts-mode)))
#+end_src
** [[https://www.seas.upenn.edu/~chaoliu/2017/09/01/python-programming-in-emacs/][Python programming]]
:PROPERTIES:
:CUSTOM_ID: sec:python-programming
:END:
The [[https://www.emacswiki.org/emacs/PythonProgrammingInEmacs][Python Programming in Emacs]] wiki page lists options to enhance Emacs's
built-in ~python-mode~. Here, the focus is on two Emacs packages and four Python
packages:
1. [[#sec:eglot][Eglot - Emacs polyGLOT: a builtin LSP client since Emacs-29.1]]. The
maintainer also contributes to Emacs itself and has a deep understanding of
[[https://sheer.tj/the_way_of_emacs.html][the Way of Emacs]]. He refuses to add new features without seeing how they fit
into [[https://sheer.tj/the_way_of_emacs.html][the Way of Emacs]] as this discussion on [[https://github.com/joaotavora/eglot/issues/523][org-mode source code blocks]]
shows.
2. [[https://jedi.readthedocs.io/en/latest/][Jedi]] is a static analysis tool for Python that is typically used in plugins
for editors or integrated development environments. Jedi has a focus on
autocompletion and object definition lookup functionality.
3. Nowadays, [[https://github.com/charliermarsh/ruff#readme][Ruff]] is the fastest Python linter and a replacement for [[https://flake8.pycqa.org/en/latest/][Flake8]] with
a variety of its plugins. [[https://notes.crmarsh.com/][Charlie Marsh]] explains why he started [[https://pypi.org/project/ruff/][Ruff]] in the
post [[https://notes.crmarsh.com/python-tooling-could-be-much-much-faster][Python tooling could be much faster]].
4. [[https://github.com/python-lsp/python-lsp-server#readme][Python LSP Server]] is in my opinion the most complete [[https://en.wikipedia.org/wiki/Language_Server_Protocol][Language Server Protocol]]
implementation for Python and it handles [[https://github.com/charliermarsh/ruff#readme][Ruff]] thanks to the [[https://github.com/python-lsp/python-lsp-ruff][python-lsp-ruff]]
plugin.
5. [[https://github.com/astoff/code-cells.el#readme][Code-cells]] allows to edit [[https://ipython.org/notebook.html][IPython or Jupyter notebooks]] in Emacs with help
from either [[https://github.com/mwouts/jupytext][Jupytext]] or [[https://pandoc.org/][Pandoc]].
Here are links covering how to integrate Emacs, Python and a Python LSP
server, before plunging into the configuration steps:
1. [[https://taingram.org/blog/emacs-lsp-ide.html][Building Your Own Emacs IDE with LSP]]
2. [[https://rgoswami.me/posts/emacs-lang-servers/][Doom Emacs and Language Servers]]
3. [[https://ddavis.io/posts/eglot-python-ide/][Eglot based Emacs Python IDE]]
4. [[https://www.mattduck.com/lsp-python-getting-started.html][Getting started with lsp-mode for Python]]
5. [[https://ddavis.io/posts/python-emacs-3/][Python & Emacs, Take 3]]
6. [[https://ddavis.io/posts/emacs-python-lsp/][Python with Emacs: py(v)env and lsp-mode]]
Here are links to Python programming background videos:
1. [[yt:HTLu2DFOdTg][Python's Class Development Kit - Raymond Hettinger]]
2. [[yt:UANN2Eu6ZnM][The Mental Game of Python - Raymond Hettinger]]
*** [[https://git.savannah.gnu.org/cgit/emacs.git/tree/lisp/progmodes/python.el][Python-mode]]
:PROPERTIES:
:CUSTOM_ID: sec:python-mode
:END:
Listing [[lst:setup-python-mode][setup Python mode]] selects a common Python interpreter in a virtual
environment for use in =python-mode= and =ob-python=. The [[https://github.com/pythonic-emacs/pythonic#readme][pythonic]], [[https://github.com/jorgenschaefer/pyvenv#readme][pyvenv]], and
[[https://github.com/jorgenschaefer/pyvenv#readme][pyvenv]] packages allow to handle Python virtual environments within Emacs. The
[[https://github.com/pyenv/pyenv][pyenv]] package provides support to work with [[https://github.com/pyenv/pyenv#readme][pyenv]] (eventually with
[[https://github.com/pyenv/pyenv-virtualenv#readme][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 [[https://github.com/pyenv/pyenv#readme][pyenv]] and [[https://github.com/pythonic-emacs/pythonic#readme][pythonic]]) or tweak the
environment variables and restart the relevant Python child processes (in case
of [[https://github.com/jorgenschaefer/pyvenv#readme][pyvenv]]). Therefore, I replace those packages with listing [[lst:access-pyenv][access pyenv]] and
[[lst:select-python-virtual-environment][select a Python interpreter in a virtual environment]] to set
=python-shell-virtualenv-root= from within Emacs.
Install [[https://github.com/PyCQA/isort#readme][isort]] and [[https://github.com/PyCQA/pyflakes#readme][pyflakes]] to enable adding, removing, sorting, and fixing
import statements in [[https://git.savannah.gnu.org/cgit/emacs.git/tree/lisp/progmodes/python.el][Python-mode]], see the end of the commentary
src_emacs-lisp[:results silent]{(find-library "python")} section.
Listing [[lst:pyproject-toml-kickoff][kickoff pyproject.toml proposal]] facilitates dropping a [[https://pip.pypa.io/en/stable/reference/build-system/pyproject-toml][pyproject.toml]]
file into Python projects which anyhow should switch to using a [[https://pip.pypa.io/en/stable/reference/build-system/pyproject-toml][pyproject.toml]]
file as explained in the post [[https://bbc.github.io/cloudfit-public-docs/packaging/this_way_up.html][This Way Up: A Bottom-Up Look At Python Packaging]].
The [[https://packaging.python.org/en/latest/tutorials/packaging-projects/][packaging Python projects tutorial]] and the [[https://github.com/pypa/sampleproject][Python sample project]] walk you
through the process of writing a [[https://pip.pypa.io/en/stable/reference/build-system/pyproject-toml][pyproject.toml]] file. [[https://github.com/charliermarsh/ruff#does-ruff-support-numpy--or-google-style-docstrings][Ruff docstring setup]]
explains how to setup documentation string checking in the [[https://pip.pypa.io/en/stable/reference/build-system/pyproject-toml][pyproject.toml]] file.
Listing [[lst:setup-cfg-kickoff][kickoff setup.cfg proposal]] implements the [[https://black.readthedocs.io/en/stable/guides/using_black_with_other_tools.html][using black with other tools]]
rules which make [[https://flake8.pycqa.org/en/latest/][flake8]] or [[https://pycodestyle.pycqa.org/en/latest/][pycodestyle]] agree with [[https://black.readthedocs.io/en/stable/index.html][black's uncompromising style]].
Finally, listing [[lst:flake8-nocolor][flake8-nocolor]] and [[lst:ruff-nocolor][ruff-nocolor]] pipe the =stdout= output of the
[[https://pypi.org/project/flake8/][flake8]] and [[https://pypi.org/project/ruff/][ruff]] executables through =cat= to remove escape sequences.
#+caption[Setup Python mode with =ob-python=]:
#+caption: Setup Python mode with =ob-python=.
#+name: lst:setup-python-mode
#+begin_src emacs-lisp -n :results silent
(with-eval-after-load 'ob-python
(setopt org-babel-python-command (concat (or (executable-find "python3")
(executable-find "python"))
" -E")))
(with-eval-after-load 'python
(setopt python-indent-guess-indent-offset nil
python-shell-completion-native-disabled-interpreters '("ipython3"
"pypy")
python-shell-interpreter (or (executable-find "python3")
(executable-find "python"))
python-shell-interpreter-args "-E -i"
python-check-command (executable-find "ruff-nocolor")
python-flymake-command (list (executable-find "ruff-nocolor")
"--stdin-filename" "stdin" "-")))
#+end_src
#+caption[Access =pyenv=]:
#+caption: Access =pyenv=.
#+name: lst:access-pyenv
#+begin_src emacs-lisp -n :results silent
(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))))))
#+end_src
#+caption[Select the Python virtual environment]:
#+caption: Select the Python virtual environment.
#+name: lst:select-python-virtual-environment
#+begin_src emacs-lisp -n :results silent
(with-eval-after-load 'python
(when (cl-every #'fboundp '(pyenv-full-path
pyenv-version-name
pyenv-versions
pyenv-virtualenvs))
(setq python-shell-virtualenv-root
(pyenv-full-path (or (pyenv-version-name)
(car (pyenv-virtualenvs))
(car (pyenv-versions)))))
(message "Now `python-shell-virtualenv-root' equals %S"
python-shell-virtualenv-root)
(defun set-python-shell-virtualenv-root-to-pyenv-version ()
"Set `python-shell-virtual-env-root' to a pyenv version."
(interactive)
(let* ((version-name (pyenv-version-name))
(prompt (format "pyenv version (%s): " version-name))
(choices (pyenv-versions))
(version (completing-read prompt choices nil 'require-match)))
(unless (string= version-name version)
(setq python-shell-virtualenv-root (pyenv-full-path version))
(setenv "PYENV_VERSION" version))
(message "Now `python-shell-virtualenv-root' equals %S"
python-shell-virtualenv-root)))
(defun set-python-shell-virtualenv-root-to-pyenv-virtualenv ()
"Set `python-shell-virtual-env-root' to a pyenv virtualenv."
(interactive)
(let* ((version-name (pyenv-version-name))
(prompt (format "pyenv virtualenv (%s): " version-name))
(choices (pyenv-virtualenvs))
(version (completing-read prompt choices nil 'require-match)))
(unless (string= version-name version)
(setq python-shell-virtualenv-root (pyenv-full-path version))
(setenv "PYENV_VERSION" version))
(message "Now `python-shell-virtualenv-root' equals %S"
python-shell-virtualenv-root)))))
#+end_src
#+caption[Kickoff =pyproject.toml= proposal]:
#+caption: Kickoff =pyproject.toml= proposal.
#+name: lst:pyproject-toml-kickoff
#+begin_src toml -n :tangle pyproject.toml
# See:
# https://docs.astral.sh/ruff/configuration/
# https://pycqa.github.io/isort/docs/configuration/black_compatibility.html
# https://setuptools.pypa.io/en/latest/userguide/pyproject_config.html
[tool.black]
line-length = 88
[tool.isort]
profile = "black"
[tool.ruff]
line-length = 88
select = [
"ARG", # flake8-unused-arguments
"B", # flake8-bugbear
"C", # mccabe
"C4", # flake8-comprehensions
"E", # pycodestyle
"D", # pydocstyle
"F", # pyflakes
"UP", # pyupgrade
"W", # pycodestyle
]
ignore = [
"B905", # `zip()` without an explicit `strict=` parameter
"D202", # no blank lines allowed after function docstring
]
[tool.ruff.mccabe]
max-complexity = 15
[tool.ruff.pydocstyle]
convention = "numpy"
# Local Variables:
# mode: conf-toml-mode
# End:
#+end_src
#+caption[Kickoff =setup.cfg= proposal]:
#+caption: Kickoff =setup.cfg= proposal.
#+name: lst:setup-cfg-kickoff
#+begin_src toml -n :tangle setup.cfg
[flake8]
docstring-convention = numpy
extend-select = B,F,W
extend-ignore = W503
max-complexity = 15
max-line-length = 88
[pycodestyle]
ignore = W503
max-line-length = 88
# Local Variables:
# mode: conf-toml
# End:
#+end_src
#+caption[Wrap =flake8= to remove color from text output]:
#+caption: Wrap =flake8= to remove color from text output.
#+header: :tangle-mode (identity #o755)
#+name: lst:flake8-nocolor
#+begin_src shell -n :eval never :tangle ~/bin/flake8-nocolor
#!/bin/sh
flake8 "$@" | cat
# Local Variables:
# mode: shell-script
# sh-indentation: 2
# sh-basic-offset: 2
# End:
#+end_src
#+caption[Wrap =ruff= to remove color from text output]:
#+caption: Wrap =ruff= to remove color from text output.
#+header: :tangle-mode (identity #o755)
#+name: lst:ruff-nocolor
#+begin_src shell -n :eval never :tangle ~/bin/ruff-nocolor
#!/bin/sh
ruff "$@" | cat
# Local Variables:
# mode: shell-script
# sh-indentation: 2
# sh-basic-offset: 2
# End:
#+end_src
*** Org Babel Ackermann Python test
:PROPERTIES:
:CUSTOM_ID: sec:obap-test
:END:
#+caption[Org Babel Ackermann Python test]:
#+caption: Org Babel Ackermann Python test.
#+name: lst:obap-test
#+header: :wrap "src text -n"
#+begin_src python -n :eval never-export :exports both :session Python
# https://rosettacode.org/wiki/Ackermann_function#Python
# https://www.youtube.com/watch?v=i7sm9dzFtEI
def ack(M, N):
if M == 0:
return N + 1
elif N == 0:
return ack(M - 1, 1)
else:
return ack(M - 1, ack(M, N - 1))
ack(3, 3)
#+end_src
#+caption[Org Babel Ackermann Python test result]:
#+caption: Org Babel Ackermann Python test result.
#+name: lst:obap-test-result
#+RESULTS: lst:obap-test
#+begin_src text -n
61
#+end_src
*** [[https://pip.pypa.io/en/stable/][Package Installer for Python]]
:PROPERTIES:
:CUSTOM_ID: sec:pip-python
:header-args: :eval never-export
:END:
Listing [[lst:shell-pip-list-outdated]] and [[lst:shell-pip-index-versions]] are two
examples of invoking [[https://pip.pypa.io/en/stable/][pip]] in =org-mode= =shell= source blocks with the respective
results in listing [[lst:shell-pip-list-outdated-results]] and
[[lst:shell-pip-index-versions-results]].
#+caption[Show how to use =pip list --outdated=]:
#+caption: Show how to use =pip list --outdated=.
#+header: :wrap "src text -n"
#+name: lst:shell-pip-list-outdated
#+begin_src shell -n :exports both :results verbatim
pip list --outdated
#+end_src
#+caption[The result of =pip list --outdated=]:
#+caption: The result of =pip list --outdated=.
#+name: lst:shell-pip-list-outdated-results
#+RESULTS: lst:shell-pip-list-outdated
#+begin_src text -n
#+end_src
#+caption[Show how to use =pip index versions=]:
#+caption: Show how to use =pip index versions=.
#+header: :wrap "src text -n"
#+name: lst:shell-pip-index-versions
#+begin_src shell -n :exports both :results verbatim
# WARNING: "pip index" is currently an experimental command.
pip index versions circular-buffer --no-color
#+end_src
#+caption[The result of =pip index versions=]:
#+caption: The result of =pip index versions=.
#+name: lst:shell-pip-index-versions-results
#+RESULTS: lst:shell-pip-index-versions
#+begin_src text -n
circular-buffer (0.2.0)
Available versions: 0.2.0, 0.1.1, 0.1.0
#+end_src
#+caption[Emacs client "Package Installer for Python" and "PyPI" options]:
#+caption: Emacs client "Package Installer for Python" and "PyPI" options.
#+name: lst:pip-pypi-options
#+begin_src emacs-lisp -n :results silent
(defgroup pip nil
"Client for accessing the \"Package Installer for Python\" and \"PyPI\"."
:group 'applications)
(defcustom pip-frozen-packages nil
"Frozen Python packages."
:group 'pip
:type '(repeat string))
;; Updating "docutils" may require a compatible "Sphinx" release.
;; (setopt pip-frozen-packages '("docutils"))
#+end_src
#+caption[Emacs interface to list outdated Python packages]:
#+caption: Emacs interface to list outdated Python packages.
#+name: lst:pip-list-outdated
#+begin_src emacs-lisp -n :results silent
(defvar pip-outdated-packages nil
"Outdated Python packages.")
(defun pip--list-outdated-sentinel (process _event)
"Sentinel function for when the `pip-list-outdated' PROCESS succeeds."
(when (and (eq (process-status process) 'exit)
(zerop (process-exit-status process))
(buffer-live-p (process-buffer process)))
(with-current-buffer (process-buffer process)
(goto-char (point-min))
(setq pip-outdated-packages
(json-parse-buffer :array-type 'list :object-type 'alist))
(let ((alists pip-outdated-packages))
(while alists
(setcdr (assq 'name (car alists))
(string-replace "_" "-" (alist-get 'name (car alists))))
(setq alists (cdr alists))))
(kill-buffer)
(describe-variable 'pip-outdated-packages)
(message "Calling `%S' succeeded" #'pip-list-outdated))))
(defun pip-list-outdated ()
"Save the outdated Python packages in `pip-outdated-packages'.
This invokes an asynchronous process and finishes with a message."
(interactive)
(let ((command '("pip" "list" "--outdated" "--format" "json")))
(make-process
:name "pip-list-outdated"
:buffer (generate-new-buffer-name "*pip-list-outdated-output*")
:command command
:sentinel #'pip--list-outdated-sentinel)
(message "Running `%s' asynchronously" (string-join command " "))))
#+end_src
#+caption[Emacs interface to upgrade outdated unfrozen Python packages]:
#+caption: Emacs interface to upgrade outdated unfrozen Python packages.
#+name: lst:pip-upgrade-maybe
#+begin_src emacs-lisp :results silent
(defun pip--upgrade-maybe-sentinel (process _event)
"Sentinel function for when the `pip-upgrade-maybe' PROCESS succeeds."
(when (and (eq (process-status process) 'exit)
(buffer-live-p (process-buffer process)))
(display-buffer (process-buffer process))))
(defun pip-upgrade-maybe ()
"Install the latest version of outdated packages unless they are frozen.
This invokes an asynchronous process and finishes with displaying the process
buffer to check whether upgrading has made the dependencies incompatible."
(interactive)
(let (found)
(dolist (alist pip-outdated-packages found)
(let ((name (alist-get 'name alist))
(latest_version (alist-get 'latest_version alist)))
(unless (member name pip-frozen-packages)
(push (format "%s==%s" name latest_version) found))))
(if (consp found)
(let ((command
`("pip" "install" "--progress-bar" "off" ,@(nreverse found))))
(make-process
:name "pip-upgrade-maybe"
:buffer (generate-new-buffer-name "*pip-upgrade-maybe*")
:command command
:sentinel #'pip--upgrade-maybe-sentinel)
(message "Running `%s' asynchronously" (string-join command " ")))
(message "`pip-upgrade-maybe' found no packages to install"))))
#+end_src
*** [[https://github.com/joaotavora/eglot][Eglot]] for [[https://git.savannah.gnu.org/cgit/emacs.git/tree/lisp/progmodes/python.el][python-mode]]
:PROPERTIES:
:CUSTOM_ID: sec:eglot-python
:END:
Listing [[lst:configure-eglot+pylsp-ruff]] configures [[https://github.com/joaotavora/eglot][eglot]] for [[https://www.python.org][Python]] using the
[[https://github.com/python-lsp/python-lsp-server][python-lsp-server]] with the [[https://github.com/python-lsp/python-lsp-ruff#readme][pylsp-ruff]] plugin for the easiest way to let
[[https://github.com/python-lsp/python-lsp-server][python-lsp-server]] use [[https://pypi.org/project/ruff/][Ruff]]. It is better to uninstall the Python packages
[[https://github.com/hhatto/autopep8#readme][autopep8]], [[https://github.com/PyCQA/flake8][flake8]], [[https://github.com/PyCQA/pydocstyle#readme][pydocstyle]], [[https://github.com/PyCQA/pylint#readme][pylint]], and [[https://github.com/python-rope/rope#readme][rope]].
Listing [[lst:eglot-directory-variables-for-python]] shows a proper [[info:emacs#Directory Variables][.dir-locals.el]]
file in the root directory of any [[https://www.python.org][Python]] project to start [[https://github.com/joaotavora/eglot][eglot]] automatically
according to the configuration in listing [[lst:eglot-maybe-ensure]].
Type {{{kbd(M-x eglot-show-workspace-configuration)}}} to dump a =JSON=
representation of src_emacs-lisp[:results silent]{(describe-variable
'eglot-workspace-configuration)} for debugging. The comment in listing [[lst:minimal-eglot-setup][minimal
Eglot setup]] also shows how to decrease or to increase the verbosity of [[https://github.com/python-lsp/python-lsp-server][Python
LSP server]] log output for debugging.
#+caption[Configure =eglot= with =python-lsp-ruff=]:
#+caption: Configure =eglot= with =python-lsp-ruff=.
#+name: lst:configure-eglot+pylsp-ruff
#+begin_src emacs-lisp -n :results silent
(with-eval-after-load 'eglot
(setq-default
eglot-workspace-configuration
;; Enable the `:pylsp_ruff' plugin and ensure to disable the
;; `:flake8', `:mccabe', and `:pycodestye' plugins.
'(:pylsp (:plugins
(:pylsp_ruff
(:enabled t)
:flake8 ;; It is better to uninstall flake8
(:enabled :json-false)
:mccabe ;; It is better to uninstall mccabe
(:enabled :json-false)
:pycodestyle ;; It is better to uninstall pycodestyle
(:enabled :json-false)
:jedi
(:auto_import_modules ["numpy"])
:jedi_completion
(:cache_for ["astropy"]))))))
#+end_src
#+caption[A =.dir-locals.el= proposal to launch =eglot= automatically]:
#+caption: A =.dir-locals.el= file proposal for Python projects or Org-mode
#+caption: projects tangling Python files to launch =eglot= automatically.
#+name: lst:eglot-directory-variables-for-python
#+begin_src emacs-lisp -n :eval never :tangle dir-locals.el
;; A .dir-locals.el file proposal in the root of any
;; Python project or Org-mode project tangling Python files
;; to launch eglot automatically.
((nil ;; nil, since Emacs-29.1 filters out irrelevant variable names.
. ((eglot-workspace-configuration
;; Enable the `:pylsp_ruff' plugin and ensure to disable the
;; `:flake8', `:mccabe', and `:pycodestye' plugins.
. (:pylsp (:plugins
(:pylsp_ruff
(:enabled t)
:flake8 ;; It is better to uninstall flake8
(:enabled :json-false)
:mccabe ;; It is better to uninstall mccabe
(:enabled :json-false)
:pycodestyle ;; It is better to uninstall pycodestyle
(:enabled :json-false)
:jedi
(:auto_import_modules ["numpy"])
:jedi_completion
(:cache_for ["astropy"]))))))))
#+end_src
#+attr_latex: :booktabs yes :float table
#+caption[Eglot related key bindings]:
#+caption: Eglot related key-bindings.
#+name: tab:eglot-related-key-bindings
|-------------------------+----------------+------------------|
| command | key map | keys |
|-------------------------+----------------+------------------|
| xref-find-definition | global-map | {{{kbd(M-.)}}} |
| xref-pop | global-map | {{{kbd(M-\,)}}} |
| flymake-goto-next-error | eglot-mode-map | {{{kbd(C-c n)}}} |
| flymake-goto-prev-error | eglot-mode-map | {{{kbd(C-c p)}}} |
| eglot-rename | eglot-mode-map | {{{kbd(C-c r)}}} |
| eldoc-doc-buffer | eglot-mode-map | {{{kbd(C-h .)}}} |
|-------------------------+----------------+------------------|
*** [[https://jedi.readthedocs.io/en/latest/][Jedi]]
:PROPERTIES:
:CUSTOM_ID: sec:jedi
:END:
Listing [[lst:example-py]] is a [[https://www.python.org][Python]] example to test whether [[https://jedi.readthedocs.io/en/latest/][jedi]] in combination
with [[https://github.com/joaotavora/eglot][eglot]] works when coding for instance [[https://numpy.org/][numpy]] universal functions.
#+caption[Tangle the =example.py= file]:
#+caption: Tangle the =example.py= file.
#+name: lst:example-py
#+begin_src python -n :tangle example.py
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)
#+end_src
*** [[https://github.com/astoff/code-cells.el#readme][Code-cells]]
:PROPERTIES:
:CUSTOM_ID: sec:code-cells
:END:
[[https://github.com/astoff/code-cells.el#readme][Code-cells]] allows to edit [[https://ipython.org/notebook.html][IPython or Jupyter notebooks]] in Emacs with help from
either [[https://github.com/mwouts/jupytext][Jupytext]] or [[https://pandoc.org/][Pandoc]] to translate between the [[https://nbformat.readthedocs.io/en/latest/][ipynb]] format and different
plain text formats. Sofar, I have used [[https://github.com/astoff/code-cells.el#readme][code-cells]] to open [[https://nbformat.readthedocs.io/en/latest/][ipynb]] notebooks with
{{{kbd(C-x C-f)}}} in Emacs for viewing. Listing [[lst:configure-code-cells]]
configures =code-cells=.
#+caption[Configure =code-cells=]:
#+caption: Configure =code-cells=.
#+name: lst:configure-code-cells
#+begin_src emacs-lisp -n :results silent
(when (ensure-package-installation 'code-cells)
(with-eval-after-load 'code-cells
(let ((map code-cells-mode-map))
(keymap-set map "M-p" #'code-cells-backward-cell)
(keymap-set map "M-n" #'code-cells-forward-cell)
(keymap-set map "C-c C-c" #'code-cells-eval))))
#+end_src
*** TODO Look into: editing facilities
1. [[https://github.com/douglasdavis/numpydoc.el/blob/main/numpydoc.el][Emacs extension to insert numpy style docstrings in function definitions]]
* [[https://github.com/emacs-tw/awesome-emacs#library][Libraries]]
:PROPERTIES:
:CUSTOM_ID: sec:libraries
:END:
** [[info:dash.info#Top][Library dash.el (info)]]
:PROPERTIES:
:CUSTOM_ID: sec:dash-library
:END:
The library [[info:dash.info#Top][dash.el (info)]] positions itself as a modern alternative for the list
processing API of [[info:cl#Top][cl]]. It is a requirement of important packages in this Emacs
setup (for instance [[https://github.com/andras-simonyi/citeproc-el#readme][citeproc]], [[https://github.com/magit/magit#readme][magit]], and [[https://github.com/Fuco1/smartparens#readme][smartparens]]). Listing
[[lst:configure-dash]] enables fontification of =dash= macros and makes
=info-lookup-symbol= take into account the =dash= macros.
#+caption[Configure =dash= fontification and info lookup]:
#+caption: Configure =dash= fontification and info lookup.
#+name: lst:configure-dash
#+begin_src emacs-lisp -n :results silent
(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)))
#+end_src
** [[https://github.com/rejeep/f.el][Library f.el]]
:PROPERTIES:
:CUSTOM_ID: sec:f-library
:END:
The library [[https://github.com/rejeep/f.el][f.el]] positions itself as a modern API for working with files and
directories in Emacs. It is a requirement of the [[https://github.com/andras-simonyi/citeproc-el#readme][citeproc]] package in this Emacs
setup and requires no configuration.
** [[https://github.com/magnars/s.el][Library s.el]]
:PROPERTIES:
:CUSTOM_ID: sec:s-library
:END:
The library [[https://github.com/magnars/s.el][s.el]] positions itself as the long lost Emacs string manipulation
library. It is a requirement of the [[https://github.com/andras-simonyi/citeproc-el#readme][citeproc]] and [[https://github.com/bdarcus/citar#readme][citar]] packages in this Emacs
setup and requires no configuration.
* [[info:emacs#Minor Modes][Minor Modes (info)]]
:PROPERTIES:
:CUSTOM_ID: sec:minor-modes
:END:
** [[https://github.com/victorhge/iedit#readme][Synchronal multiple-region editing]]
:PROPERTIES:
:CUSTOM_ID: sec:synchronal-multiple-region-editing
:END:
#+caption[Enable =iedit=]:
#+caption: Enable =iedit=.
#+name: lst:enable-iedit
#+begin_src emacs-lisp -n :results silent
(when (ensure-package-installation 'iedit)
(require 'iedit nil 'noerror))
#+end_src
** [[https://github.com/lewang/ws-butler#readme][Unobtrusive whitespace trimming]]
:PROPERTIES:
:CUSTOM_ID: sec:unobtrusive-whitespace-trimming
:END:
#+caption[Configure =ws-butler=]:
#+caption: Configure =ws-butler=.
#+name: lst:configure-ws-butler
#+begin_src emacs-lisp -n :results silent
(when (and (ensure-package-installation 'ws-butler)
(require 'ws-butler nil 'noerror))
(setopt ws-butler-keep-whitespace-before-point nil)
(add-hook 'prog-mode-hook #'ws-butler-mode)
(add-hook 'text-mode-hook #'ws-butler-mode))
#+end_src
** [[https://countvajhula.com/2021/09/25/the-animated-guide-to-symex/][Structural editing]]
:PROPERTIES:
:CUSTOM_ID: sec:structural-editing
:END:
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 [[https://github.com/Fuco1/smartparens][smartparens]] which offers a normal mode (=smartparens-mode=) and a
strict mode (=smartparens-strict-mode=). Although both modes insert character
pairs, the normal mode allows to delete one of the paired characters easily
while the strict mode does not. Therefore, the strict mode is more for code
editing since it never breaks programming language rules and the normal mode is
more for text editing where structure is a matter of convention instead of
programming language rules.
For instance, the strict mode in Python allows to delete entire lists, tuples,
or the arguments after the cursor (what Emacs calls =point=) in a function call
without breaking the character pair balance. In order to repair a broken
character pair balance, insert a single character by prefixing it with "C-q"
bound to =quoted-insert=.
The [[https://smartparens.readthedocs.io/en/latest/index.html][smartparens documentation]] targets experienced Emacs users. The
following links show how to put the documentation to practical use:
1. The [[https://gist.github.com/pvik/8eb5755cc34da0226e3fc23a320a3c95][smartparens cheatsheet]] demonstrates its usage visually.
2. [[https://gist.github.com/oantolin][Omar Antolin's gist "my-smartparens-config.el"]] is the first place to look for
how to tweak [[https://github.com/Fuco1/smartparens][smartparens]]. However, the gist may be partially obsolete, since
it is not part of his current [[https://github.com/oantolin/emacs-config][Emacs configuration]].
3. [[https://lists.gnu.org/archive/html/help-gnu-emacs/2014-07/msg00135.html][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 [[https://github.com/Fuco1/smartparens][smartparens]] for Go, LaTeX,
Lisp dialects, Org, and Python. Execute src_emacs-lisp[:results
silent]{(sp-cheat-sheet)} for short documentation taking into account the
overridden key bindings in listing [[lst:configure-smartparens]]. Table
[[tab:smartparens-commands-and-bindings]] lists commands with key bindings taken in
order from src_emacs-lisp[:results silent]{(sp-cheat-sheet)} that takes the
overrides of listing [[lst:configure-smartparens]] into account.
Despite the provocative post [[https://andreyorst.gitlab.io/posts/2022-02-20-what-if-structural-editing-was-a-mistake/]["What if structural editing was a mistake?"]],
[[https://github.com/Fuco1/smartparens][smartparens]] is one of my favorite packages. In particular, [[https://github.com/Fuco1/smartparens][smartparens]] handles
matching pairs in Org-mode files with export and source blocks for multiple
formats and languages almost (exceptions are due to ~Search failed~ errors
inside source blocks) correctly. Therefore, [[https://smartparens.readthedocs.io/en/latest/index.html?highlight=show-smartparens-mode#getting-started][show-smartparens-mode]] highlights
matching pairs immediately in front or after point in such files correctly,
contrary to for instance [[https://github.com/Fanael/rainbow-delimiters#readme][rainbow-delimiters]].
#+caption[Configure =smartparens=]:
#+caption: Configure =smartparens=.
#+name: lst:configure-smartparens
#+begin_src emacs-lisp -n :results silent
(when (and (ensure-package-installation 'smartparens)
;; Require `smartparens-config' instead of `smartparens' to
;; disable pairing of the quote character for lisp modes.
(require 'smartparens-config nil 'noerror))
(setopt sp-base-key-bindings 'sp
sp-override-key-bindings '(("C-(" . sp-backward-slurp-sexp)
("C-)" . sp-forward-slurp-sexp)
("C-M-(" . sp-backward-barf-sexp)
("C-M-)" . sp-forward-barf-sexp)))
(dolist (symbol '(conf-toml-mode-hook prog-mode-hook text-mode-hook))
(add-hook symbol #'smartparens-mode))
(dolist (symbol '(emacs-lisp-mode-hook
geiser-repl-startup-hook
go-ts-mode-hook
ielm-mode-hook
lisp-data-mode-hook
lisp-mode-hook
python-mode-hook
sly-mrepl-mode-hook))
(add-hook symbol #'smartparens-strict-mode))
(when (fboundp 'go-ts-mode)
;; Stolen from `smartparens-go':
(sp-with-modes 'go-ts-mode
(sp-local-pair "{" nil :post-handlers '(("||\n[i]" "RET")))
(sp-local-pair "/*" "*/" :post-handlers '(("| " "SPC")
("* ||\n[i]" "RET"))))
;; Go has no sexp suffices. This fixes slurping:
;; (|foo).bar -> (foo.bar)
(add-to-list 'sp-sexp-suffix (list #'go-mode 'regexp "")))
;; https://xenodium.com/emacs-smartparens-auto-indent/index.html
(defun indent-between-pair (&rest _ignored)
(newline)
(indent-according-to-mode)
(forward-line -1)
(indent-according-to-mode))
(dolist (left '("(" "[" "{"))
(sp-local-pair 'prog-mode left
nil :post-handlers '((indent-between-pair "RET"))))
(show-smartparens-global-mode +1))
#+end_src
#+attr_latex: :booktabs yes :float table
#+caption[Smartparens commands with key bindings in this setup]:
#+caption: Smartparens commands with key bindings in this setup.
#+name: tab:smartparens-commands-and-bindings
|--------------------------------+----------------------------+----------|
| command | keys | status |
|--------------------------------+----------------------------+----------|
| sp-forward-sexp | {{{kbd(C-M-f)}}} | |
| sp-backward-sexp | {{{kbd(C-M-b)}}} | |
| sp-next-sexp | {{{kbd(C-M-n)}}} | |
| sp-prev-sexp | {{{kbd(C-M-p)}}} | |
| sp-down-sexp | {{{kbd(C-M-d)}}} | shadowed |
| sp-backward-down-sexp | {{{kbd(C-M-a)}}} | |
| sp-beginning-of-sexp | {{{kbd(C-S-d)}}} | |
| sp-end-of-sexp | {{{kbd(C-S-a)}}} | |
| sp-up-sexp | {{{kbd(C-M-e)}}} | |
| sp-backward-up-sexp | {{{kbd(C-M-u)}}} | |
| sp-kill-sexp | {{{kbd(C-M-k)}}} | |
| sp-copy-sexp | {{{kbd(C-M-w)}}} | |
| sp-forward-slurp-sexp | {{{kbd(C-))}}} | override |
| sp-backward-slurp-sexp | {{{kbd(C-()}}} | override |
| sp-forward-barf-sexp | {{{kbd(C-M-))}}} | override |
| sp-backward-barf-sexp | {{{kbd(C-M-()}}} | override |
| sp-forward-symbol | {{{kbd(M-F)}}} | |
| sp-backward-symbol | {{{kbd(M-B)}}} | |
| sp-unwrap-sexp | {{{kbd(M-<delete>)}}} | shadowed |
| sp-backward-unwrap-sexp | {{{kbd(M-<backspace>)}}} | |
| sp-splice-sexp | {{{kbd(M-D)}}} | |
| sp-splice-killing-backward | {{{kbd(C-M-<backspace>)}}} | |
| sp-splice-sexp-killing-forward | {{{kbd(C-M-<delete>)}}} | shadowed |
| sp-select-next-thing | {{{kbd(C-M-])}}} | |
| sp-select-next-thing-exchange | {{{kbd(C-])}}} | |
| sp-mark-sexp | {{{kbd(C-M-SPC)}}} | |
|--------------------------------+----------------------------+----------|
** [[https://github.com/davidshepherd7/electric-operator#readme][Electric operators]]
:PROPERTIES:
:CUSTOM_ID: sec:electric-operators
:END:
Listing [[lst:configure-electric-operator]] configures =electric-operator-mode= to
add spaces around operators for compatibility with for instance the [[https://black.readthedocs.io/en/stable/][Black code
formatter for Python]].
#+caption[Configure =electric-operator=]:
#+caption: Configure =electric-operator=.
#+name: lst:configure-electric-operator
#+begin_src emacs-lisp -n :results silent
(when (and (ensure-package-installation 'electric-operator)
(fboundp 'electric-operator-mode))
(add-hook 'c-mode-common-hook #'electric-operator-mode)
(add-hook 'python-mode-hook #'electric-operator-mode))
#+end_src
** [[https://joaotavora.github.io/yasnippet/][Smart snippets]]
:PROPERTIES:
:CUSTOM_ID: sec:smart-snippets
:END:
#+caption[Enable =yas-global-mode=]:
#+caption: Enable =yas-global-mode=.
#+name: lst:enable-yas-global-mode
#+begin_src emacs-lisp -n :results silent
(when (ensure-package-installation 'yasnippet)
;; Set `yas-alias-to-yas/prefix-p' before loading `yasnippet'.
(setopt yas-alias-to-yas/prefix-p nil)
(dolist (hook '(LaTeX-mode-hook
org-mode-hook
python-mode-hook
python-ts-mode-hook))
(add-hook hook #'yas-minor-mode)))
#+end_src
** [[info:autotype#Tempo][Tempo (info)]] :noexport:
:PROPERTIES:
:CUSTOM_ID: sec:tempo
:header-args:emacs-lisp: :tangle no :eval never-export
:END:
The official [[info:autotype#Tempo][tempo (info)]] documentation is very short and the following links
are obsolete or do not reveal the full power of [[info:autotype#Tempo][tempo (info)]] after its
adaptation to =lexical-binding=:
1. [[https://www.emacswiki.org/emacs/TempoMode][Tempo (Emacs Wiki)]].
2. [[https://www.lysator.liu.se/~davidk/elisp/][David Kågedals elisp]].
3. [[https://github.com/yilkalargaw/emacs-native-snippets][Emacs's Native Snippets]], [[https://www.reddit.com/r/emacs/comments/wdbk34/emacss_native_templating_and_snippet_fuctionality/][Emacs's native templating and snippet functionality]].
4. [[https://www.n16f.net/blog/templating-in-emacs-with-tempo/][Templating in Emacs with Tempo]].
Listing [[lst:configure-tempo-ui]] defines a function that inserts major modes
specific tempo templates by means of [[https://github.com/minad/marginalia][marginalia]], a function to define local
keybindings and the tools to add =tempo-tags= to abbreviation tables.
Listing [[lst:configure-tempo-latex-completing-read]] shows how to combine =tempo=,
=latex= and =completing-read= for the builtin =tempo= package. Listing
[[lst:setup-python-tempo]] configures =tempo= for =python-mode=.
#+caption[Configure =tempo= user interface]:
#+caption: Configure =tempo= user-interface.
#+name: lst:configure-tempo-ui
#+begin_src emacs-lisp -n :results silent
(when (require 'tempo nil 'noerror)
(setopt tempo-interactive t)
(with-eval-after-load 'marginalia
(add-to-list 'marginalia-prompt-categories
'("\\<tempo template\\>" . command)))
;; https://www.n16f.net/blog/templating-in-emacs-with-tempo/
(defun insert-tempo-template ()
"Insert a major mode specific tempo template."
(interactive)
(let* ((choices (mapcar #'cdr (tempo-build-collection)))
(function-name (completing-read "Tempo template: " choices)))
(funcall (intern function-name))))
(defun setup-local-tempo-key-bindings ()
"Initialize local `tempo' key bindings."
(keymap-local-set "M-n" #'tempo-forward-mark)
(keymap-local-set "M-p" #'tempo-backward-mark)
(keymap-local-set "M-+" #'insert-tempo-template))
(defun abbrev-tempo-expand-if-complete ()
"Hook function for `define-abbrev' with `no-self-insert' property `t'."
(tempo-expand-if-complete))
(put 'abbrev-tempo-expand-if-complete 'no-self-insert t)
(defun add-tempo-tags-to-abbrev-table (tempo-tags abbrev-table)
"Add the tags in TEMPO-TAGS to ABBREV-TABLE.
Allows `tempo' expansion by typing <SPC> after a complete `tempo' tag."
(dolist (tag tempo-tags)
(unless (abbrev-symbol (car tag) abbrev-table)
(define-abbrev abbrev-table
(car tag) 1 'abbrev-tempo-expand-if-complete)))))
#+end_src
*** Tempo for =LaTeX-mode= of =AUCTeX=
:PROPERTIES:
:CUSTOM_ID: sec:tempo-latex
:END:
#+caption[Configure =tempo= for =latex= with =completing-read=]:
#+caption: Configure =tempo= for =latex= with =completing-read=.
#+name: lst:configure-tempo-latex-completing-read
#+begin_src emacs-lisp -n :results silent
(with-eval-after-load 'latex
(require 'tempo nil 'noerror)
(defun tempo-latex-environment-handler (element)
(when (eq element 'latex-environment)
(let ((environment (completing-read "LaTeX environment: "
(LaTeX-environment-list)
#'always 'require-match)))
`(l "\\begin{" ,environment "}" > n > r n "\\end{" ,environment "}" >))))
(cl-pushnew 'tempo-latex-environment-handler tempo-user-elements)
(defun setup-tempo-latex ()
(defvar latex-tempo-tags nil)
(tempo-define-template
"LaTeX-environment"
'('latex-environment)
"env"
"Insert a LaTeX environment"
'latex-tempo-tags)
(tempo-use-tag-list 'latex-tempo-tags)
(add-tempo-tags-to-abbrev-table latex-tempo-tags latex-mode-abbrev-table)
(setup-local-tempo-key-bindings))
(add-hook 'LaTeX-mode-hook 'setup-tempo-latex))
#+end_src
*** Tempo for =python-mode=
:PROPERTIES:
:CUSTOM_ID: sec:tempo-python
:END:
#+caption[Configure =tempo= for =python-mode=]:
#+caption: Configure =tempo= for =python-mode=.
#+name: lst:setup-python-tempo
#+begin_src emacs-lisp -n :results silent
(with-eval-after-load 'python
(require 'tempo nil 'noerror)
(defun setup-tempo-python ()
(defvar python-tempo-tags nil)
(tempo-define-template
"python-base-class"
'("class " p ":" n >)
"clsb" "Define a base class" 'python-tempo-tags)
(tempo-define-template
"python-derived-class"
'("class " p "(" p "):" n >)
"clsd" "Define a derived class" 'python-tempo-tags)
(tempo-define-template
"python-class-method"
'("@classmethod" > n > "def " p "(cls, " p "):" n >)
"defc" "Define a class method" 'python-tempo-tags)
(tempo-define-template
"python-function"
'("def " p "(" p "):" n >)
"deff" "Define a function" 'python-tempo-tags)
(tempo-define-template
"python-normal-method"
'("def " p "(self, " p "):" n >)
"defm" "Define a normal method" 'python-tempo-tags)
(tempo-define-template
"python-static-method"
'("@staticmethod" > n > "def " p "(" p "):" n >)
"defs" "Define a static method" 'python-tempo-tags)
(tempo-use-tag-list 'python-tempo-tags)
(add-tempo-tags-to-abbrev-table python-tempo-tags python-mode-abbrev-table)
(setup-local-tempo-key-bindings))
(add-hook 'python-mode-hook 'setup-tempo-python))
#+end_src
* [[info:emacs#Display][Display (info)]]
:PROPERTIES:
:CUSTOM_ID: sec:display
:END:
** [[info:emacs#Narrowing][Narrowing]]
:PROPERTIES:
:CUSTOM_ID: sec:narrowing
:END:
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" [[https://endlessparentheses.com/emacs-narrow-or-widen-dwim.html][narrow-or-widen-dwim]] function allows to toggle between narrowed and
widened buffer states. Here, the function =narrow-or-widen-dwim= operates also
on any Org table, Org source block, Org block, or Org subtree.
#+caption[Configure =narrow-or-widen-dwim=]:
#+caption: Configure =narrow-or-widen-dwim=.
#+name: lst:configure-narrow-or-widen-dwim
#+begin_src emacs-lisp -n :results silent
(with-eval-after-load 'emacs
(declare-function org-at-table-p "org" (&optional table-type))
(defun org-narrow-to-table ()
"Narrow buffer to table at point."
(interactive)
(if (org-at-table-p)
(narrow-to-region (org-table-begin) (org-table-end))
(user-error "Not in a table")))
(defun narrow-or-widen-dwim (p)
"Widen a narrowed buffer, narrow \"Do What I Mean\" otherwise.
DWIM means: region, Org table, Org source block, Org block, Org
subtree, LaTeX environment, TeX group, or defun, whichever
applies first. Narrowing to org-src-block actually calls
`org-edit-src-code'.
With prefix P, don't widen, just narrow even if buffer is already
narrowed."
(interactive "P")
(declare (interactive-only))
(cond ((and (buffer-narrowed-p) (not p))
(widen))
((and (bound-and-true-p org-src-mode) (not p))
(org-edit-src-exit))
((region-active-p)
(narrow-to-region (region-beginning) (region-end)))
((derived-mode-p 'org-mode)
(or (with-demoted-errors "DWIM: %S" (org-narrow-to-table) t)
(with-demoted-errors "DWIM: %S" (org-edit-src-code) t)
(with-demoted-errors "DWIM: %S" (org-narrow-to-block) t)
(org-narrow-to-subtree)))
((derived-mode-p 'latex-mode)
(LaTeX-narrow-to-environment))
((derived-mode-p 'tex-mode)
(TeX-narrow-to-group))
(t
(narrow-to-defun))))
(keymap-set ctl-x-map "n t" #'org-narrow-to-table)
(keymap-set ctl-x-map "C-n" #'narrow-or-widen-dwim))
#+end_src
** [[https://jblevins.org/log/rainbow-mode][Visualize color codes and names]]
:PROPERTIES:
:CUSTOM_ID: sec:rainbow-mode
:END:
Listing [[lst:enable-rainbow-mode]] enables =rainbow-mode= to colorize color codes
and names in buffers for debugging.
#+caption[Enable =rainbow-mode=]:
#+caption: Enable =rainbow-mode=.
#+name: lst:enable-rainbow-mode
#+begin_src emacs-lisp -n :results silent
(when (and (ensure-package-installation 'rainbow-mode)
(fboundp 'rainbow-mode))
(setopt rainbow-x-colors-major-mode-list '(c++-mode
c-mode
emacs-lisp-mode
inferior-emacs-lisp-mode
java-mode
lisp-interaction-mode
org-mode
python-mode))
(rainbow-mode +1))
#+end_src
** [[https://karthinks.com/software/batteries-included-with-emacs/][Flash the line around point for visual feedback]]
:PROPERTIES:
:CUSTOM_ID: sec:flash-line-around-point
:END:
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.
#+caption[Implement =flash-line-around-point=]:
#+caption: Implement =flash-line-around-point=.
#+name: lst:flash-line-around-point
#+begin_src emacs-lisp -n :results silent
;; Ensure loading `pulse' with the dynamic variables `pulse-delay' and
;; `pulse-iterations' before masking them lexically instead of after
;; to prevent the warning triggered by lazy loading.
(when (require 'pulse nil 'noerror)
;; https://karthinks.com/software/batteries-included-with-emacs/
;; https://github.com/karthink/.emacs.d/blob/master/init.el#L2077
(defun flash-line-around-point (&rest _)
"Flash the line around point."
(let ((pulse-iterations 16)
(pulse-delay 0.1))
(pulse-momentary-highlight-one-line (point))))
(dolist (command '(scroll-up-command
scroll-down-command
recenter-top-bottom
other-window))
(advice-add command :after #'flash-line-around-point)))
#+end_src
* Applications
:PROPERTIES:
:CUSTOM_ID: sec:applications
:END:
** [[info:emacs#Hyperlinking][Hyperlinking and Web Navigation Features (info)]]
:PROPERTIES:
:CUSTOM_ID: sec:hyperlinking-web-navigating
:END:
*** [[info:emacs#Browse-URL][Browse URL (info)]]
:PROPERTIES:
:CUSTOM_ID: sec:browse-url
:END:
Listing [[lst:configure-browse-url]] configures =browse-url=.
#+attr_latex: :options breaklines
#+caption[Configure =browse-url=]:
#+caption: Configure =browse-url=.
#+name: lst:configure-browse-url
#+begin_src emacs-lisp -n :results silent
(with-eval-after-load 'browse-url
(defun browse-url-mpv (url &optional _)
(start-process "mpv" nil "mpv" url))
(defconst browse-url-mpv-regexp
(rx bos "http" (opt "s") "://"
(or (seq "youtu.be/")
(seq "www.youtube.com/" (or "embed/" "watch?"))
(seq (+? nonl) (or ".mp4" ".webm") eos)))
"Match hyperlinks to open with mpv.")
(setopt
browse-url-generic-program (or (when (eq system-type 'darwin) "open")
(executable-find "firefox"))
browse-url-handlers `((,browse-url-mpv-regexp . browse-url-mpv)
("." . eww-browse-url))))
#+end_src
*** [[https://en.wikipedia.org/wiki/Eww_(web_browser)][Emacs Web Wowser]]
:PROPERTIES:
:CUSTOM_ID: sec:emacs-web-wowser
:END:
#+caption[Configure =eww= URLs]:
#+caption: Configure =eww= URLs.
#+name: lst:configure-eww-urls
#+begin_src emacs-lisp -n :results silent
(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)))))
#+end_src
#+caption[Rename =eww= buffers and display =pdf= links properly]:
#+caption: Rename =eww= buffers and display =pdf= links properly.
#+name: lst:rename-eww-buffer-display-pdf-links
#+begin_src emacs-lisp -n :results silent
(with-eval-after-load 'eww
(defun eww-display-pdf-as-binary (fn &rest args)
(let ((buffer-file-coding-system 'binary))
(apply fn args)))
(defun eww-rename-buffer ()
"Rename the buffer using title or url."
(let* ((title (plist-get eww-data :title))
(name (or (and (eq "" title) (plist-get eww-data :url)) title)))
(rename-buffer (format "*%s # eww*" name) t)))
(add-hook 'eww-after-render-hook #'eww-rename-buffer)
(advice-add 'eww-display-pdf :around #'eww-display-pdf-as-binary))
#+end_src
*** [[https://www.gnu.org/software/hyperbole/][GNU Hyperbole]] :noexport:
:PROPERTIES:
:CUSTOM_ID: sec:ensure-hyperbole-installation
:header-args:emacs-lisp: :tangle no :eval never-export
:END:
Listing [[lst:ensure-hyperbole-installation][ensure Hyperbole installation]] works around two [[https://www.gnu.org/software/hyperbole/][GNU Hyperbole]] quirks:
1. The [[https://elpa.gnu.org/elpa/hyperbole.html][GNU ELPA Hyperbole]] and [[https://elpa.gnu.org/devel/hyperbole.html][GNU ELPA Hyperbole developer]] packages ship their
own =hyperbole-autoloads= and =kotl-autoloads= files which are incompatible
with Emacs Lisp packages from "package archives". Consequently, Emacs can
neither autoload nor load the =hyperbole-autoloads= and =kotl-autoloads=
files. Therefore, the predicate loading the =kotl-autoloads= file in the
first =unless= form of listing [[lst:ensure-hyperbole-installation][ensure Hyperbole installation]] will fail and
the body of this form will overwrite the =hyperbole-autoloads= file to fix =load-path= *and* load the =hyperbole-autoloads= and =kotl-autoloads= files.
A next time, Emacs will be able to autoload the new =hyperbole-autoloads=
file but not the =kotl-autoloads= file. However, Emacs will now be able to
load the =kotl-autoloads= file which the predicate in the first =unless= form
of listing [[lst:ensure-hyperbole-installation][ensure Hyperbole installation]] does.
2. [[https://www.gnu.org/software/hyperbole/][GNU Hyperbole]] and [[info:vertico#Top][Vertico (info)]] do not play well together, since the =vertico-mode= preselection of the first candidate out of the full candidate
completion list interferes with =hyperbole-mode= expectations where it must
handle a full candidate list. Therefore, the function call
src_emacs-lisp[:results silent]{(either-hyperbole-or-vertico-mode)} handles
this conflict by toggling between =hyperbole-mode= and =vertico-mode=.
#+caption[Ensure Hyperbole installation]:
#+caption: Ensure Hyperbole installation and address two Hyperbole quirks.
#+name: lst:ensure-hyperbole-installation
#+begin_src emacs-lisp -n :results silent
(when (ensure-package-installation 'hyperbole)
(unless (load "kotl-autoloads.el" 'noerror nil nil 'must-suffix)
;; See `package-generate-autoloads' for `loaddefs-generate' usage.
(unless (bound-and-true-p package-archive-contents)
(package-read-all-archive-contents))
(let* ((pkg-desc (cadr (assq 'hyperbole package-archive-contents)))
(pkg-dir (expand-file-name
(package-desc-full-name pkg-desc) package-user-dir))
(hpbl-alf (expand-file-name "hyperbole-autoloads.el" pkg-dir))
(kotl-alf (expand-file-name
(file-name-concat "kotl" "kotl-autoloads.el") pkg-dir))
(extra-data
(concat
(prin1-to-string
'(add-to-list
'load-path
(or (and load-file-name (file-name-directory load-file-name))
(car load-path))))
"\n"
(prin1-to-string
'(add-to-list
'load-path
(expand-file-name
"kotl"
(or (and load-file-name (file-name-directory load-file-name))
(car load-path)))))))
(autoload-timestamps nil)
(version-control 'never))
(loaddefs-generate pkg-dir hpbl-alf nil extra-data nil 'generate-full)
(dolist (alf (list hpbl-alf kotl-alf))
(let ((buf (find-buffer-visiting hpbl-alf)))
(when buf (kill-buffer buf)))
(load alf nil nil nil 'must-suffix))))
(defun either-hyperbole-or-vertico-mode ()
"Toggle between either `hyperbole-mode' or `vertico-mode'."
(interactive)
(when (and (boundp hyperbole-mode) (boundp vertico-mode))
(if hyperbole-mode
(progn
(hyperbole-mode -1)
(vertico-mode +1))
(hyperbole-mode +1)
(vertico-mode -1)))))
#+end_src
*** [[https://en.wikipedia.org/wiki/Media_type#Mailcap][Mailcap]]
:PROPERTIES:
:CUSTOM_ID: sec:mailcap
:END:
Extension packages as [[info:emacs#EWW][EWW (info)]], [[info:emacs#Gnus][Gnus (info)]], and [[info:org#Top][Org (info)]] rely on a [[https://en.wikipedia.org/wiki/Media_type#Mailcap][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.
#+caption[Tangle =mailcap= file]:
#+caption: Tangle =mailcap= file.
#+name: lst:tangle-mailcap-file
#+begin_src org -n :tangle ~/.mailcap
# https://emacs.stackexchange.com/a/24502 answers the question:
# How to use pdf-tools (pdf-view-mode) in emacs?
application/pdf; emacsclient %s
#+end_src
*** [[https://github.com/minad/osm#readme][Open Street Map viewer for Emacs]]
:PROPERTIES:
:CUSTOM_ID: sec:open-street-map
:END:
[[https://github.com/minad/osm#readme][Open Street Map]] is a tile-based map viewer with a responsive movable and
zoomable display and with a list of multiple preconfigured tile servers.
Listing [[lst:ensure-osm-installation]] ensures the installation of ~osm~ and
listing [[lst:using-osm-example]] is a minimal example of how to use ~osm~. Inside
~osm-mode~ buffers, the key binding for the command
src_emacs-lisp{(osm-bookmark-set)} is {{{kbd(C-x r b)}}} allowing to bookmark
such buffers. Outside ~osm-mode~ buffers, the key binding for the command
src_emacs-lisp{(call-interactively 'consult-bookmark)} is {{{kbd(C-x r b)}}}.
#+caption[Ensure "osm" installation]:
#+caption: Ensure ~osm~ installation.
#+name: lst:ensure-osm-installation
#+begin_src emacs-lisp -n :results silent
(ensure-package-installation 'osm)
#+end_src
#+caption[Using "osm" example]:
#+caption: Using ~osm~ example.
#+name: lst:using-osm-example
#+begin_src emacs-lisp -n :results silent :tangle no
(osm "Tower of London")
#+end_src
*** [[https://github.com/pashky/restclient.el#readme][Restclient]]
:PROPERTIES:
:CUSTOM_ID: sec:restclient
:END:
[[https://github.com/pashky/restclient.el#readme][Restclient]] is a tool to manually explore and test =HTTP= =REST= webservices. It
runs queries from a plain-text query sheet and displays results as images,
pretty-printed =XML=, and pretty-printed =JSON=.
#+begin_src emacs-lisp -n :results silent
(ensure-package-installation 'restclient)
#+end_src
#+caption[Explore =PEP-691= and =PEP-700= with =restclient=]:
#+caption: Explore =PEP-691= and =PEP-700= with =restclient=.
#+name: lst:restclient-pep-691-700
#+begin_src restclient -n :results client
# Get the JSON project list from PyPI using PEP-691 and PEP-700
GET https://pypi.org/simple
Accept: application/vnd.pypi.simple.latest+json
# Get the JSON project details of "circular-buffer" using PEP-691 and PEP-700
GET https://pypi.org/simple/circular-buffer
Accept: application/vnd.pypi.simple.latest+json
# Get the JSON project details of "holygrail" using PEP-691 and PEP-700
GET https://pypi.org/simple/holygrail
Accept: application/vnd.pypi.simple.latest+json
# Get the JSON project details of "pipdeptree" using PEP-691 and PEP-700
GET https://pypi.org/simple/pipdeptree
Accept: application/vnd.pypi.simple.latest+json
# Get the JSON project details of "numpy" using PEP-691 and PEP-700
GET https://pypi.org/simple/numpy
Accept: application/vnd.pypi.simple.latest+json
#+end_src
*** [[https://www.emacswiki.org/emacs/WebJump][Webjump]]
:PROPERTIES:
:CUSTOM_ID: sec:webjump
:END:
Listing [[lst:set-webjump-options]] binds {{{kbd(C-c j)}}} to =webjump= and
sets the =webjump-sites= option.
#+attr_latex: :options breaklines
#+caption[Set =webjump= options and bind the =webjump= command]:
#+caption: Set =webjump= options and bind the =webjump= command.
#+name: lst:set-webjump-options
#+begin_src emacs-lisp -n :results silent
(when (fboundp 'webjump)
(keymap-global-set "C-c j" 'webjump)
(with-eval-after-load 'webjump
(setopt
webjump-sites
'(("CS 325 AI Programming" . "courses.cs.northwestern.edu/325")
("Emacs News" . "sachachua.com/blog/category/emacs-news")
("Mastering Emacs" . "www.masteringemacs.org")
("Planet Emacs Life" . "planet.emacslife.com")
("Worg - Org Mode Community" . "orgmode.org/worg")
("Git: Emacs" . "git.savannah.gnu.org/cgit/emacs.git")
("Git: Emacs MultiMedia System" .
"https://git.savannah.gnu.org/cgit/emms.git")
("Git: GNU AUCTeX" . "git.savannah.gnu.org/cgit/auctex.git")
("Git: GNU ELPA" . "git.savannah.gnu.org/cgit/emacs/elpa.git")
("Git: NonGNU ELPA" . "git.savannah.gnu.org/cgit/emacs/nongnu.git")
("Git: Org Mode" . "git.savannah.gnu.org/cgit/emacs/org-mode.git")
("List: Org Mode" . "list.orgmode.org")
("List: Emacs Developer Archives" .
"lists.gnu.org/archive/html/emacs-devel/")
("List: Help GNU Emacs Archives" .
"lists.gnu.org/archive/html/help-gnu-emacs/")
;; ("NRC". "www.nrc.nl")
;; ("Trouw" . "www.trouw.nl")
;; ("Volkskrant" . "www.volkskrant.nl")
("Asian Pacific Journal Japan Focus" . "apjjf.org")
("Counterpunch" . "www.counterpunch.org")
("Dictionary FR" . [simple-query "www.cnrtl.fr"
"www.cnrtl.fr/definition/" ""])
("Dictionary NL" . [simple-query "www.woorden.org"
"www.woorden.org/woord/" ""])))))
#+end_src
** [[info:gnus#Top][Reading News and Mail (info)]]
:PROPERTIES:
:CUSTOM_ID: sec:reading-news-mail
:END:
Reading news and mail:
1. [[https://www.maketecheasier.com/emacs-usenet-reader-with-gnus/][How to use Emacs as a USENET reader with Gnus]]
2. [[https://github.com/redguardtoo/mastering-emacs-in-one-year-guide/blob/master/gnus-guide-en.org][A practical guide to Gnus]]
3. [[https://jao.io/blog/2021-05-17-reading-and-searching-gmane-with-gnus-fast.html][Fast reading and searching of Gmane.io with Gnus]]
4. [[https://www.brautaset.org/posts/leafnode-nntp-os-x.html][Setting up Leafnode on macOS]]
5. [[https://blog.bitside.pl/posts/gnus/][Gnus]]
6. [[http://www.bobnewell.net/publish/35years/gmailhacks.html][Stupid GMail hacks for Gnus]]
7. [[http://www.bobnewell.net/publish/35years/gnuhacks.html][More stupid Gnus hacks]]
8. [[https://config.phundrak.com/emacs.html][See Mu4e section of Phundrak's Emacs configuration]]
Password management:
1. [[https://www.passwordstore.org/][Pass: the standard unix password manager]]
2. [[https://www.howtogeek.com/devops/how-to-use-pass-a-command-line-password-manager-for-linux-systems/][How to use Pass, a command-line password manager for Unix systems]]
3. [[https://vitalyparnas.com/guides/pass/][Clever uses of pass, the Unix password manager]]
#+attr_latex: :booktabs yes :float table
#+caption[Gnus key bindings]:
#+caption: Gnus key bindings.
#+name: tab:gnus-key-bindings
| command | map | keys |
|--------------------------------+---------------------+----------------|
| gnus-group-list-active | gnus-group-list-map | {{{kbd(A-A)}}} |
| gnus-group-list-all-groups | gnus-group-mode-map | {{{kbd(L)}}} |
| gnus-group-toggle-subscription | gnus-group-mode-map | {{{kbd(U)}}} |
|--------------------------------+---------------------+----------------|
#+caption[Configure =gnus=]:
#+caption: Configure =gnus=.
#+name: lst:configure-gnus
#+begin_src emacs-lisp -n :results silent
(with-eval-after-load 'gnus
(setopt gnus-select-method '(nntp "news.gmane.io")))
(with-eval-after-load 'gnus-start
(setopt gnus-check-bogus-newsgroups nil
gnus-check-new-newsgroups 'ask-server
gnus-read-newsrc-file t
gnus-read-active-file 'some
gnus-save-killed-list t
gnus-save-newsrc-file t
gnus-use-dribble-file t))
(with-eval-after-load 'gnus-sum
(setopt gnus-thread-hide-subtree t))
#+end_src
** [[info:emacs#Sending Mail][Sending Mail (info)]]
:PROPERTIES:
:CUSTOM_ID: sec:sending-mail
:END:
1. [[https://macowners.club/posts/email-emacs-mu4e-macos/][Email setup in Emacs with Mu4e on macOS]]
2. [[https://www.bounga.org/tips/2020/05/03/multiple-smtp-accounts-in-gnus-without-external-tools/][Setting up multiple IMAP and SMTP accounts in Gnus]]
3. [[https://jherrlin.github.io/posts/emacs-on-macos-monterey/][Emacs on Macos Monterey]]
4. [[https://github.com/00riddle00/dotfiles/blob/master/.msmtprc][Msmtp resource file for posteo.net]]
5. [[https://www.devdungeon.com/content/gpg-tutorial][GPG tutorial]]
6. [[https://en.wikipedia.org/wiki/Key_server_(cryptographic)#Keyserver_examples][Cryptographic key server examples]]
7. [[https://rakhim.org/fastmail-setup-with-emacs-mu4e-and-mbsync-on-macos/][Fastmail setup with Emacs, mu4e and mbsync on macOS]]
#+caption[Configure =message=]:
#+caption: Configure =message=.
#+name: lst:configure-message
#+begin_src emacs-lisp -n :results silent
(setopt user-full-name "Gerard Vermeulen"
user-mail-address "gerard.vermeulen@posteo.net")
(with-eval-after-load 'message
(setopt message-sendmail-envelope-from 'header))
#+end_src
#+caption[Configure =sendmail=]:
#+caption: Configure =sendmail=.
#+name: lst:configure-sendmail
#+begin_src emacs-lisp -n :results silent
(with-eval-after-load 'sendmail
(setopt mail-specify-envelope-from t
mail-envelope-from 'header
send-mail-function #'sendmail-send-it
sendmail-program (executable-find "msmtp")))
#+end_src
#+attr_latex: :options breaklines
#+caption[Resource file for =msmtp= on =Darwin=]:
#+caption: Resource file for =msmtp= on =Darwin=.
#+name: lst:darwin-msmpt-resource-file
#+header: :tangle (if (eq 'darwin system-type) "~/.msmtprc" "no")
#+begin_src conf -n
# Set default values for all accounts.
defaults
auth on
tls on
logfile ~/.msmtp.log
# CNRS on Darwin
# security add-generic-password -s cnrs -a gerard.vermeulen@neel.cnrs.fr -w
account cnrs
host smtps.grenoble.cnrs.fr
port 465
tls_starttls off
from gerard.vermeulen@neel.cnrs.fr
user gerard.vermeulen@neel.cnrs.fr
passwordeval security find-generic-password -s cnrs -w
# https://notmuchmail.org/emacstips/#index11h2
# https://wiki.debian.org/msmtp
# https://tushartyagi.com/blog/configure-mu4e-and-msmtp/
# https://www.emacswiki.org/emacs/GnusMSMTP
# https://www.ying-ish.com/essay/emacs-notmuch-mbsync-msmtp-email/
# Local Variables:
# mode: conf-unix
# End:
#+end_src
#+attr_latex: :options breaklines
#+caption[Resource file for =msmtp= on =Linux=]:
#+caption: Resource file for =msmtp= on =Linux=.
#+name: lst:linux-msmpt-resource-file
#+header: :tangle (if (eq 'gnu/linux system-type) "~/.msmtprc" "no")
#+begin_src conf -n
# Set default values for all accounts.
defaults
auth on
tls on
tls_trust_file /etc/ssl/certs/ca-certificates.crt
logfile ~/.msmtp.log
# CNRS on Linux
# secret-tool store --label=msmtp host smtps.grenoble.cnrs.fr service smtp user gerard.vermeulen@neel.cnrs.fr
account cnrs
host smtps.grenoble.cnrs.fr
port 465
tls_starttls off
from gerard.vermeulen@neel.cnrs.fr
user gerard.vermeulen@neel.cnrs.fr
# https://notmuchmail.org/emacstips/#index11h2
# https://wiki.debian.org/msmtp
# https://tushartyagi.com/blog/configure-mu4e-and-msmtp/
# https://www.emacswiki.org/emacs/GnusMSMTP
# https://www.ying-ish.com/essay/emacs-notmuch-mbsync-msmtp-email/
# Local Variables:
# mode: conf-unix
# End:
#+end_src
** [[https://github.com/skeeto/elfeed#readme][Elfeed: Emacs web feed reader]]
:PROPERTIES:
:CUSTOM_ID: sec:emacs-web-feed-reader
:END:
Listing [[lst:set-elfeed-options]] sets =elfeed= options, binds the =elfeed=
command, and makes a minimal attempt to enable =emms=.
#+attr_latex: :options breaklines
#+caption[Set =elfeed= options and bind =elfeed= command]:
#+caption: Set =elfeed= options and bind =elfeed= command.
#+name: lst:set-elfeed-options
#+begin_src emacs-lisp -n :results silent
(when (and (ensure-package-installation 'elfeed)
(fboundp 'elfeed))
(keymap-global-set "C-x w" #'elfeed)
(with-eval-after-load 'elfeed
(setopt
elfeed-feeds
'(("https://frame.work/fr/fr/blog.rss" framework)
("https://nullprogram.com/feed/" c-wellons)
("https://planet.emacslife.com/atom.xml" planet-emacs)
("https://protesilaos.com/codelog.xml" p-stavrou)
("https://sachachua.com/blog/category/emacs/feed" s-chua)
("https://updates.orgmode.org/feed/updates" org-updates)
("https://www.bof.nl/rss/" bof)
("https://www.laquadrature.net/fr/rss.xml" lqdn)
("https://www.lemonde.fr/blog/huet/feed/" sciences))))
(with-eval-after-load 'elfeed-show
(when (fboundp 'emms-all)
(emms-all))))
#+end_src
** [[info:emms#Top][Emacs Multimedia System (info)]]
:PROPERTIES:
:CUSTOM_ID: sec:emacs-multimedia-system
:END:
The link [[https://sqrtminusone.xyz/posts/2021-09-07-emms/][my emms and elfeed setup]] is a nice introduction to configuring and
using ~emms~ with ~elfeed~. Listing [[lst:set-emms-options]] configures ~emms~
for ~mpv~ while eliminating use of ~mpd~.
#+caption[Set =emms= options]:
#+caption: Set =emms= options.
#+name: lst:set-emms-options
#+begin_src emacs-lisp -n :results silent
(when (ensure-package-installation 'emms)
(with-eval-after-load 'emms
(emms-all)
;; Since `emms-all' may add `emms-player-mpd' to `emms-player-list',
;; get rid off `emms-player-mpd' after calling `emms-all'.
(setopt emms-player-list '(emms-player-mpv)))
(with-eval-after-load 'emms-mode-line
(setopt emms-mode-line-format ""))
(with-eval-after-load 'emms-player-mpd
(setopt emms-player-mpd-music-directory (expand-file-name "~/Music")
emms-player-mpd-server-name "localhost"
emms-player-mpd-server-port "6600"
emms-player-mpd-verbose t))
(with-eval-after-load 'emms-player-mpv
(setopt emms-player-mpv-ipc-method 'ipc-server
emms-player-mpv-update-metadata t)
;; Uncomment the next two lines in case of too many broken YouTube links:
(add-to-list 'emms-player-mpv-parameters "--ytdl-format=best" 'append)
(add-to-list 'emms-player-mpv-parameters "--config=no" 'append)
;; The two previous lines may make the setup less user friendly.
(add-to-list 'emms-player-mpv-parameters "--fullscreen" 'append))
(with-eval-after-load 'emms-playing-time
(setopt emms-playing-time-display-format " %s "))
(with-eval-after-load 'emms-playlist-mode
(setopt emms-playlist-mode-center-when-go t))
(with-eval-after-load 'emms-streams
(setopt emms-streams-file
(no-littering-expand-etc-file-name "emms/streams.emms"))))
#+end_src
*** Hiding spurious buffers
:PROPERTIES:
:CUSTOM_ID: sec:hiding-spurious-buffers
:END:
When =emms-player-mpd= opens a connection to =mpd= and =mpd= is not running,
=mpd= responds with a message of the form "OK MPD 0.23.5". Neither
=emms-player-mpd= nor its support library =tq= anticipate the "OK MPD 0.23.5"
message, but send such messages to a new intrusive buffer called =*spurious*=.
Listing [[lst:hiding-spurious-buffers]] hides those =*spurious*= buffers, but you
can always switch to them. See:
1. [[info:elisp#The Zen of Buffer Display][The Zen of Buffer Display (info)]]
2. [[https://e17i.github.io/articles-emacs-display-1/][Configuring the Emacs display system]]
3. [[https://www.masteringemacs.org/article/demystifying-emacs-window-manager][Demystifying Emacs's Window Manager]]
for technical information.
#+caption[Hiding =*spurious*= buffers]:
#+caption: Hiding =*spurious*= buffers.
#+name: lst:hiding-spurious-buffers
#+begin_src emacs-lisp -n :results silent
(with-eval-after-load 'emms-player-mpd
;; https://www.masteringemacs.org/article/demystifying-emacs-window-manager
;; Hide `*spurious*' buffers, but you can always switch to them:
(add-to-list 'display-buffer-alist
'("\\*spurious\\*.*" display-buffer-no-window
(allow-no-window . t))))
#+end_src
*** [[https://www.musicpd.org/][Music Player Daemon]] configuration
:PROPERTIES:
:CUSTOM_ID: sec:mpd-configuration-file
:END:
Listing [[lst:darwin-mpd-conf]] proposes a [[https://www.musicpd.org/][Music Player Daemon]] configuration file on
Darwin that keeps each new connection for a day in order to reduce the rate of
=*spurious*= generation.
#+caption[A Music Player Daemon configuration file on Darwin]:
#+caption: A Music Player Daemon configuration file on Darwin.
#+name: lst:darwin-mpd-conf
#+begin_src conf -n :tangle (if (eq 'darwin system-type) "~/.mpd/mpd.conf" "no")
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"
}
#+end_src
* [[info:emacs#Init File][Init File (info)]] footer
:PROPERTIES:
:CUSTOM_ID: sec:user-init-file-footer
:END:
#+caption[Tangle the =user-init-file= footer]:
#+caption: Tangle the =user-init-file= footer.
#+name: lst:tangle-user-init-file-footer
#+begin_src emacs-lisp -n :results silent
(provide 'init)
;; Emacs looks for "Local variables:" after the last "newline-formfeed".
;; Local Variables:
;; indent-tabs-mode: nil
;; End:
;;; init.el ends here
#+end_src
* Local variables linking to [[#sec:latexmk-save-compile-display-loop][Latexmk save-compile-display-loop]]
:PROPERTIES:
:CUSTOM_ID: sec:local-variables
:END:
Only the [[info:org#Top][Org]] source file shows the local variables footer.
#+print_bibliography:
# Emacs looks for "Local variables:" after the last "newline-formfeed".
# Local Variables:
# compile-command: "latexmk -interaction=nonstopmode -lualatex -pvc -shell-escape README.tex"
# fill-column: 80
# org-edit-src-content-indentation: 0
# org-latex-src-block-backend: engraved
# eval: (org-eval-emacs-lisp-setup-blocks)
# End: