220 KiB
Emacs setup for use with LaTeX, Org, and Python
- Quick start
- Introduction
- Early Init File (info)
- Init File (info) header
- Install the selected packages (info)
- Text faces or styles (info)
- Window management (info)
- Advising Functions (info)
- Dired: directory editor as file manager (info)
- Processes (info)
- Help (info)
- Key bindings (info)
- Using Emacs as a server (info)
- Completion
- Search and replace (info)
- Version Control (info)
- Reading
- Writing
- Writing LaTeX files
- Writing Org files
- Org activation (info)
- Org customization
- Citar: citing bibliography with Org Mode
- Compare bibtex and biblatex
- Engrave Faces
- Making Org hyperlink types (info)
- Translate capital keywords (old) to lower case (new)
- Evaluate specific source blocks at load-time
- Easy LaTeX preamble editing
- #+INCLUDE: <FILE> (info)
- Advanced LaTeX export settings
- Org Syntax
- Grammar, spelling, and style tools
- Programming Tools
- Programming Modes
- Libraries
- Minor Modes (info)
- Display (info)
- Applications
- Init File (info) footer
- Local variables linking to Latexmk save-compile-display-loop
Quick start
Backup your user-emacs-directory
(defaults often to ~/.emacs.d
on Linux
,
Unix
, or Darwin
) directory and execute the commands in listing
lst:prepare-user-emacs-directory. After invoking Emacs interactively (in
interactive mode, neither in batch mode, nor in server mode), Emacs will ask you
to install a selected set of packages. Quit Emacs and invoke Emacs again.
cd ~
git clone ccdr@mercury.grenoble.cnrs.fr:SERVER/emacs.d.git .emacs.d
make --directory=.emacs.d init
emacs &
Introduction
This Emacs setup aims to install automatically a minimal set of extension packages that allows to handle my reports and presentations. The file format of the reports is Org Mode plain text with Python source code blocks and the file format of the presentations is LaTeX.
This org file (more precisely the original org source file of this file) illustrates three methods in my work-flow:
- How to tangle (or export) source blocks from org files. This file contains
source blocks to produce the files
early-init.el
,init.el
,latexmkrc
,org-store-link
, andexample.py
by tangling. - How to export org files to other formats such as HTML, LaTeX, and PDF.
- How org hyperlinks (info) allow to link inside and outside Org Mode: hover over or click on the links to experiment.
The AUCTeX - Aalborg University Center TeX extension package provides a powerful Text-based User Interface (TUI) environment to edit the LaTeX presentations.
The citar extension package provides quick filtering and selecting of bibliographic entries, and the option to run different commands on those selections. Citar requires Org-9.5 (info), which is already part of Emacs-28.1. Citar exploits the enhancements of Emacs' builtin selection mechanism provided by the extension packages vertico, orderless, embark, marginalia, and consult. The citeproc extension package provides CSL: citation style language processing capabilities to citar and Org Mode. Citation style language: style repository links to a curated repository of CSL styles.
The pdf-tools extension package renders PDF file with the possibility to
annotate the file or to click on anchors in the PDF file that link back to the
original LaTeX file of the document. An example of my work-flow are the steps
to convert this org file to PDF and to see the result with pdf-tools in Emacs:
execute the commands pdf-tools-install
, org-babel-tangle
,
org-latex-export-latex-to-latex
, and compile
. This sets up an infinite
LaTeX compilation loop to update and redisplay the PDF file after excution of
the org-latex-export-latex-to-latex
command in this buffer.
Here follows a list of interesting Emacs configurations:
- Musa Al-hassy's configuration is an impressive example of producing the Emacs initialization files and other files by tangling an org file. His methodology is impressive, as his Elisp Cheat Sheet and org-special-block-extra package show. To me, this is a configuration to admire, but his methodology is way over my head.
- Omar Antolin Camarena's configuration exploits built-in packages, Omar's own
small packages, and large external packages. Omar is the author of orderless
and embark. I have stolen his idea of using
custom-set-variables
instead of the customize interface although that is bad Emacs advice for new users. - 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.
- Sacha Chua's configuration is a practical example of producing the Emacs initialization files by tangling an org file. It gives me the impression that she is a very practical person trying to achieve her goals by the most efficient means.
- Steve Purcell's configuration is well organized, a showcase of readable code, as well helpful commit and issue histories. See for instance the discussion on the correctness of order of company candidates in Emacs lisp mode.
Here follows a list of links on how to use Emacs and Elisp:
- A guided tour of Emacs is a link to a video tour pointing how buffers (info), dired (info), documentation (info), elisp (info), elisp debugging (info), eshell (info), and keyboard macros (info) turn Emacs in an powerful coding and editing environment.
- Emergency Emacs is a link to a video on fundamental editing features and principles with focus on ediff (info), undo (info), moving point (info), erasing (info), and dynamic abbreviations (info).
- Endless Parentheses is a blog with many mindblowing code snippets.
- How to open a file in Emacs looks into elisp (info) and elisp debugging (info) and then compares Emacs, Vim, Neovim, and Visual Studio Code with respect to values and technology.
- Learning Emacs and Elisp is a link to a video tutorial with a transcript on the best approach to learn Emacs and Elisp.
- Mastering Emacs is a link to a blog with many interesting posts that promotes a book on how to become a proficient Emacs user.
Early Init File (info)
Try to load no-littering as early as possible, since it helps to keep
~/.emacs.d
clean.
;;; early-init.el --- user early-init file -*- lexical-binding: t -*-
;;; Commentary:
;;; Code:
(setq load-prefer-newer t)
(require 'no-littering nil 'noerror)
(provide 'early-init)
;; Emacs looks for "Local variables:" after the last "?\n?\f".
;; Local Variables:
;; indent-tabs-mode: nil
;; End:
;;; earl-init.el ends here
In order to get help in understanding the code block above in a buffer showing the original Org source file, type
(eval
after moving point (or cursor) to one of the items of the list:- src_emacs-lisp{(describe-variable #'load-prefer-newer t)}
- src_emacs-lisp{(apropos-library "no-littering")}
- src_emacs-lisp{(find-function #'hack-local-variables)}
to execute the code between the curly braces for access to help. This shows that Emacs is a self-documenting editor.
Init File (info) header
The user-init-file
header requires cl-lib
and customizes Emacs variables.
It consists of four parts in listing lst:1st-custom-set-variables-call,
lst:2nd-custom-set-variables-call, lst:3rd-custom-set-variables-call, and
lst:4th-custom-set-variables-call in order to limit the length of the listings
for exporting to LaTeX.
The quoting (info) and the backquote (info) pages explain how to understand the
'
(quote), `
(backquote), ,
(substitute) and @,
(splice) in the
custom-set-variables
function calls in listing
lst:1st-custom-set-variables-call, lst:2nd-custom-set-variables-call,
lst:3rd-custom-set-variables-call, and lst:4th-custom-set-variables-call. A
tutorial of how to use those reader macros is the didactic emacs-lisp macro
example.
The init file (info) does not load the custom-file
as saving customizations
(info) recommends because of the custom-set-variables
function calls.
;;; init.el --- user init file -*- lexical-binding: t -*-
;;; Commentary:
;;; Code:
(require 'cl-lib)
(custom-set-variables
'(after-save-hook #'executable-make-buffer-file-executable-if-script-p)
'(column-number-mode t)
'(cursor-type 'box)
`(custom-file ,(locate-user-emacs-file "custom.el"))
'(epg-pinentry-mode 'loopback)
'(global-hl-line-mode t)
'(global-hl-line-sticky-flag t)
'(history-delete-duplicates t)
'(history-length 500)
'(indent-tabs-mode nil)
'(inhibit-startup-buffer-menu t)
'(inhibit-startup-screen t)
'(initial-buffer-choice t)
'(initial-scratch-message "")
`(insert-directory-program ,(or (executable-find "gls")
(executable-find "ls")))
'(kill-ring-max 300))
(custom-set-variables
'(package-archives '(("gnu" . "https://elpa.gnu.org/packages/")
("gnu-devel" . "https://elpa.gnu.org/devel/")
("melpa" . "https://melpa.org/packages/")
("nongnu" . "https://elpa.nongnu.org/nongnu/")))
;; Pin packages to GNU ELPA for info or stability.
'(package-pinned-packages '((auctex . "gnu")
(compat . "gnu")
(consult . "gnu")
(engrave-faces . "gnu")
(marginalia . "gnu")
(org . "gnu-devel")
(python . "gnu-devel")
(queue . "gnu")
(rainbow-mode . "gnu")
(spinner . "gnu")
(xr . "gnu")
(vertico . "gnu"))))
(custom-set-variables
'(package-selected-packages
'(async ; asynchroneous processing
auctex ; Aalborg University Center TeX
company ; complete anything
magit ; Git Text-based User Interface
no-littering ; keep `user-emacs-directory' clean
orderless ; Emacs completion style
org ; thought organizer
python ; major mode to edit Python files
vertico ; VERTical Interactive Completion
wgrep ; open a writable grep buffer
xr))) ; undo rx to grok regular expressions
(custom-set-variables
'(recentf-mode t)
'(save-place-mode t)
'(scroll-bar-mode nil)
'(tab-always-indent 'complete)
'(tab-width 8)
'(tool-bar-mode nil)
'(url-cookie-trusted-urls nil)
'(url-cookie-untrusted-urls '(".*"))
'(use-dialog-box nil)
'(view-read-only t))
(when (version< "28.0" emacs-version)
(custom-set-variables
'(mode-line-compact 'long)
'(next-error-message-highlight t)
'(use-short-answers t)))
(when (eq system-type 'darwin)
(custom-set-variables
'(ns-alternate-modifier nil)
'(ns-command-modifier 'meta)
'(ns-right-command-modifier 'super)))
(when (eq window-system 'ns)
(add-to-list 'initial-frame-alist '(height . 51))
(add-to-list 'initial-frame-alist '(width . 180)))
Install the selected packages (info)
Emacs installs packages from archives on the internet. This setup uses three archives:
- The GNU Emacs Lisp Package Archive
- The NonGNU Emacs Lisp Package Archive.
- The Milkypostman's Emacs Lisp Package Archive (MELPA).
The code in listing lst:install-selected-packages assumes that the package system is in a virgin state if the package no-littering is not present:
- It installs and loads no-littering after ensuring refreshing of the contents of available packages.
- It installs Org (GNU-devel ELPA archive) early to shadow the built-in package while preventing collisions between the snapshot and built-in packages.
- 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.
- It defines a function to ensure the installation of packages in other source
blocks. This allows skipping installation in sections with a
:noexport:
tag by disallowing tangling.
In case of normal Emacs usage, src_emacs-lisp{(package-list-packages)} refreshes the contents of packages and allows to update packages to the latest version.
(unless noninteractive
(unless (require 'no-littering nil 'noerror)
(unless (bound-and-true-p package-archive-contents)
(package-refresh-contents))
;; Install and require `no-littering'.
(package-install 'no-littering)
(require 'no-littering)
;; https://emacs.stackexchange.com/a/45939 answers
;; "How to shadow automatically a built-in package by installing it?"
(defun shadow-builtin-by-install (pkg)
(when-let ((desc (cadr (assq pkg package-archive-contents))))
(package-install desc 'dont-select)))
;; Shadow built-in `org' by installing `org'.
(shadow-builtin-by-install 'org)
;; Shadow built-in `python' by installing `python'.
(shadow-builtin-by-install 'python)
;; Install the selected packages.
(package-install-selected-packages)))
(defun ensure-package-installation (&rest packages)
"Ensure installation of all packages in PACKAGES."
(let ((ok t))
(dolist (package packages)
(unless (package-installed-p package)
(package-install package))
(if (package-installed-p package)
(when (bound-and-true-p package-selected-packages)
(cl-pushnew package package-selected-packages))
(setq ok nil)))
ok))
Text faces or styles (info)
This setup does not configure custom themes (info) in order to prevent endless useless tweaking. See the note on mixed font heights in Emacs for how to setup fonts properly. It boils down to two rules:
- The height of the default face must be an integer number to make the height a physical quantity.
- The heights of all other faces must be real numbers to scale those heights with respect to the height of the face (those heights default to 1.0 for no scaling).
The code in listing lst:configure-face-attributes source implements those rules. Listing lst:set-default-face-height shows that font scaling is easy in case of proper initialization of all face heigths. Listing lst:fix-gtk-color-for-invert-default-face and listing lst:shadow-font-lock-faces show a few tweaks to improve visibility without theming:
- Fixing the
gtk
background color of the already loadedregion
face. - Toggling between a dark and light background by means of src_emacs-lisp{(invert-default-face)}.
- Shadowing the definition of faces before loading. The last item in the page on fontifying Org mode source code blocks describes this method.
(with-eval-after-load 'emacs
;; Set face attributes.
(cond
((eq system-type 'darwin)
(set-face-attribute 'default nil :family "Hack" :height 120)
(set-face-attribute 'fixed-pitch nil :family "Hack")
(set-face-attribute 'variable-pitch nil :family "FiraGo"))
((eq system-type 'gnu/linux)
(set-face-attribute 'default nil :family "Hack" :height 110)
(set-face-attribute 'fixed-pitch nil :family "Hack")
(set-face-attribute 'variable-pitch nil :family "FiraGo"))
(t
(set-face-attribute 'default nil :family "Hack" :height 110)
(set-face-attribute 'fixed-pitch nil :family "Hack")
(set-face-attribute 'variable-pitch nil :family "DejaVu Sans"))))
(with-eval-after-load 'emacs
(defun set-default-face-height ()
"Set the default face height in all current and future frames.
Scale all other faces with a height that is a real number."
(interactive)
(let* ((prompt (format "face heigth (%s): "
(face-attribute 'default :height)))
(choices (mapcar #'number-to-string
(number-sequence 50 200 10)))
(height (string-to-number
(completing-read prompt choices nil 'require-match))))
(message "Setting the height of the default face to %s" height)
(set-face-attribute 'default nil :height height))))
;; Use proportional font faces in current buffer
(defun set-buffer-variable-pitch-face ()
"Set a variable width (proportional) font in current buffer."
(interactive)
(setq buffer-face-mode-face 'variable-pitch)
(buffer-face-mode))
;; Use monospaced font faces in current buffer
(defun set-buffer-fixed-pitch-face ()
"Set a fixed width (monospace) font in current buffer."
(interactive)
(setq buffer-face-mode-face 'fixed-pitch)
(buffer-face-mode))
(add-hook 'magit-mode-hook #'set-buffer-fixed-pitch-face)
(add-hook 'prog-mode-hook #'set-buffer-fixed-pitch-face)
(with-eval-after-load 'emacs
(defun fix-gtk-region-face-background-color ()
(when (featurep 'gtk)
(set-face-attribute
'region nil
:background (cdr (assoc (face-attribute 'default :background)
'(("white" . "LightGoldenrod2")
("black" . "blue3")))))))
(defun invert-default-face ()
"Invert the default face."
(interactive)
(invert-face 'default)
(fix-gtk-region-face-background-color))
(fix-gtk-region-face-background-color))
(with-eval-after-load 'emacs
;; Shadow two definitions in org-faces.el:
(defface org-block
;; https://emacs.stackexchange.com/a/9604 answers:
;; How to override a defined face for light and dark backgrounds?
`((((background dark))
:inherit (fixed-pitch)
,@(and (>= emacs-major-version 27) '(:extend t))
:background "#444444")
(t
:inherit (fixed-pitch)
,@(and (>= emacs-major-version 27) '(:extend t))
:background "#FFFFD0"))
"My face used for text inside various blocks.
It is always used for source blocks. You can refine what face
should be used depending on the source block language by setting,
`org-src-block-faces', which takes precedence.
When `org-fontify-quote-and-verse-blocks' is not nil, text inside
verse and quote blocks are fontified using the `org-verse' and
`org-quote' faces, which inherit from `org-block'.")
(defface org-table
;; https://emacs.stackexchange.com/a/9604 answers:
;; How to override a defined face for light and dark backgrounds?
`((((background dark))
:inherit (fixed-pitch)
,@(and (>= emacs-major-version 27) '(:extend t))
:background "#444444")
(t
:inherit (fixed-pitch)
,@(and (>= emacs-major-version 27) '(:extend t))
:background "#FFFFD0"))
"My face for tables.")
;; Shadow one definition in sh-script.el:
(defface sh-heredoc
'((((class color) (background dark))
(:foreground "yellow"))
(((class color) (background light))
(:foreground "magenta"))
(t
(:weight bold)))
"My face to show a here-document."))
Window management (info)
Mickey Peterson's post Demystifying Emacs's Window Manager invites to improve window placement. Listing lst:1st-window-management, lst:2nd-window-management and lst:3rd-window-management implement a selection of his recommendations.
(with-eval-after-load 'emacs
;; https://www.masteringemacs.org/article/demystifying-emacs-window-manager
(defun split-root-below (arg)
"Split window below from the root or from the parent with ARG."
(interactive "P")
(split-window (if arg (window-parent (selected-window))
(frame-root-window))
nil 'below nil))
(defun split-root-right (arg)
"Split window right from the root or from the parent with ARG."
(interactive "P")
(split-window (if arg (window-parent (selected-window))
(frame-root-window))
nil 'right nil))
(defun toggle-window-dedication ()
"Toggles window dedication in the selected window."
(interactive)
(set-window-dedicated-p
(selected-window) (not (window-dedicated-p (selected-window)))))
(defun make-display-buffer-matcher-function (major-modes)
"Return a lambda function to match a list of MAJOR-MODES."
(lambda (buffer-name action)
(with-current-buffer buffer-name (apply #'derived-mode-p major-modes))))
(global-set-key (kbd "M-o") #'other-window))
(with-eval-after-load 'emacs
(winner-mode +1))
(with-eval-after-load 'emacs
;; https://www.masteringemacs.org/article/demystifying-emacs-window-manager
(custom-set-variables
'(switch-to-buffer-in-dedicated-window 'pop)
'(switch-to-buffer-obey-display-actions t))
(add-to-list 'display-buffer-alist
`(,(rx (or "*Apropos*"
"*Help*"
"*info"))
(display-buffer-reuse-window display-buffer-pop-up-window)
(inhibit-same-window . nil)))
(add-to-list 'display-buffer-alist
`(,(rx (or "*Occur*"
"*grep*"
"*xref*"))
display-buffer-reuse-window
(inhibit-same-window . nil)))
(add-to-list 'display-buffer-alist
'("\\*compilation\\*"
display-buffer-no-window
(allow-no-window . t))))
Advising Functions (info)
(with-eval-after-load 'emacs
(defun toggle-advice (symbol where function &optional props)
"Toggle between states after `advice-remove' and `advice-add'."
(let ((how "%s `%s' advice `%s' %s `%s'"))
(if (advice-member-p function symbol)
(progn
(message how "Removal of" where function "from" symbol)
(advice-remove symbol function))
(message how "Addition of" where function "to" symbol)
(advice-add symbol where function props)))))
Dired: directory editor as file manager (info)
Dired (info) and the ranger file manager offer similar capabilities for copying, deleting, opening, renaming, and viewing files and directories, but the integration of dired (info) in Emacs is obviously much better than the integration of ranger in Emacs.
For instance, this setup allows to insert an org-mode
link to an poorly
identified file containing a specific image into an org-mode
buffer by means
of the facilities of dired (info) as follows:
- Type
(eval
to open the directory containing the pooly identified file with the specific image in adired-mode
buffer. - Start searching for the specific image by navigating to the name of any image file.
- Type
(eval
to switch to view the file in animage-mode
buffer. -
In the
image-mode
buffer, continue searching for the specific image by typing:(eval
to view the next image in thedired-mode
buffer,(eval
to view the previous image in thedired-mode
buffer, and(eval
to quit theimage-mode
buffer and to go back to thedired-mode
buffer.
- After finding the specific image, use
(eval
in theimage-mode
buffer or thedired-mode
buffer to store theorg-link
. - Switch to the
org-mode
buffer and use(eval
to insert theorg-link
into the buffer.
Listing lst:customize-dired makes the directory editor sort entries alphabetically after grouping the directories before grouping the files by extension.
(with-eval-after-load 'dired
(custom-set-variables
'(dired-dwim-target t)
;; | switch | action |
;; |--------+----------------------------------------|
;; | -a | also list hidden entries |
;; | -l | use a long listing format |
;; | -X | sort alphabetically by entry extension |
;; | -G | skip long listing format group names |
;; | -1 | list one entry per line |
'(dired-listing-switches "-alGX1 --group-directories-first")
'(dired-recursive-copies 'always)
'(dired-recursive-deletes 'always)))
(with-eval-after-load 'files
(custom-set-variables
;; Ensure the use of `GNU-ls' from `coreutils' on darwin.
'(insert-directory-program (or (executable-find "gls")
(executable-find "ls")))))
(with-eval-after-load 'wdired
(custom-set-variables
'(wdired-allow-to-change-permissions t)))
Listing lst:extra-dired-key-bindings adds new key bindings to dired-mode-map
to:
- Open files with the Emacs Web Wowser inside Emacs.
- Let rsync copy marked files outside Emacs to a local or remote directory.
The link asynchoronous execution library for Emacs Dired is the original source for the idea of using rsync and the blog articles using rsync in dired and asynchronous rsync with Emacs, dired and tramp are vivid recommendations written by experienced Emacs users.
(with-eval-after-load 'dired
(defun dired-eww-open-file ()
"In Dired, open the regular file named on this line with eww"
(interactive)
(let ((file (dired-get-file-for-visit)))
(if (file-regular-p file)
(eww-open-file file)
(error "Eww rejects `%s', since it is not a regular file" file))))
;; https://truongtx.me/tmtxt-dired-async.html
(defun dired-rsync (target)
"Copy marked files with `rsync' to TARGET directory."
(interactive
(list (expand-file-name
(read-file-name "Rsync to:" (dired-dwim-target-directory)))))
;; Store all marked files into the `files' list and intialize
;; the `rsync-command'.
(let ((files (dired-get-marked-files nil current-prefix-arg))
(rsync-command "rsync -av --progress "))
;; Add all marked files as arguments to the `rsync-command'.
(dolist (file files)
(setq rsync-command
(concat rsync-command
(if (string-match "^/ssh:\\(.*\\)$" file)
(format " -e ssh %s" (match-string 1 file))
(shell-quote-argument file)) " ")))
;; Append the destination directory to the `rsync-command'.
(setq rsync-command
(concat rsync-command
(if (string-match "^/ssh:\\(.*\\)$" target)
(format " -e ssh %s" (match-string 1 target))
(shell-quote-argument target))))
;; Run the async shell command.
(async-shell-command rsync-command)
;; Finally, switch to that window.
(other-window 1)))
(define-key dired-mode-map (kbd "E") #'dired-eww-open-file)
(define-key dired-mode-map (kbd "Y") #'dired-rsync))
Processes (info)
Listing lst:process-utilities defines a function to run a (command-line) program
with arguments and to obtain a list with its numeric exit status as well as its
output to stdout
.
;; https://gitlab.com/howardabrams/spacemacs.d/blob/master/layers/ha-org/funcs.el#L418
(defun shell-command-with-exit-code (program &rest args)
"Run PROGRAM with ARGS and return exit-code and output in a list."
(with-temp-buffer
(list (apply 'call-process program nil (current-buffer) nil args)
(buffer-string))))
Help (info)
Table tab:help-key-bindings lists a number of key bindings to start playing with the help facilities of Emacs. Try or
(eval
.command | key map | keys |
Info-goto-emacs-command-node | help-map | (eval |
describe-function | help-map | (eval |
describe-key | help-map | (eval |
describe-symbol | help-map | (eval |
describe-variable | help-map | (eval |
info | help-map | (eval |
Shortdoc-display-group (info)
Listing lst:configure-shortdoc binds
(eval
toshort-doc-display-group
and defines a short documentation group for functions
defined in this Org file.
(when (fboundp 'shortdoc-display-group)
(define-key help-map (kbd "y") #'shortdoc-display-group)
(with-eval-after-load 'shortdoc
;; Ensure defining the functions before documenting them.
(define-short-documentation-group init
"Advice"
(toggle-advice :no-manual t)
"Face"
(invert-default-face :no-manual t)
(set-default-face-height :no-manual t)
"Interaction"
(enable-this-command :no-manual t)
(narrow-or-widen-dwim :no-manual t)
(org-narrow-to-table :no-manual t)
(toggle-ilog-timer-function-after :no-manual)
"LaTeX"
(TeX-brace-count-line-override :no-manual t)
(biber-delete-cache :no-manual t)
(toggle-TeX-brace-count-line-override :no-manual t)
(update-lualatex-opentype-font-name-database :no-manual t)
"Org"
(by-backend :no-manual t)
(by-backend-kbd-org-macro :no-manual t)
(org-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)
(toggle-engrave-faces-latex-face-apply-override :no-manual t)
(toggle-engrave-faces-latex-face-mapper-override :no-manual t)
(toggle-org-babel-python-format-session-value-override :no-manual t)
"Python"
(choose-common-python-interpreter :no-manual t))))
Info (info)
Listing lst:configure-info adds the required paths to the places where info
looks for files.
(with-eval-after-load 'info
;; Make Emacs find the "*.info" files in `package-user-dir' on Gentoo Linux.
(when (eq system-type 'gnu/linux)
(dolist (path
(nreverse
(mapcar
(lambda (name)
(expand-file-name (file-name-directory name)))
(directory-files-recursively package-user-dir "\\.info\\'"))))
(add-to-list 'Info-directory-list path nil #'file-equal-p)))
;; Make Emacs find my "python.info" file.
(add-to-list 'Info-directory-list
(expand-file-name "~/.local/share/info")))
Key bindings (info)
command | key map | keys |
undo | global-map | (eval |
backward-kill-word | global-map | (eval |
backward-char | global-map | (eval |
forward-char | global-map | (eval |
backward-word | global-map | (eval |
forward-word | global-map | (eval |
backward-sentence | global-map | (eval |
forward-sentence | global-map | (eval |
Disabling Commands (info)
Execute src_emacs-lisp{(find-library "novice")} to see how Emacs prevents new users from shooting themselves in the feet. Listing lst:configure-disabled-command-function enables disabled commands on the fly.
(with-eval-after-load 'emacs
(setq disabled-command-function
(defun enable-this-command (&rest _args)
"Called when a disabled command is executed.
Enable it and re-execute it."
(put this-command 'disabled nil)
(message "You typed %s. %s was disabled until now."
(key-description (this-command-keys)) this-command)
(sit-for 0)
(call-interactively this-command))))
Interaction-log
(when (and (ensure-package-installation 'interaction-log)
(require 'interaction-log nil 'noerror))
;; https://www.skybert.net/emacs/show-shortcuts-when-giving-presentations/
(defun ilog-ensure-ilog-buffer-window ()
"Ensure the visibility of the `ilog' buffer, when it exists."
(when (and (get-buffer ilog-buffer-name)
(not (get-buffer-window ilog-buffer-name)))
(display-buffer (get-buffer ilog-buffer-name))))
(define-key help-map (kbd "C-l") #'interaction-log-mode))
(with-eval-after-load 'emacs
(defun toggle-ilog-timer-function-after ()
"Toggle `ilog-timer-function' advice."
(interactive)
(toggle-advice 'ilog-timer-function :after #'ilog-ensure-ilog-buffer-window))
(toggle-ilog-timer-function-after))
Using Emacs as a server (info)
Emacs can act as a server that listens to a socket to share its state (for
instance buffers and command history) with other programs by means of a shell
command emacsclient
. Section #sec:latexmk-save-compile-display-loop and
#sec:qutebrowser-userscript show how to use emacsclient
to:
- Install an asynchronous (or background) loop of saving a LaTeX file, compiling it, and redisplaying the output in Emacs.
- Make qutebrowser send html links with document titles to Emacs.
The code in listing lst:start-emacs-server starts the Emacs server.
(when window-system
(unless (or noninteractive (daemonp))
(add-hook 'after-init-hook #'server-start)))
Latexmk save-compile-display loop
The latexmk
resource file in the next source code block shows how to use
emacsclient
to (re)display the PDF file in Emacs after each succesful
(re)compilation on condition that the settings of the compile-command
local
variable in section are compatible. The local variable compile-command
in the
local variables section (only visible in org
files, but not in html
and
pdf
files) shows how to use the latexmkrc
file.
# pdf creator
$pdf_mode = 4; # 4 means lualatex
# pdf previewer and update pdf previewer
$pdf_previewer = "emacsclient -e '(find-file-other-window %S)'";
$pdf_update_method = 4; # 4 runs $pdf_update_command to force the update
$pdf_update_command = "emacsclient -e '(with-current-buffer (find-buffer-visiting %S) (pdf-view-revert-buffer nil t))'";
# see for instance glossary.latexmkrc
add_cus_dep( 'acn', 'acr', 0, 'makeglossaries' );
add_cus_dep( 'glo', 'gls', 0, 'makeglossaries' );
# use ".=" to append to $clean_ext
$clean_ext .= " acr acn alg bbl dvi glo gls glg ist lol lst";
$clean_ext .= " nav run.xml snm synctex.gz";
sub makeglossaries {
my ($name, $path) = fileparse( $$Psource );
return system "makeglossaries -d '$path' '$name'";
}
# Emacs looks for "Local variables:" after the last "newline-formfeed".
# Local Variables:
# mode: perl
# End:
Qutebrowser userscript
The next block contains an userscript that sends a store-link org-protocol
message with the url and the title from qutebrowser to emacsclient
. The
function urlencode
translates the url and the title for the message. The
Python urllib examples show how to use urlencode
. The final execvp
call
deals with a qutebrowser userscript requirement: the emacsclient
process must
get the PID of the userscript that must kill itself after the take-over.
Termination of the emacsclient
process hands control back to qutebrowser.
On a POSIX system, you can run the userscript from qutebrowser or from a terminal to see whether it works. In case you try to run it from Emacs, Emacs may hang or die.
#!/usr/bin/env python
from urllib.parse import urlencode
from os import environ, execvp
url = environ.get("QUTE_URL", "https://orgmode.org")
title = environ.get("QUTE_TITLE", "Org Mode")
parameters = urlencode({"url": url, "title": title})
print(payload := f"org-protocol://store-link?{parameters}")
execvp("emacsclient", ("-n", payload))
TODO Look into: org-protocol handling with other browser on Darwin
(when (ensure-package-installation 'applescript-mode))
on emacsclient(input)
do shell script "/usr/local/bin/emacsclient -n -c '" & input & "'"
tell application "Emacs" to activate
end emacsclient
on open location input
emacsclient(input)
end open location
on open inputs
repeat with raw_input in inputs
set input to POSIX path of raw_input
emacsclient(input)
end repeat
end open
on run
do shell script emacsclient("")
end run
Completion
Vertico (info) provides a performant and minimalistic vertical completion UI based on the default completion system and behaves therefore correctly under all circumstances. Using Vertico, Marginalia, Consult, and Embark links to a video demonstration. Vertico integrates well with fully supported complementary packages to enrich the completion UI:
- Orderless (info) for an advanced completion style,
- Embark (info) for minibuffer actions with context menus,
- Marginalia (info) for rich annotations in the minibuffer, and
- Consult (info) for useful search and navigation commands,
where the order is that of enhancing citar's experience and the configuration steps below.
Finally, company: a modular complete-anything framework for Emacs provides completion in any buffer and minibuffer-history-completion provides completion on previous input in the minibuffer.
Vertico (info)
Listing lst:enable-vertico-mode configures and enables savehist-mode
and
enables vertico-mode
. The documentation src_emacs-lisp{(describe-function
'savehist-mode)} why it is best to turn on savehist-mode
in the Emacs init
file.
(when (fboundp 'savehist-mode)
(savehist-mode +1)
(custom-set-variables
'(savehist-additional-variables
'(eww-history
kill-ring
regexp-search-string
search-ring
search-string))))
(when (fboundp 'vertico-mode)
(vertico-mode +1))
(with-eval-after-load 'vertico
(define-key vertico-map (kbd "RET") #'vertico-directory-enter)
(define-key vertico-map (kbd "DEL") #'vertico-directory-delete-char)
(define-key vertico-map (kbd "M-DEL") #'vertico-directory-delete-word))
command | remap | keys |
vertico-directory-delete-char | (eval |
|
vertico-directory-delete-word | (eval |
|
vertico-directory-enter | (eval |
|
vertico-exit | exit-minibuffer | (eval |
vertico-exit-input | (eval |
|
vertico-first | beginning-of-buffer | (eval |
vertico-first | minibuffer-beginning-of-buffer | (eval |
vertico-insert | (eval |
|
vertico-last | end-of-buffer | (eval |
vertico-next-group | forward-paragraph | (eval |
vertico-next | next-line-or-history-element | (eval |
vertico-next | next-line | (eval |
vertico-previous-group | backward-paragraph | (eval |
vertico-previous | previous-line-or-history-element | (eval |
vertico-previous | previous-line | (eval |
vertico-save | kill-ring-save | (eval |
vertico-scroll-down | scroll-down-command | (eval |
vertico-scroll-up | scroll-up-command | (eval |
Orderless (info)
Listing lst:configure-orderless configures orderless for company (info).
(when (ensure-package-installation 'marginalia)
(with-eval-after-load 'orderless
(custom-set-variables
'(orderless-component-separator " +"))
(defun just-one-face (fn &rest args)
(let ((orderless-match-faces [completions-common-part]))
(apply fn args)))
(advice-add 'company-capf--candidates :around #'just-one-face)))
Embark (info)
Listing lst:configure-embark configures embark
key bindings:
embark-act
prompts the user for an action and performs it.-
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. Sinceembark-dwim
acts on a superset of target types, it rendersgoto-addr
superfluous. See:- Goto Address mode (info).
- The default (embark-dwim) action on a target (info).
embark-bindings
allows to explore all current command key bindings in the minibuffer.- The initialization src_emacs-lisples minibuffer help after a prefix key
(for instance
or
(eval
shows.
(when (ensure-package-installation 'embark)
(when (fboundp 'embark-act)
(global-set-key (kbd "C-,") #'embark-act))
(when (fboundp 'embark-dwim)
(global-set-key (kbd "C-:") #'embark-dwim))
(when (fboundp 'embark-bindings)
(global-set-key (kbd "C-h B") #'embark-bindings))
(when (fboundp 'embark-prefix-help-command)
(setq prefix-help-command #'embark-prefix-help-command)))
Marginalia (info)
Listing lst:enable-marginalia-mode enables marginalia-mode
.
(when (and (ensure-package-installation 'marginalia)
(fboundp 'marginalia-mode))
(marginalia-mode +1))
Consult (info)
Consult provides practical commands based on the Emacs minibuffer completion
function completing-read. Listing lst:configure-consult configures consult
.
command | key map | keys |
consult-apropos | help-map | (eval |
consult-bookmark | ctl-x-r-keymap | (eval |
consult-buffer-other-frame | ctl-x-5-keymap | (eval |
consult-buffer-other-window | ctl-x-4-keymap | (eval |
consult-buffer | ctl-x-keymap | (eval |
consult-compile-error | goto-map | (eval |
consult-complex-command | ctl-x-keymap | (eval |
consult-find | search-map | (eval |
consult-focus-lines | search-map | (eval |
consult-git-grep | search-map | (eval |
consult-global-mark | goto-map | (eval |
consult-goto-line | goto-map | (eval |
consult-goto-line | goto-map | (eval |
consult-history | global-map | (eval |
consult-imenu | goto-map | (eval |
consult-keep-lines | search-map | (eval |
consult-line | search-map | (eval |
consult-mark | goto-map | (eval |
consult-mode-command | global-map | (eval |
consult-multi-occur | search-map | (eval |
consult-outline | goto-map | (eval |
consult-register | ctl-x-r-keymap | (eval |
consult-yank-pop | global-map | (eval |
deadgrep | search-map | (eval |
elfeed | global-map | (eval |
embark-act | global-map | (eval |
embark-bindings | global-map | (eval |
embark-dwim | global-map | (eval |
iedit-mode | global-map | (eval |
minibuffer-complete-history | minibuffer-local-map | (eval |
narrow-or-widen-dwim | ctl-x-keymap | (eval |
org-agenda | global-map | (eval |
org-capture | global-map | (eval |
org-cite | org-mode-map | (eval |
org-insert-link-global | global-map | (eval |
org-narrow-to-table | ctl-x-keymap | (eval |
org-store-link | global-map | (eval |
(when (ensure-package-installation 'consult)
(custom-set-variables
'(consult-project-function #'(lambda (_) (vc-root-dir))))
;; C-c bindings (current-global-map)
(global-set-key (kbd "C-c h") #'consult-history)
(global-set-key (kbd "C-c m") #'consult-mode-command)
;; C-h bindings (help-map)
(define-key help-map (kbd "a") #'consult-apropos)
;; C-x bindings (ctl-x-map)
(define-key ctl-x-map (kbd "M-:") #'consult-complex-command)
(define-key ctl-x-map (kbd "b") #'consult-buffer)
(define-key ctl-x-4-map (kbd "b") #'consult-buffer-other-window)
(define-key ctl-x-5-map (kbd "b") #'consult-buffer-other-frame)
(define-key ctl-x-r-map (kbd "x") #'consult-register)
(define-key ctl-x-r-map (kbd "b") #'consult-bookmark)
;; M-g bindings (goto-map)
(define-key goto-map (kbd "g") #'consult-goto-line)
(define-key goto-map (kbd "M-g") #'consult-goto-line)
(define-key goto-map (kbd "o") #'consult-outline)
(define-key goto-map (kbd "m") #'consult-mark)
(define-key goto-map (kbd "k") #'consult-global-mark)
(define-key goto-map (kbd "i") #'consult-imenu)
(define-key goto-map (kbd "e") #'consult-compile-error)
;; M-s bindings (search-map)
(define-key search-map (kbd "g") #'consult-git-grep)
(define-key search-map (kbd "f") #'consult-find)
(define-key search-map (kbd "k") #'consult-keep-lines)
(define-key search-map (kbd "l") #'consult-line)
(define-key search-map (kbd "m") #'consult-multi-occur)
(define-key search-map (kbd "u") #'consult-focus-lines)
;; Other bindings (current-global-map)
(global-set-key (kbd "M-y") #'consult-yank-pop))
Company: a modular complete anything framework for Emacs
Listing lst:configure-company configures company
.
(when (fboundp 'company-mode)
(custom-set-variables
;; https://github.com/purcell/emacs.d/issues/778
'(company-transformers '(company-sort-by-occurrence)))
(dolist (hook '(LaTeX-mode-hook
org-mode-hook
emacs-lisp-mode-hook
lisp-interaction-mode-hook
lisp-mode-hook
python-mode-hook
ielm-mode-hook
sly-mrepl-mode-hook))
(add-hook hook #'company-mode)))
Minibuffer history completion
See Juri Linkov (Emacs Developer mailing list) for how to allow completion on previous input in the minibuffer. Listing lst:enable-minibuffer-history-completion enables minibuffer history completion.
(with-eval-after-load 'minibuffer
;; https://github.com/oantolin/emacs-config/blob/master/init.el#L333
(custom-set-variables
'(completion-category-defaults nil)
'(completion-category-overrides '((file (styles basic partial-completion))))
'(completion-ignore-case t)
'(completion-styles '(orderless basic partial-completion)))
(defun minibuffer-setup-history-completions ()
(unless (or minibuffer-completion-table minibuffer-completion-predicate)
(setq-local minibuffer-completion-table
(symbol-value minibuffer-history-variable))))
(add-hook 'minibuffer-setup-hook 'minibuffer-setup-history-completions)
;; Stolen from Emacs-28.1 for Emacs-27.2:
(unless (fboundp 'minibuffer--completion-prompt-end)
(defun minibuffer--completion-prompt-end ()
(let ((end (minibuffer-prompt-end)))
(if (< (point) end)
(user-error "Can't complete in prompt")
end))))
;; Adapted from ‘minibuffer-complete’:
(defun minibuffer-complete-history ()
"Allow minibuffer completion on previous input."
(interactive)
(completion-in-region (minibuffer--completion-prompt-end) (point-max)
(symbol-value minibuffer-history-variable)
nil))
(define-key minibuffer-local-map (kbd "C-<tab>") #'minibuffer-complete-history))
Search and replace (info)
Regexp Replace (info)
Executing
(eval
prompts fo a regular expressionREGEXP
and a substitution NEWSTRING
to replace REGEXP
with NEWSTRING
. The tools
in section Narrowing allow to limit the scope of (eval
. A list pointing to relevant documentation is:- Regexp Replace (info) explains how the substitution handles references to regular expression groups and lisp expressions.
- Syntax of Regular Expressions (info) describes regular expression features for users.
- Emacs Lisp Regular Expressions (info) describes regular expression features for programmers.
- Emacs Wiki has an article on replacing regexp with lisp expressions.
Table tab:replace-regexp-tranform tabulates how to perfom example tasks by means of
(eval
using(regular-expression transform)
pairs.
task | regular expression | substitution |
---|---|---|
move dot two digits forward | 0\.\([[:digit:]][[:digit:]]\) |
0\1. |
move dot two digits forward | 0\.\([0-9][0-9]\) |
0\1. |
renumber grep/occur matches | \(.+:\) |
\,(1+ \#)._ |
Deadgrep
Deadgrep uses ripgrep for superfast text searching in the default directory or
the current VCS directory tree. Listing lst:configure-deadgrep binds deadgrep
to .
(when (and (ensure-package-installation 'deadgrep)
(fboundp 'deadgrep))
(define-key search-map (kbd "d") #'deadgrep)
(with-eval-after-load 'deadgrep
(define-key deadgrep-mode-map (kbd "C-c C-w") #'deadgrep-edit-mode)))
Ugrep
Ugrep is an ultra fast grep with interactive query UI and fuzzy search. The pages Using ugrep within Emacs and Ugrep in Emacs show how to make Emacs use it with lgrep and xref. Listing lst:use-ugrep shows my implementation of the suggestions in those pages.
(when (executable-find "ugrep")
(with-eval-after-load 'grep
(custom-set-variables
`(grep-template ,(string-join '("ugrep"
"--color=always"
"--ignore-binary"
"--ignore-case"
"--include=\"<F>\""
"--line-number"
"--null"
"--recursive"
"--regexp=<R>")
" "))))
(with-eval-after-load 'xref
(custom-set-variables
'(xref-search-program-alist
'((grep
. "xargs -0 grep <C> --null -snHE -e <R>")
(ripgrep
. "xargs -0 rg <C> --null -nH --no-heading --no-messages -g '!*/' -e <R>")
(ugrep
. "xargs -0 ugrep <C> --null -ns -e <R>")))
'(xref-search-program 'ugrep))))
Version Control (info)
Ediff (info)
Video links to complete the ediff (info) manual are:
Listing lst:configure-ediff configures ediff
to display all its buffers in a
single frame and to make all text visible prior to ediffing Org buffers.
(with-eval-after-load 'emacs
(custom-set-variables
'(ediff-merge-split-window-function #'split-window-horizontally)
'(ediff-split-window-function #'split-window-horizontally)
'(ediff-window-setup-function #'ediff-setup-windows-plain)))
(with-eval-after-load 'org
;; https://github.com/oantolin/emacs-config#readme
(defun ediff-with-org-show-all ()
"Expand all headings prior to ediffing org buffers."
(add-hook 'ediff-prepare-buffer-hook #'org-show-all nil t))
(add-hook 'org-mode-hook #'ediff-with-org-show-all))
Reading
Reading DjVu files
Reading EPUB files
The package nov.el provides a major mode for reading EPUB files in Emacs. Listing lst:configure-nov configures nov.el.
(when (and (ensure-package-installation 'nov)
(fboundp 'nov-mode))
(add-to-list 'auto-mode-alist `(,(rx ".epub" eos) . nov-mode)))
Reading PDF files
The pdf-tools package exploits the poppler library to render and to let you
annotate PDF files. It also exploits the SyncTeX library to link anchors in PDF
files produced with LaTeX to the original LaTeX sources. The saveplace-pdf-view
package saves pdf-view
and doc-view
places.
In order to use pdf-tools, you have to type
(eval
after installation of pdf-tools from MELPA or after each update of poppler to build or rebuild theepdfinfo
executable that serves the PDF files to Emacs.
(when (and (ensure-package-installation 'pdf-tools 'saveplace-pdf-view)
(fboundp 'pdf-loader-install))
;; `pdf-loader-install' is the lazy equivalent of `pdf-tools-install':
;; see the README file.
(pdf-loader-install)
(with-eval-after-load 'pdf-outline
;; Unmask the `image-save' key binding in `pdf-view-mode-map' and
;; in `image-mode-map' (by parenthood).
(define-key pdf-outline-minor-mode-map (kbd "o") nil)
(define-key pdf-outline-minor-mode-map (kbd "O") #'pdf-outline))
(with-eval-after-load 'pdf-view
(custom-set-variables
'(pdf-view-display-size 'fit-page)
`(pdf-view-use-scaling ,(eq system-type 'darwin)))
(require 'saveplace-pdf-view nil 'noerror)))
Writing
Writing LaTeX files
Loading tex.el
immediately instead of lazily ensures proper initialization of
AUCTeX. For instance, the TeX-master
safe local variable in the tex.el
elisp library file has no autoload cookie. Without prior loading of tex.el
,
Emacs will complain that TeX-master
is no safe local variable in case it reads
a LaTeX file that sets TeX-master
. Listing lst:require-auctex initializes
AUCTeX properly for LuaTeX or LuaLaTeX. In case the output of LuaLaTeX
has
missing fonts, listing lst:update-lualatex-opentype-font-name-database defines a
function to update the OpenType Font
name database for LuaLaTeX
. Finally,
out of the box, AUCTeX does not indent text between square brackets. The code
in listing lst:configure-auctex corrects this by advising to override
TeX-brace-count-line
with TeX-brace-count-line-advice
.
;; Use `require' to make `TeX-master' a safe local variable.
(when (require 'tex nil 'noerror)
(custom-set-variables
'(TeX-auto-save t)
'(TeX-engine 'luatex)
'(TeX-install-font-lock #'font-latex-setup)
'(TeX-parse-self t)
;; Disable `TeX-electric-math' to prevent collisions with `smartparens'.
'(TeX-electric-math nil)))
(with-eval-after-load 'emacs
(defun update-lualatex-opentype-font-name-database ()
"Update the \"OpenType Font\" name database for \"LuaLaTeX\"."
(interactive)
(cl-destructuring-bind (exit-code output)
(shell-command-with-exit-code
"luaotfload-tool" "-vv" "--update" "--force")
(if (= 0 exit-code)
(message "%s" (string-trim output))
(error "%s" (string-trim output))))))
(when (require 'tex nil 'noerror)
;; https://emacs.stackexchange.com/a/35507 answers:
;; How to indent between square brackets?
(defun TeX-brace-count-line-override ()
"Count number of open/closed braces."
(save-excursion
(let ((count 0) (limit (line-end-position)) char)
(while (progn
(skip-chars-forward "^{}[]\\\\" limit)
(when (and (< (point) limit) (not (TeX-in-comment)))
(setq char (char-after))
(forward-char)
(cond ((eq char ?\{)
(setq count (+ count TeX-brace-indent-level)))
((eq char ?\})
(setq count (- count TeX-brace-indent-level)))
((eq char ?\[)
(setq count (+ count TeX-brace-indent-level)))
((eq char ?\])
(setq count (- count TeX-brace-indent-level)))
((eq char ?\\)
(when (< (point) limit)
(forward-char) t))))))
count)))
(defun toggle-TeX-brace-count-line-override ()
"Toggle `TeX-brace-count-line-override' advice."
(interactive)
(toggle-advice 'TeX-brace-count-line :override #'TeX-brace-count-line-override))
(toggle-TeX-brace-count-line-override))
Listing lst:configure-bibtex configures the Emacs bibtex
library to use the
LaTeX BiBTeX
dialect for backwards compatibility. Listing
lst:configure-font-latex disables font scaling of section titles. Listing
lst:configure-latex configures latex
for a full featured
LaTeX-section-command
.
(with-eval-after-load 'bibtex
(custom-set-variables '(bibtex-dialect 'BibTeX)))
(with-eval-after-load 'font-latex
(custom-set-variables
'(font-latex-fontify-sectioning 1.0)))
(with-eval-after-load 'latex
(custom-set-variables
'(LaTeX-electric-left-right-brace t)
'(LaTeX-section-hook '(LaTeX-section-heading
LaTeX-section-title
LaTeX-section-toc
LaTeX-section-section
LaTeX-section-label))))
Math-preview
Math-preview uses Mathjax-3 to display LaTeX, MathML,and asciimath in Emacs buffers with help of an external node.js program.
(when (ensure-package-installation 'math-preview)
(with-eval-after-load 'math-preview
;; https://docs.mathjax.org/en/latest/input/tex/extensions/physics.html
(let ((physics (split-string "
abs absolutevalue acomm acos acosecant acosine acot acotangent
acsc admat anticommutator antidiagonalmatrix arccos arccosecant
arccosine arccot arccotangent arccsc arcsec arcsecant arcsin
arcsine arctan arctangent asec asecant asin asine atan atangent
bmqty bqty Bqty bra braket comm commutator cos cosecant cosh
cosine cot cotangent coth cp cross crossproduct csc csch curl dd
derivative det determinant diagonalmatrix diffd differential div
divergence dmat dotproduct dv dyad erf ev eval evaluated exp
expectationvalue exponential expval fderivative fdv flatfrac
functionalderivative grad gradient gradientnabla hypcosecant
hypcosine hypcotangent hypsecant hypsine hyptangent
identitymatrix Im imaginary imat innerproduct ip ket ketbra
laplacian ln log logarithm matrixdeterminant matrixel
matrixelement matrixquantity mdet mel mqty naturallogarithm norm
op order outerproduct partialderivative paulimatrix pb
pderivative pdv pmat pmqty Pmqty poissonbracket pqty Pr
principalvalue Probability pv PV qall qand qas qassume qc qcc
qcomma qelse qeven qfor qgiven qif qin qinteger qlet qodd qor
qotherwise qq qqtext qsince qthen qty quantity qunless qusing
rank Re real Res Residue sbmqty sec secant sech sin sine sinh
smallmatrixquantity smdet smqty spmqty sPmqty svmqty tan tangent
tanh tr Tr trace Trace va var variation vb vdot vectorarrow
vectorbold vectorunit vmqty vnabla vqty vu xmat xmatrix
zeromatrix zmat")))
(cl-pushnew `("physics" . ,physics)
math-preview-tex-packages-autoload-packages))
(setq math-preview-raise 0.5
math-preview-scale 1
math-preview-tex-default-packages '("autoload" "ams" "physics"))
(let ((command (executable-find "~/node_modules/.bin/math-preview")))
(if command
(setq math-preview-command command)
;; https://stackoverflow.com/a/17509764 answers:
;; How to install an npm package from github directly?
(unless (file-directory-p "~/node_modules")
(make-directory "~/node_modules"))
(cl-destructuring-bind (exit-code output)
(shell-command-with-exit-code
"npm" "install"
"git+https://gitlab.com/matsievskiysv/math-preview.git")
(if (= 0 exit-code)
(message "%s" (string-trim output))
(error "%s" (string-trim output))))))))
TODO Improve the AUCTeX configuration slowly
Writing Org files
Org activation (info)
(when (cl-every #'fboundp '(org-agenda org-capture))
(global-set-key (kbd "C-c a") #'org-agenda)
(global-set-key (kbd "C-c c") #'org-capture)
(with-eval-after-load 'ol
(global-set-key (kbd "C-c l") #'org-store-link)
(global-set-key (kbd "C-c C-l") #'org-insert-link-global)))
Org customization
Listing lst:customize-org-babel, lst:customize-org, lst:customize-org-link does basic customization of Org-mode variables. In addition, listing lst:customize-org-babel selects the Python interpreter by means of src_emacs-lisp
(eval
)} defined in listing lst:choose-common-python-interpreter. Listing lst:customize-org-for-lualatex-export, and lst:customize-ox-latex-for-lualatex-export configure Org-mode for export to LuaLaTeX. Listing lst:customize-ob-python allows to pretty-print Python session source block values with black instead of pprint. Listing lst:customize-org-link configuresorg-link
to use relative file path links.
Listing lst:customize-org-latex-classes defines org-latex-classes (info) for
backward compatibility. See table tab:org-latex-class-tag-placeholder and type
(eval
for an explanation of the code in listing lst:customize-org-latex-classes.#+caption[The relation tag-placeholder in listing lst:customize-org-latex-classes]:
tag | placeholder |
---|---|
+1 | [DEFAULT-PACKAGES] |
+2 | [PACKAGES] |
+3 | [EXTRA] |
-1 | [NO-DEFAULT-PACKAGES] |
-2 | [NO-PACKAGES] |
-3 | [NO-EXTRA] |
(with-eval-after-load 'ob-core
(custom-set-variables
'(org-confirm-babel-evaluate nil)))
(with-eval-after-load 'ob-latex
(custom-set-variables
'(org-babel-latex-preamble
(lambda (_)
"\\documentclass[preview]{standalone}\n"))
'(org-babel-latex-begin-env
(lambda (_)
"\\begin{document}\n"))
'(org-babel-latex-end-env
(lambda (_)
"\\end{document}\n"))))
(with-eval-after-load 'ob-lisp
(with-eval-after-load 'sly
(custom-set-variables
'(org-babel-lisp-eval-fn #'sly-eval))))
(with-eval-after-load 'ob-python
(choose-common-python-interpreter 'python))
(with-eval-after-load 'ob-python
(defun org-babel-python-format-session-value-override
(src-file result-file result-params)
"Return Python code to evaluate SRC-FILE and write result to RESULT-FILE.
Use `black' instead of `pprint' when \"pp\" is a member of RESULT-PARAMS."
(format "\
import ast
with open('%s') as __org_babel_python_tmpfile:
__org_babel_python_ast = ast.parse(__org_babel_python_tmpfile.read())
__org_babel_python_final = __org_babel_python_ast.body[-1]
if isinstance(__org_babel_python_final, ast.Expr):
__org_babel_python_ast.body = __org_babel_python_ast.body[:-1]
exec(compile(__org_babel_python_ast, '<string>', 'exec'))
__org_babel_python_final = eval(
compile(ast.Expression(__org_babel_python_final.value), '<string>', 'eval')
)
with open('%s', 'w') as __org_babel_python_tmpfile:
if %s:
import black
__org_babel_python_tmpfile.write(
black.format_str(repr(__org_babel_python_final), mode=black.Mode())
)
else:
__org_babel_python_tmpfile.write(str(__org_babel_python_final))
else:
exec(compile(__org_babel_python_ast, '<string>', 'exec'))
__org_babel_python_final = None"
(org-babel-process-file-name src-file 'noquote)
(org-babel-process-file-name result-file 'noquote)
(if (member "pp" result-params) "True" "False"))))
(with-eval-after-load 'emacs
(defun toggle-org-babel-python-format-session-value-override ()
"Toggle `org-babel-python-format-session-value' advice."
(interactive)
(toggle-advice 'org-babel-python-format-session-value
:override #'org-babel-python-format-session-value-override))
(toggle-org-babel-python-format-session-value-override))
(with-eval-after-load 'org
(custom-set-variables
'(org-babel-load-languages '((C . t)
(calc . t)
(dot . t)
(emacs-lisp . t)
(eshell . t)
(fortran . t)
(gnuplot . t)
(js . t)
(latex . t)
(lisp . t)
(maxima . t)
(org . t)
(perl . t)
(python . t)
(shell . t)))
'(org-export-backends '(ascii beamer html icalendar latex odt texinfo))
'(org-file-apps '((auto-mode . emacs)
(directory . emacs)
("\\.mm\\'" . default)
("\\.x?html?\\'" . default)
("\\.pdf\\'" . emacs)))
'(org-modules '(ol-bibtex
ol-doi
ol-eww
ol-info
org-id
org-protocol
org-tempo))
'(org-structure-template-alist '(("a" . "export ascii")
("c" . "center")
("C" . "comment")
("e" . "example")
("E" . "export")
("h" . "export html")
("l" . "export latex")
("q" . "quote")
("s" . "src")
("p" . "src python :session")
("v" . "verse")))))
(with-eval-after-load 'emacs
;; From: "Nicolas Richard" <theonewiththeevillook@yahoo.fr>
;; Date: Fri, 08 Mar 2013 16:23:02 +0100 [thread overview]
;; Message-ID: <87vc913oh5.fsf@yahoo.fr> (raw)
(defun org-electric-dollar ()
"When called once, insert \\(\\) and leave point in between.
When called twice, replace the previously inserted \\(\\) by one $."
(interactive)
(if (and (looking-at "\\\\)") (looking-back "\\\\(" (- (point) 2)))
(progn (delete-char 2)
(delete-char -2)
(insert "$"))
(insert "\\(\\)")
(backward-char 2)))
(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
(define-key org-mode-map
(kbd "C-c <SPC>") #'org-inactive-current-time-stamp)
(define-key org-mode-map
(kbd "C-c C-<SPC>") #'org-active-current-time-stamp)
(define-key org-mode-map (kbd "$") #'org-electric-dollar)
(define-key org-mode-map (kbd "M-q") #'org-fill-paragraph)))
(with-eval-after-load 'ol
(custom-set-variables
'(org-link-file-path-type 'relative)))
(with-eval-after-load 'ox
(custom-set-variables
'(org-export-dispatch-use-expert-ui t)))
(with-eval-after-load 'org
;; 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")))
(custom-set-variables
'(org-preview-latex-default-process 'luamagick)
'(org-latex-default-packages-alist '(("" "fontspec" t ("lualatex"))
("AUTO" "inputenc" t ("pdflatex"))
("T1" "fontenc" t ("pdflatex"))
("" "graphicx" t)
("" "longtable" nil)
("" "wrapfig" nil)
("" "rotating" nil)
("normalem" "ulem" t)
("" "amsmath" t)
("" "amssymb" t)
("" "capt-of" nil)
("" "hyperref" nil)))))
(with-eval-after-load 'ox-latex
(custom-set-variables
'(org-latex-pdf-process
;; https://tecosaur.github.io/emacs-config/#compiling
`(,(concat "latexmk -f -pdf -%latex"
" -interaction=nonstopmode -shell-escape -outdir=%o %f")))
'(org-latex-compiler "lualatex")
'(org-latex-hyperref-template "\\hypersetup{
pdfauthor={%a},
pdftitle={%t},
pdfkeywords={%k},
pdfsubject={%d},
pdfcreator={%c},
pdflang={%L},
citecolor=blue,
colorlinks=true,
filecolor=blue,
hyperfootnotes=false,
linkcolor=blue,
unicode=true,
urlcolor=blue,
}\n")
'(org-latex-listings 'minted)
'(org-latex-minted-langs '((cc "c++")
(conf "text")
(cperl "perl")
(diff "diff")
(shell-script "bash")
(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))
'(org-latex-prefer-user-labels t)
'(org-latex-subtitle-separate t)))
(with-eval-after-load 'ox-latex
(mapc (function (lambda (element)
(add-to-list 'org-latex-classes element)))
(nreverse
(quote (("elsarticle-1+2+3" ; Elsevier journals
"\\documentclass{elsarticle}
[NO-DEFAULT-PACKAGES]
[PACKAGES]
[EXTRA]"
("\\section{%s}" . "\\section*{%s}")
("\\subsection{%s}" . "\\subsection*{%s}")
("\\subsubsection{%s}" . "\\subsubsection*{%s}")
("\\paragraph{%s}" . "\\paragraph*{%s}")
("\\subparagraph{%s}" . "\\subparagraph*{%s}"))
("article-1+2+3"
"\\documentclass{article}
[NO-DEFAULT-PACKAGES]
[PACKAGES]
[EXTRA]"
("\\section{%s}" . "\\section*{%s}")
("\\subsection{%s}" . "\\subsection*{%s}")
("\\subsubsection{%s}" . "\\subsubsection*{%s}")
("\\paragraph{%s}" . "\\paragraph*{%s}")
("\\subparagraph{%s}" . "\\subparagraph*{%s}"))
("report-1+2+3"
"\\documentclass[11pt]{report}
[NO-DEFAULT-PACKAGES]
[PACKAGES]
[EXTRA]"
("\\part{%s}" . "\\part*{%s}")
("\\chapter{%s}" . "\\chapter*{%s}")
("\\section{%s}" . "\\section*{%s}")
("\\subsection{%s}" . "\\subsection*{%s}")
("\\subsubsection{%s}" . "\\subsubsection*{%s}"))
("book-1+2+3"
"\\documentclass[11pt]{book}
[NO-DEFAULT-PACKAGES]
[PACKAGES]
[EXTRA]"
("\\part{%s}" . "\\part*{%s}")
("\\chapter{%s}" . "\\chapter*{%s}")
("\\section{%s}" . "\\section*{%s}")
("\\subsection{%s}" . "\\subsection*{%s}")
("\\subsubsection{%s}" . "\\subsubsection*{%s}")))))))
Citar: citing bibliography with Org Mode
Citar is a completing-read front-end to browse and act on BibTeX, BibLaTeX, as
well as CSL JSON bibliographic data with LaTeX, markdown, and org-cite editing
support. In combination with vertico, orderless, embark, marginalia, and
consult, Citar provides quick filtering and selecting of bibliographic entries
from the minibuffer, as well as the option to run different commands on those
selections. The article Citations in org-mode: Org-cite and Citar tries to walk
you from understanding the general context (bibliography producer, text
processor, text to product convertor) to an Emacs setup. Listing
lst:configure-oc-cite+citar shows my org-cite
, citar
, and org
configuration.
(with-eval-after-load 'oc
;; Org-9.5 needs the requirements, but Org-9.6 does not.
(require 'oc-biblatex)
(require 'oc-csl))
(with-eval-after-load 'org
(define-key org-mode-map (kbd "C-c b") #'org-cite-insert))
(when (ensure-package-installation 'citar 'citar-embark)
(with-eval-after-load 'embark
(when (fboundp 'citar-embark-mode)
(citar-embark-mode +1)))
(with-eval-after-load 'oc
(custom-set-variables
'(org-cite-export-processors '((latex biblatex)
(t csl)))
'(org-cite-global-bibliography '("~/VCS/research/refs.bib")))
(when (require 'citar nil 'noerror)
(custom-set-variables
'(org-cite-activate-processor 'citar)
'(org-cite-follow-processor 'citar)
'(org-cite-insert-processor 'citar))))
(with-eval-after-load 'org
(when (require 'citar nil 'noerror)
(custom-set-variables
'(citar-bibliography '("~/VCS/research/refs.bib"))
'(citar-library-file-extensions '("djvu" "pdf"))
'(citar-library-paths '("~/VCS/research/papers/"))))))
Ref. [cite:@Schulte.MCSE.2011.41] shows that Org-mode is a simple, plain-text
markup language for hierarchical documents allowing intermingling of data, code,
and prose. It serves as an example link to to show how to use Citar within this
setup, provided that citar-bibliography
and citar-library-paths
point to
valid directories and files. In an Org-mode buffer this setup allows to:
-
Insert one or more Org-mode cite links:
- Type .
-
Navigate to a bibliographic entry to insert:
- Select it with
(eval
to select more entries. - Select it with
(eval
to select the last entry.
- Select it with
-
View an electronic copy of an Org-mode cite link:
- Move point to the Org-mode cite link.
- Type .
- Navigate to the corresponding library file.
- Select it.
-
Open the DOI of an Org-mode cite link:
- Move point to the Org-mode cite link.
- Type .
- Navigate to the corresponding link.
- Select it.
TODO Compare bibtex and biblatex
- Using bibtex: a short guide
- The biblatex package
- Rebiber: A tool for normalizing bibtex with official info
- A biblatex implementation of the AIP and APS bibliography style
(with-eval-after-load 'emacs
;; https://tex.stackexchange.com/a/579356 answers
;; "How to solve Biber exiting with error code 2 but no error messages?"
(defun biber-delete-cache ()
"Delete the `Biber' cache to get rid of `Biber' exit code 2."
(interactive)
(cl-destructuring-bind (exit-code output)
(shell-command-with-exit-code "rm" "-rf" "$(biber --cache)")
(if (= 0 exit-code)
(message "%s" (string-trim output))
(error "%s" (string-trim output))))))
Engrave Faces
This package aims to produce a versatile generic core which can process a
fontified buffer and pass the data to any number of backends to deal with
specific output formats and it should work with org-mode
. Thanks to the
listings below, Engrave Faces and Org source block export to LaTeX (info) are
capable of handling this /gav451/emacs.d/src/commit/d956948796b91e75de68af035ac39fff96fd15df/README.org file:
- Listing lst:ensure-engrave-faces-with-latex-face-mapper-fix ensures
installation of
engrave-faces
and makesengrave-faces-latex-face-mapper
handle multi-line documentation strings. - Listing lst:ensure-engrave-faces-with-latex-face-apply-fix ensures
installation of
engrave-faces
and makesengrave-faces-latex-face-apply
handle symbolic color name strings (in addition to the hexadecimal strings). - The
org-latex-engraved-source-block-filter
in listing lst:org-latex-engraved-source-block-filter and theorg-latex-engraved-preamble
customization in listing lst:smart-latex-engrave-org-source-blocks allow to engraveorg-mode
source blocks to "floating unbreakable" or "non-floating breakable" LaTeX environments. - Listing lst:ox-engraved-emacs-lisp-setup shows how to use this configuration.
(when (ensure-package-installation 'engrave-faces)
(require 'engrave-faces-latex nil t)
(defun engrave-faces-latex-face-mapper-override (faces content)
"Create a LaTeX representation of CONTENT With FACES applied."
(let* ((style (engrave-faces-preset-style faces))
(protected-content
(funcall
(if (and engrave-faces-latex-mathescape
(eq 'font-lock-comment-face (car style)))
#'engrave-faces-latex--protect-content-mathescape
#'engrave-faces-latex--protect-content)
content)))
;; Wrap groups of "words joined by blank characters" in LaTeX commands.
;; Do not wrap newlines and other whitespace between those groups.
(let ((regexp (rx (or (group-n 1 (+ graph ) (* (+ blank) (+ graph)))
(group-n 2 (+ (any "\n" space))))))
(slug (when (and style
(eq engrave-faces-latex-output-style 'preset))
(plist-get (cdr style) :slug)))
(start 0)
(stack))
(while (string-match regexp protected-content start)
(setq start (match-end 0))
(when-let ((pc (match-string 1 protected-content)))
(if (stringp slug)
(push (concat "\\EF" slug "{" pc "}") stack)
(push (engrave-faces-latex-face-apply faces pc) stack)))
(when-let ((pc (match-string 2 protected-content)))
(push pc stack)))
(apply #'concat (nreverse stack))))))
(with-eval-after-load 'emacs
(defun toggle-engrave-faces-latex-face-mapper-override ()
"Toggle `engrave-faces-latex-face-mapper' advice."
(interactive)
(toggle-advice 'engrave-faces-latex-face-mapper
:override #'engrave-faces-latex-face-mapper-override))
(toggle-engrave-faces-latex-face-mapper-override))
(when (and (ensure-package-installation 'engrave-faces)
(require 'engrave-faces-latex nil t))
(defun engrave-faces-latex--color (color)
"Convert COLOR loosely to a string of six hexadecimal digits."
(if (char-equal ?# (aref color 0))
(substring color 1 7)
(apply 'format "%02x%02x%02x"
(mapcar (lambda (c) (ash c -8))
(color-values color)))))
(defun engrave-faces-latex-face-apply-override (faces content)
"Convert attributes of FACES to LaTeX commands applied to CONTENT."
(let ((attrs (engrave-faces-merge-attributes faces)))
(let ((bg (plist-get attrs :background))
(fg (plist-get attrs :foreground))
(it (eql (plist-get attrs :slant) 'italic))
(bl (member (plist-get attrs :weight) '(bold extra-bold)))
(st (plist-get attrs :strike-through)))
(concat
(when bg (concat "\\colorbox[HTML]{"
(engrave-faces-latex--color bg) "}{"))
(when fg (concat "\\textcolor[HTML]{"
(engrave-faces-latex--color fg) "}{"))
(when st "\\sout{") (when bl "\\textbf{") (when it "\\textit{")
content
(when bg "}") (when fg "}") (when st "}") (when bl "}") (when it "}"))))))
(with-eval-after-load 'emacs
(defun toggle-engrave-faces-latex-face-apply-override ()
"Toggle `engrave-faces-latex-face-apply' advice."
(interactive)
(toggle-advice 'engrave-faces-latex-face-apply
:override #'engrave-faces-latex-face-apply-override))
(toggle-engrave-faces-latex-face-apply-override))
(defun org-latex-engraved-source-block-filter (data _backend _info)
"Replace \"Code\" with \"Breakable\" in non-floating environments.
Customize `org-latex-engraved-preamble' to define a Breakable (non-floating)
environment and an unbreakable Code (floating) environment."
(unless (string-match "^\\\\DeclareTColorBox\\[\\]{Breakable}"
org-latex-engraved-preamble)
(user-error "`org-latex-engraved-preamble' defines no `Breakable' environment"))
(when (eq org-latex-src-block-backend 'engraved)
(let ((enter "^\\\\begin{Code}\n\\\\begin{Verbatim}")
(leave "^\\\\end{Verbatim}\n\\\\end{Code}"))
;; Transform only blocks matching the enter regexp at position 0.
(when (and (string-match enter data) (eql 0 (match-beginning 0)))
(setq data (replace-match
"\\begin{Breakable}\n\\begin{Verbatim}" t t data))
(when (string-match leave data (match-end 0))
(setq data (replace-match
"\\end{Verbatim}\n\\end{Breakable}" t t data))
(when (string-match enter data)
(user-error "The `enter' regexp `%s' should not match" enter))
(when (string-match leave data)
(user-error "The `leave' regexp `%s' should not match" leave))
data)))))
(defun smart-latex-engrave-org-source-blocks ()
"Enable smart LaTeX engraving of `org-src-mode' blocks.
Sets backend and preamble locally to support floating unbreakable LaTeX
environments and non-floating breakable LaTeX environments by means of
`org-latex-engraved-source-block-filter'."
(interactive)
(autoload 'org-latex-src-block-backend "ox-latex" nil 'noerror)
(setq-local org-latex-src-block-backend 'engraved
org-latex-engraved-preamble "\\usepackage{fvextra}
[FVEXTRA-SETUP]
% Make code and line numbers normalsize. Make line numbers grey.
\\renewcommand\\theFancyVerbLine{
\\normalsize\\color{black!40!white}\\arabic{FancyVerbLine}}
% In case engrave-faces-latex-gen-preamble has not been run.
\\providecolor{EfD}{HTML}{f7f7f7}
\\providecolor{EFD}{HTML}{28292e}
% To use \\DeclareTColorBox from the tcolorbox package:
\\usepackage[breakable,xparse]{tcolorbox}
% Define a Breakable environment to fontify code outside floats.
\\DeclareTColorBox[]{Breakable}{o}{
colback=EfD, colframe=EFD, colupper=EFD,
fontupper=\\normalsize\\setlength{\\fboxsep}{0pt},
IfNoValueTF={#1}{
boxsep=1pt, arc=1pt, outer arc=1pt, boxrule=0.5pt,
}{
boxsep=1pt, arc=0pt, outer arc=0pt, boxrule=0pt, leftrule=1pt
},
left=2pt, right=2pt, top=1pt, bottom=1pt, breakable
}
% Define an unbreakable Code environment to fontify code inside floats.
\\DeclareTColorBox[]{Code}{o}{
colback=EfD, colframe=EFD, colupper=EFD,
fontupper=\\normalsize\\setlength{\\fboxsep}{0pt},
IfNoValueTF={#1}{
boxsep=1pt, arc=1pt, outer arc=1pt, boxrule=0.5pt
}{
boxsep=1pt, arc=0pt, outer arc=0pt, boxrule=0pt, leftrule=1pt
},
left=2pt, right=2pt, top=1pt, bottom=1pt,
}
[LISTINGS-SETUP]"))
(with-eval-after-load 'ox
(make-variable-buffer-local 'org-export-filter-src-block-functions)
(add-to-list 'org-export-filter-src-block-functions
'org-latex-engraved-source-block-filter)
(when (fboundp 'smart-latex-engrave-org-source-blocks)
(smart-latex-engrave-org-source-blocks)))
Making Org hyperlink types (info)
The listings below implement four groups of org-link
types:
- Listing lst:org-ref-like-org-link-types defines
org-link
types for backwards compatibility with org-ref. - Listing lst:define-org-pdfview-link-type uses ideas from the definition of
docview-org-link
anddoi-org-link
types to define anpdfview-org-link
type for use with pdf-tools. - Listing lst:define-org-yt-link-type reimplements the code of the post Extending org-mode to handle YouTube links allowing to open the YouTube link Extending org-mode to handle YouTube links link.
- 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 constantsorg-info-emacs-documents
andorg-info-other-documents
as buffer local variables in order to export theinfo-org-link
types in this document tohtml
and LaTeX correctly.
(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))))
(with-eval-after-load 'ol
(autoload 'pdf-view-goto-page "pdf-view" 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 (in-emacs = 1) to ensure
;; org-link-frame-setup is respected.
(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)))))
(with-eval-after-load 'ol
;; https://bitspook.in/blog/extending-org-mode-to-handle-youtube-links/
(org-link-set-parameters "yt"
:follow #'org-yt-open
:export #'org-yt-export)
(defun org-yt-open (path prefix)
"Open an \"yt\" link."
(let* ((url (format "https:%s" path))
(display-buffer-alist
`((,shell-command-buffer-name-async . (display-buffer-no-window)))))
(if (and prefix (executable-find "mpv"))
(browse-url url)
(async-shell-command (format "mpv \"%s\"" url))
(message "Launched mpv with \"%s\"" url))))
(defun org-yt-export (path desc backend _)
"Export an \"yt\" link."
(pcase backend
(`html
(let* ((video-id (cadar (url-parse-query-string path)))
(url (if (string-empty-p video-id)
path
(format "//youtube.com/embed/%s" video-id))))
(format
(concat "<iframe width=\"560\" height=\"315\" src=\"%s\" "
"title=\"%s\" frameborder=\"0\" allowfullscreen></iframe>")
url desc)))
(`latex (format "\\href{https:%s}{%s}" path desc))
(_ path))))
(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)))))
(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")
("marginalia" . "https://github.com/minad/marginalia")
("maxima" .
"https://maxima.sourceforge.io/docs/manual/maxima_singlepage.html")
("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)))
Translate capital keywords (old) to lower case (new)
(with-eval-after-load 'emacs
;; https://tecosaur.github.io/emacs-config/#translate-capital-keywords
(defun org-syntax-convert-keyword-case-to-lower ()
"Convert all #+KEYWORDS to #+keywords."
(interactive)
(when (derived-mode-p 'org-mode)
(save-excursion
(goto-char (point-min))
(let ((count 0)
(case-fold-search nil))
(while (re-search-forward "^[ \t]*#\\+[A-Z_]+" nil t)
(unless (s-matches-p "RESULTS" (match-string 0))
(replace-match (downcase (match-string 0)) t)
(setq count (1+ count))))
(message "Replaced %d keywords" count))))))
Evaluate specific source blocks at load-time
How to do load time source block evaluation
(with-eval-after-load 'emacs
(defun org-eval-named-blocks-with-infix (infix)
"Evaluate all source blocks having INFIX in their name."
(when (eq major-mode 'org-mode)
(let ((blocks
(org-element-map
(org-element-parse-buffer 'greater-element nil) 'src-block
(lambda (block)
(when-let ((name (org-element-property :name block)))
(when (string-match-p infix name) block))))))
(dolist (block blocks)
(goto-char (org-element-property :begin block))
(org-babel-execute-src-block)))))
(defun org-eval-emacs-lisp-setup-blocks ()
"Evaluate all source blocks having \"emacs-lisp-setup\" in their name."
(interactive)
(org-eval-named-blocks-with-infix "emacs-lisp-setup"))
(defun org-eval-python-setup-blocks ()
"Evaluate all source blocks having \"python-setup\" in their name."
(interactive)
(org-eval-named-blocks-with-infix "python-setup"))
;; Emacs looks for "Local variables:" after the last "newline-formfeed".
(add-to-list 'safe-local-eval-forms '(org-eval-emacs-lisp-setup-blocks))
(add-to-list 'safe-local-eval-forms '(org-eval-python-setup-blocks)))
Easy LaTeX preamble editing
There are at least two ways (new and old) to edit the LateX preamble
latex_header
and latex-extra_header
export options easily in LaTeX source or
export blocks. This Org (info) file uses the new way, but keeps the old way for
backwards compatibility.
The new way – exploiting an idea of Omar Antolin Camarena – is to code new <LANGUAGE>-modes allowing to edit in LaTeX mode and to export to LaTeX code with correct LaTeX preamble export setting prefixes. Here, are links to three posts exposing his idea:
- Export LaTeX macros to LaTeX and HTML/MathJax preambles (reddit),
- Export JavaScript source blocks to script tags in HTML (reddit),
- Export JavaScript source blocks to script tags in HTML (SX).
Listing lst:org-babel-latex-header-blocks implements this way by means of two
new <LANGUAGE>-modes: latex-header
and latex-extra-header
.
(with-eval-after-load 'emacs
(defun prefix-all-lines (prefix body)
(with-temp-buffer
(insert body)
(string-insert-rectangle (point-min) (point-max) prefix)
(buffer-string)))
(defun org-babel-execute:latex-extra-header (body _params)
"Execute a block of LaTeX extra header lines with org-babel.
This function is called by `org-babel-execute-src-block' and
prefixes all lines with \"#+latex_extra_header: \"."
(prefix-all-lines "#+latex_extra_header: " body))
(defun org-babel-execute:latex-header (body _params)
"Execute a block of LaTeX header lines with org-babel.
This function is called by `org-babel-execute-src-block' and
prefixes all lines with \"#+latex_header: \"."
(prefix-all-lines "#+latex_header: " body))
(defvar org-babel-default-header-args:latex-extra-header
'((:exports . "results") (:results . "raw")))
(defvar org-babel-default-header-args:latex-header
'((:exports . "results") (:results . "raw")))
(with-eval-after-load 'org-src
(custom-set-variables
'(org-src-window-setup 'current-window))
(add-to-list 'org-src-lang-modes '("toml" . conf-toml))
(add-to-list 'org-src-lang-modes '("latex-header" . latex))
(add-to-list 'org-src-lang-modes '("latex-extra-header" . latex))))
The old way is to use a special export attribute as in the function
org-latex-header-blocks-filter
in ox-extra.el. Apparently, nobody is using
this broken function (broken, since it relies on support only in org-mode before
2014-11-11
). Listing lst:org-latex-header-blocks-filter proposes a fix for
org-latex-header-blocks-filter
.
(with-eval-after-load 'ox
(defun org-latex-header-blocks-filter (backend)
"Convert marked LaTeX export blocks to \"#+latex_header: \" lines.
The marker is a line \"#+header: :header yes\" preceding the block.
For instance, the LaTeX export block
,#+header: :header yes
,#+begin_export latex
% This line converts to a LaTeX header line.
,#+end_export
converts to
\"#+latex_header: % This line converts to a LaTeX header line.\"."
(when (org-export-derived-backend-p backend 'latex)
(let ((blocks
(org-element-map
(org-element-parse-buffer 'greater-element nil) 'export-block
(lambda (block)
(let ((type (org-element-property :type block))
(header (org-export-read-attribute :header block :header)))
(when (and (string= type "LATEX") (string= header "yes"))
block))))))
(mapc (lambda (block)
;; Set point to where to insert LaTeX header lines
;; after deleting the block.
(goto-char (org-element-property :post-affiliated block))
(let ((lines
(split-string (org-element-property :value block) "\n")))
(delete-region (org-element-property :begin block)
(org-element-property :end block))
(dolist (line lines)
(insert (concat "#+latex_header: "
(replace-regexp-in-string "\\` *" "" line)
"\n")))))
;; Reverse to go upwards to avoid wrecking the list of
;; block positions in the file that would occur in case
;; of going downwards.
(reverse blocks)))))
;; Push the filter on the hook.
(cl-pushnew #'org-latex-header-blocks-filter
org-export-before-parsing-hook))
This file uses the new way, while keeping the old way for backwards compatibility, because the new way feels less hackish than the old way. A practical difference is that new way source blocks (contrary to old way export blocks) do not work in #+SETUPFILE: <FILE>, but only in #+INCLUDE: <FILE> files.
#+INCLUDE: <FILE> (info)
Listing lst:source-file-export-keyword-settings shows the first nine lines of this /gav451/emacs.d/src/commit/d956948796b91e75de68af035ac39fff96fd15df/README.org file. The last line shows that include.org is the argument for #+INCLUDE: <FILE>.
Listing lst:by-backend-kbd-org-macro defines the tools for the definition of the
Org mode kbd
macro on the fifth line of listing
lst:source-file-export-keyword-settings.
(with-eval-after-load 'emacs
(when (ensure-package-installation 'htmlize)
(autoload 'htmlize-protect-string "htmlize" nil t))
;; https://orgmode.org/worg/org-contrib/babel/languages/ob-doc-LaTeX.html
(defmacro by-backend (&rest body)
"Help for org-export backend dependent execution."
`(cl-case ',(bound-and-true-p org-export-current-backend) ,@body))
(defun by-backend-kbd-org-macro (keys)
"Help for an org-export backend dependent \"#+macro: kbd\"."
(by-backend
(html (format "@@html:<kbd>%s</kbd>@@" (htmlize-protect-string keys)))
(latex (format "@@latex:\\colorbox{PowderBlue}{\\texttt{%s}}@@" keys)))))
Listing lst:use-latex-header-1, lst:use-latex-header-2, lst:use-latex-header-3, lst:use-latex-header-4, and lst:use-latex-header-5 tangle into the include.org file in order to create the LaTeX preamble.
#+begin_src latex-header
% LuaLaTeX-, PdfLaTeX-, or XeTeX-COMPILER COMPATIBILITY:
% Prevent collisions by using font packages before compiler specific packages.
\usepackage{ifthen,ifluatex,ifxetex}
\ifthenelse{\boolean{luatex}}{
\usepackage{fontspec} % lualatex
}{\ifthenelse{\boolean{xetex}}{
\usepackage{mathspec} % xetex
}{
\usepackage[T1]{fontenc} % pdflatex
\usepackage[utf8]{inputenc} % pdflatex
}
}
\usepackage{amsmath}
\usepackage{amssymb}
\usepackage{pifont} % check mark (\ding{52}) and cross mark (\ding{56})
\usepackage{textcomp} % \texttimes
\usepackage{wasysym} % \diameter
% Org-mode REQUIREMENTS:
\usepackage{graphicx}
\usepackage{longtable}
\usepackage{wrapfig}
\usepackage{rotating}
\usepackage[normalem]{ulem}
\usepackage{capt-of}
\usepackage{hyperref}
#+end_src
#+begin_src latex-header
% LANGUAGE:
\usepackage{babel}
\usepackage{fvextra}
\usepackage{csquotes}
% LISTS:
\usepackage{enumitem}
\setlist{noitemsep}
% LISTINGS:
% Section 2.6 of caption-eng.pdf (texdoc caption) explains that the sign
% of "skip" depends on the assumption "position=above" or "position=below".
% The assumption should match the real caption position in the LaTeX code.
\usepackage{caption}
\usepackage[newfloat]{minted}
\captionsetup[listing]{position=below,skip=0em}
\usemintedstyle{xcode}
% TABLES:
% https://tex.stackexchange.com/questions/341205/
% what-is-the-difference-between-tabular-tabular-and-tabularx-environments
% https://emacs.stackexchange.com/questions/26179/
% change-org-mode-table-style-just-for-latex-export
% https://tex.stackexchange.com/questions/468585/
% table-formatting-using-siunitx
\usepackage{booktabs}
\usepackage{colortbl}
\usepackage{tabularx} % DANGER: beware of Org table :width and :align options!
#+end_src
#+begin_src latex-header
% PAGE LAYOUT:
\usepackage{fancyhdr}
\usepackage{lastpage}
\usepackage[
headheight=20mm,
top=40mm,
bottom=20mm,
left=0.1\paperwidth,
right=0.1\paperwidth,
heightrounded,
verbose,
]{geometry}
% TECHNICS:
\usepackage{siunitx}
\usepackage{tikz}
#+end_src
#+begin_src latex-header
% FLOAT BARRIERS:
% https://tex.stackexchange.com/questions/118662/use-placeins-for-subsections
% Make section an implicit float barrier:
\usepackage[section]{placeins}
% Make subsection an implicit float barrier:
\makeatletter
\AtBeginDocument{%
\expandafter\renewcommand\expandafter\subsection\expandafter{%
\expandafter\@fb@secFB\subsection
}%
}
\makeatother
% Make subsubsection an implicit float barrier:
\makeatletter
\AtBeginDocument{%
\expandafter\renewcommand\expandafter\subsubsection\expandafter{%
\expandafter\@fb@secFB\subsubsection
}%
}
\makeatother
#+end_src
#+begin_src latex-header
% FANCY HEADERS AND FOOTERS:
% Add fancy headers and footers to normal pages.
\pagestyle{fancy}
\fancyhf{}
\renewcommand{\footrulewidth}{0.4pt}
\fancyfoot[C]{\emph{
Emacs setup for use with \LaTeX{}, Org, and Python -- Gerard Vermeulen}}
\renewcommand{\headrulewidth}{0.4pt}
\fancyhead[L]{\includegraphics[height=1.8cm]{Org-mode-unicorn.png}}
\fancyhead[C]{
Page: \thepage/\pageref{LastPage} \\
\text{ } \\
\text{ } \\
DRAFT
}
\fancyhead[R]{\includegraphics[height=1.8cm]{Emacs-logo.png}}
% Add fancy header and footer to custom titlepage.
% https://tex.stackexchange.com/questions/506102/
% adding-header-and-footer-to-custom-titlepage
\fancypagestyle{titlepage}{%
\fancyhf{}
\renewcommand{\footrulewidth}{0.4pt}
\fancyfoot[C]{\emph{
Emacs setup for use with \LaTeX{}, Org, and Python -- Gerard Vermeulen}}
\renewcommand{\headrulewidth}{0.4pt}
\fancyhead[L]{\includegraphics[height=1.8cm]{Org-mode-unicorn.png}}
\fancyhead[C]{
\pageref{LastPage} pages \\
\text{ } \\
\text{ } \\
DRAFT
}
\fancyhead[R]{\includegraphics[height=1.8cm]{Emacs-logo.png}}
}
% #+latex_header: END.
#+end_src
Advanced LaTeX export settings
Listing lst:ox-latex-emacs-lisp-setup initializes the buffer local variables
org-latex-classes
, org-latex-title-command
, org-latex-toc-command
, and
org-latex-subtitle-format
. Listing /gav451/emacs.d/src/commit/d956948796b91e75de68af035ac39fff96fd15df/lst/title-page is a template to initialize
org-latex-title-command
. Type
(eval
,(eval
}},(eval
, and(eval
to read how those variables control exporting from Org-mode to LaTeX. (when (require 'ox-latex nil 'noerror)
;; https://emacs.stackexchange.com/questions/47347/
;; customizing-org-latex-title-command-to-edit-title-page
(make-variable-buffer-local 'org-latex-classes)
(cl-pushnew '("article-local"
"\\documentclass[11pt]{article}
[NO-DEFAULT-PACKAGES]
[PACKAGES]
[EXTRA]"
("\\section{%s}" . "\\section*{%s}")
("\\subsection{%s}" . "\\subsection*{%s}")
("\\subsubsection{%s}" . "\\subsubsection*{%s}")
("\\paragraph{%s}" . "\\paragraph*{%s}")
("\\subparagraph{%s}" . "\\subparagraph*{%s}"))
org-latex-classes :key #'car :test #'equal)
(make-variable-buffer-local 'org-latex-title-command)
(setq org-latex-title-command (concat title-page))
(make-variable-buffer-local 'org-latex-toc-command)
(setq org-latex-toc-command "
\\tableofcontents\\label{toc}
\\listoflistings
\\listoftables
\\newpage
")
(make-variable-buffer-local 'org-latex-subtitle-format)
(setq org-latex-subtitle-format ""))
\begin{titlepage}
%% https://tex.stackexchange.com/questions/506102/
%% adding-header-and-footer-to-custom-titlepage
\thispagestyle{titlepage}
\begin{center}
%% Title
\begin{Huge}
{\bf %t} \\
\vspace{1em}
\end{Huge}
%% Author
\begin{Large}
{\bf %a} \\
\vspace{1em}
\end{Large}
\end{center}
\end{titlepage}
Org Syntax
Two tools to grok how Org mode parsing works are the Org Syntax specification and the Org mode parser tutorial. The Org element parsing API boils down to three functions:
- The function
org-element-parse-buffer
implements a fully recursive buffer parser that returns an abstract syntax tree. - The functions
org-element-at-point
andorg-element-context
return information on the document structure around point either at the element level or at the object level in case oforg-element-context
.
Listing lst:grok-org-element-tree improves the Org mode parser tutorial by
defining interactive wrappers that pretty print the results of those
non-interactive org-element
functions to an Emacs-lisp
buffer.
(with-eval-after-load 'org-element
(when (require 'pp nil 'noerror)
(defconst grok-org-output
"*Grok Org Element Output*"
"Grok Org output buffer name.")
(defun grok-org-element-at-point ()
(interactive)
(pp-display-expression
(org-element-at-point) grok-org-output))
(defun grok-org-element-context ()
(interactive)
(pp-display-expression
(org-element-context) grok-org-output))
(defun grok-org-element-parse-buffer ()
(interactive)
(let ((what (completing-read
"granularity: "
'(headline element greater-element object)
nil 'require-match)))
(pp-display-expression
(org-element-parse-buffer what) grok-org-output)))
(defun grok-org-heading-components ()
(interactive)
(pp-display-expression
(org-heading-components) grok-org-output))))
Grammar, spelling, and style tools
Abbrevs (info)
Mickey Peterson has posted Correcting Typos and Misspellings with Abbrev showing
how to use Keyboard Macros (info) to exploit Wikipedia's list of common
misspellings for machines. Listing lst:misspellings-abbrev defines his
keyboard macro under the name misspellings-abrev
. I have used those
directions to define src_emacs-lisp{global-abbrev-table} and to store it in
src_emacs-lisp{abbrev-file-name} for git management. I can change the
abbreviation definitions in this file by means of:
- Execute src_emacs-lisp{(edit-abbrevs)} to alter abbreviation definitions by
editing an
*Abbrevs*
buffer. - Add, edit, or remove definitions of the form
"source" 1 "target"
under the global or a mode-specific table. - Execute src_emacs-lisp{(abbrev-edit-save-buffer)} to save all user abbreviation definitions in the current buffer.
(with-eval-after-load 'emacs
(fset 'misspellings-abbrev
(kmacro-lambda-form
[?\C-s ?- ?> return backspace backspace ?\C-k ?\C-x ?a ?i ?g ?\C-y return]
0 "%d"))
(setq-default abbrev-mode t))
Listing lst:word-games defines the anagram-p
function that migth be used for a
better exploitation of Wikipedia's list of common misspellings for machines in
the future.
(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")))
(defun wordnet-check (word)
"Check whether WORD occurs in the wordnet lexical database."
(cl-destructuring-bind (exit-code output)
(shell-command-with-exit-code "wn" word "-grepn" "-grepv" "-grepa" "-grepr")
(if (= 0 exit-code)
(< 0 (length (string-trim output)))
(error "%s" (string-trim output)))))
(defun better-misspellings ()
"Pick the best from each common misspelling multiple choice of corrections."
(interactive)
(let (start end lines)
(save-excursion
(if (re-search-forward (rx bol "abandon"))
(setq start (match-beginning 0))
(user-error "Failed to find beginning of misspellings"))
(if (re-search-forward (rx bol "Retrieved from"))
(setq end (match-beginning 0))
(user-error "Failed to find end of misspellings")))
(dolist (line (split-string
(buffer-substring-no-properties start end) "\n"))
(pcase-let ((`(,lhs . (,rhs . ,rest)) (split-string line "->")))
(when (and (stringp rhs)
(string-match ", " rhs))
(message "%s -> %s" lhs rhs)
(let ((words (split-string rhs ", "))
(stack))
(dolist (word words)
(when (wordnet-check word)
(message "wordnet finds: %s" word)
(push word stack))))))))))
;; 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))))))
Emacs LEXICal information viewer
The Emacs LEXICal information viewer is a front-end for offline dictionary, etymology, or thesaurus back-ends (current limited to sdcv). Listing lst:configure-lexic configures lexic.
(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")))
(custom-set-variables
'(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"))))
Wordnet
The wordnut package is a major mode interface to Wordnet, a lexical database for the English language. Listing lst:check-wordnut checks whether the system meets the wordnut prerequisites.
(when (ensure-package-installation 'wordnut)
(with-eval-after-load 'wordnut
(if-let ((wn (executable-find "wn")))
(custom-set-variables
'(wordnut-cmd wn))
(message "`wordnut' fails to find the `wn' executable"))))
Writegood mode
Writegood mode is a minor mode to aid in finding common writing problems. The post Matt Might's "My Ph.D. advisor rewrote himself in bash" scripts inspired this mode. Listing lst:configure-writegood-mode configures writegood mode.
(when (and (ensure-package-installation 'writegood-mode)
(fboundp 'writegood-mode))
(global-set-key (kbd "C-c g") #'writegood-mode))
Programming Tools
Eglot
Emacs polyGLOT (eglot) is an Emacs language-server-protocol client that stays out of the way. The following listings contribute to a programming language mode independent eglot configuration:
- Listing lst:ensure-eglot-installation ensures installation of eglot with minimal configuration.
- Listing lst:help-setup-org-src-mode-for-eglot and
lst:setup-python-org-src-mode-for-eglot try to prepare any
org-src-mode
buffers for use with eglot. They are a refactored implementation of the post Using rustic, eglot, and org-babel with LSP support in Emacs for my use with Python. - Listing lst:narrow-format-all-python defines a function to format only the
narrowed region of Python
org-src-mode
blocks prepared by means of the code in listing lst:setup-python-org-src-mode-for-eglot. Do not useformat-all-buffer
on such buffers, sinceformat-all-buffer
does not handle the narrowing. - Listing lst:eglot-maybe-ensure starts eglot in case of proper programming modes and proper directory local variables (meaning in presence of a proper file .dir-locals.el in the root directory of any project using proper programming modes).
(when (ensure-package-installation 'eglot)
;; (defvar eglot-server-programs
;; `((python-mode . ("pylsp" "-vvv")))
;; "Shadow the definition of `eglot-server-programs' in `eglot'.")
(with-eval-after-load 'eglot
(define-key eglot-mode-map (kbd "C-c n") #'flymake-goto-next-error)
(define-key eglot-mode-map (kbd "C-c p") #'flymake-goto-prev-error)
(define-key eglot-mode-map (kbd "C-c r") 'eglot-rename)))
(when (fboundp 'eglot-ensure)
(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 and `org-mode-src' buffer to make `eglot-ensure' succeed.
INFO has a form similar to the return value of
`org-babel-get-src-block-info'. Try to load the tangled file
into the `org-src-mode' buffer as well as to narrow the region to
the Org-mode source block code before calling `eglot-ensure'."
(unless (bound-and-true-p org-src-mode)
(user-error "Buffer %s is no `org-src-mode' buffer" (buffer-name)))
(let ((mark (point))
(body (nth 1 info))
(filename (cdr (assq :tangle (nth 2 info)))))
(when (string= filename "no")
(user-error "Org source block has no tangled file"))
(setq filename (expand-file-name filename))
(unless (file-readable-p filename)
(user-error "Tangled file %s is not readable" filename))
(with-temp-buffer
(insert-file-contents filename 'visit nil nil 'replace)
(unless (search-forward body nil 'noerror)
(user-error "Org source block does not occur in tangled file %s"
filename))
(when (search-forward body nil 'noerror)
(user-error "Org source block occurs twice or more in tangled file %s"
filename)))
(goto-char (point-min))
(insert-file-contents filename 'visit nil nil 'replace)
(let ((max-point (search-forward body))
(min-point (search-backward body)))
(narrow-to-region min-point max-point))
(goto-char mark)
(eglot-ensure))))
(when (fboundp 'eglot-ensure)
;; https://www.reddit.com/r/emacs/comments/w4f4u3
;; /using_rustic_eglot_and_orgbabel_for_literate/
(defun undo-eglot-org-babel-edit-prep()
"Undo the `eglot' setup by deleting the text hidden by narrowing.
This is to advice `org-edit-src-exit' and `org-edit-src-save'."
(when (and (bound-and-true-p org-src-mode)
(buffer-file-name)
(apply #'derived-mode-p eglot-maybe-ensure-modes))
(save-excursion
(goto-char (point-min))
(save-restriction
(widen)
(delete-region (point-min) (point)))
(goto-char (point-max))
(save-restriction
(widen)
(delete-region (point) (point-max))))))
(defun org-babel-edit-prep:python (info)
(eglot-org-babel-edit-prep info))
(advice-add 'org-edit-src-exit :before #'undo-eglot-org-babel-edit-prep)
(advice-add 'org-edit-src-save :before #'undo-eglot-org-babel-edit-prep))
#+caption[Experimental =narrow-format-all:python=]:
(when (and (fboundp 'eglot-ensure)
(fboundp 'format-all-buffer))
(defun narrow-format-all:python ()
"Format narrowed Python `org-src-mode' buffers correctly.
Use this function instead of `format-all-buffer' on `org-babel-edit-prep:python'
prepared buffers."
(interactive)
(when (eq major-mode 'python-mode)
(let ((source-min (point-min))
(source-max (point-max))
(source (current-buffer))
(target (get-buffer-create "*narrow-format-all:python*")))
(with-current-buffer target
(barf-if-buffer-read-only)
(erase-buffer)
(save-excursion
(insert-buffer-substring-no-properties
source source-min source-max))
(python-mode)
(setq-local format-all-formatters '(("Python" black)))
(format-all-buffer)
(let ((target-min (point-min))
(target-max (point-max)))
(with-current-buffer source
(erase-buffer)
(save-excursion
(insert-buffer-substring-no-properties
target target-min target-max)))))))))
(when (fboundp 'eglot-ensure)
(defun eglot-maybe-ensure ()
(when (and (apply #'derived-mode-p eglot-maybe-ensure-modes)
(assoc 'eglot-workspace-configuration dir-local-variables-alist))
(eglot-ensure)))
;; The two hooks `after-change-major-mode-hook' and
;; `hack-local-variables-hook' are OK, but language mode hooks like
;; `python-mode-hook' are not.
(add-hook 'after-change-major-mode-hook #'eglot-maybe-ensure))
Format-all
Listing lst:configure-format-all:
- Configures format-all which is a package that provides an universal interface to code formatters of more than 60 computer languages.
- Adds
format-all-org-babel-post-tangle
toorg-babel-post-tangle-hook
to format tangled Python code.
;; https://github.com/lassik/emacs-format-all-the-code#readme
;; https://ianyepan.github.io/posts/format-all/
;; https://jamesaimonetti.com/posts/formatting-tangled-output-in-org-mode/
(when (ensure-package-installation 'format-all)
;; (with-eval-after-load 'prog-mode
;; (when (autoload 'format-all-ensure-formatter "format-all")
;; (add-hook 'prog-mode-hook #'format-all-ensure-formatter)))
(with-eval-after-load 'ob-tangle
(add-hook
'org-babel-post-tangle-hook
(defun format-all-org-babel-post-tangle ()
(when (derived-mode-p 'python-mode)
(setq-local format-all-formatters '(("Python" black)))
(format-all-buffer)
(save-buffer)
(message "Saved reformatted tangled buffer `%s'" (buffer-file-name)))))))
Flymake (info)
Flymake is an universal on-the-fly syntax checker for Emacs. It is a requirement of eglot, but you can use it without eglot for instance in python-mode buffers that do not visit a file.
Programming Modes
Common Lisp programming
Listing lst:configure-sly configures the Sly (info) Common Lisp IDE for Emacs for use with Steel Bank Common Lisp (sbcl):
- It configures
sly-default-lisp
andsly-lisp-implementations
as in thesly
documentation string instead of in the multiple lisps (info) manual. - It ensures the automatic connection to the lisp server (info) when opening a Common Lisp file.
- It configures searching documentation in the Common Lisp HyperSpec according to the basic customization (info) manual.
Finally, listing lst:configure-sly uses a technique to load Slynk faster (info) by means of a custom core file in src_emacs-lisp{no-littering-var-directory}. Listing lst:sbcl-core-for-sly tangles to a script to dump such a SBCL core.
(when (ensure-package-installation 'sly)
(with-eval-after-load 'sly
(custom-set-variables
;; Customize `sly-default-lisp' instead of `inferior-lisp-program',
;; because `sly' uses `inferior-lisp-program' only as a backwards
;; compatibility fallback option.
'(sly-default-lisp 'sbcl)
`(sly-lisp-implementations
'((sbcl (,(executable-find "sbcl")
"--core"
,(no-littering-expand-var-file-name "sbcl.core-for-sly"))))))
(add-hook 'sly-mode-hook
(defun on-sly-mode-hook ()
(unless (sly-connected-p)
(save-excursion (sly)))))
(cond
((eq system-type 'darwin)
(setq common-lisp-hyperspec-root
"file:///usr/local/share/doc/hyperspec/HyperSpec/")
(setq common-lisp-hyperspec-symbol-table
(concat common-lisp-hyperspec-root "Data/Map_Sym.txt")))
((eq system-type 'gnu/linux)
(setq common-lisp-hyperspec-root
"file:///usr/share/doc/hyperspec-7.0/HyperSpec/"))
(t (message "Default Common Lisp HyperSpec access")))
(define-key sly-prefix-map (kbd "M-h") #'sly-documentation-lookup)))
#!/bin/sh
sbcl <<EOF
(mapc 'require '(sb-bsd-sockets sb-posix sb-introspect sb-cltl2 asdf))
(save-lisp-and-die "sbcl.core-for-sly")
EOF
# Local Variables:
# mode: shell-script
# sh-indentation: 2
# sh-basic-offset: 2
# End:
CS 325 AI Programming
The CS 325 AI Programming course allows to learn Common Lisp by self-study and the page CS 325: Setting up Lisp gives instructions how to:
Quicklisp
Quicklisp is a library manager for Common Lisp. Listing lst:download+verify-quicklisp downloads the Quicklisp file and verifies its signature to prevent tampering. Listing lst:bootstrap-quicklisp tangles to a shell script allowing to bootstrap Quicklisp with SBCL. Listing lst:quicklisp-sbclrc-file tangles to the a SBCL resource file with Quicklisp support.
curl -sS -O https://beta.quicklisp.org/quicklisp.lisp
curl -sS -O https://beta.quicklisp.org/quicklisp.lisp.asc
curl -sS -O https://beta.quicklisp.org/release-key.txt
gpg --import release-key.txt
gpg --verify quicklisp.lisp.asc quicklisp.lisp
#!/bin/sh
sbcl --load ~/quicklisp.lisp <<EOF
(quicklisp-quickstart:install)
(quit)
EOF
# Local Variables:
# mode: shell-script
# sh-indentation: 2
# sh-basic-offset: 2
# End:
;;; Hey Emacs, this is my -*- lisp -*- .sbclrc file.
#-quicklisp
(let ((quicklisp-init (merge-pathnames "quicklisp/setup.lisp"
(user-homedir-pathname))))
(when (probe-file quicklisp-init)
(load quicklisp-init)))
Maxima Programming (info)
Listing lst:configure-maxima configures Maxima Mode. The following list contains more information:
- Maxima Mode is an Emacs interface to the computer algebra system Maxima.
- Maxima Source Code Blocks in Org Mode shows how to use Maxima in Org Mode.
- Maxima By Example is a tutorial for Maxima.
Listing lst:maxima-example is a minimal example of how to use how to use Maxima in Org Mode.
(when (ensure-package-installation 'maxima 'company-maxima)
(add-hook 'maxima-mode-hook #'maxima-hook-function)
(add-hook 'maxima-inferior-mode-hook #'maxima-hook-function)
(add-to-list 'auto-mode-alist
(cons "\\.mac\\'" 'maxima-mode))
(add-to-list 'interpreter-mode-alist
(cons "maxima" 'maxima-mode)))
print(1+1);
Python programming
The Python Programming in Emacs wiki page lists options to enhance Emacs's
built-in python-mode
. Here, the focus is on three packages:
- Eglot - Emacs polyGLOT: an Emacs LSP client that stays out of your way. The maintainer also contributes to Emacs itself and has a deep understanding of the Way of Emacs. He refuses to add new features without seeing how they fit into the Way of Emacs as this discussion on org-mode source code blocks shows.
- 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.
- Code-cells allows to edit IPython or Jupyter notebooks in Emacs with help from either Jupytext or Pandoc.
Here are links covering how to integrate Emacs, Python and a Python LSP server, before plunging into the configuration steps:
- Building Your Own Emacs IDE with LSP
- Doom Emacs and Language Servers
- Eglot based Emacs Python IDE
- Getting started with lsp-mode for Python
- Python & Emacs, Take 3
- Python with Emacs: py(v)env and lsp-mode
Python-mode
Listing lst:configure-python configures python-mode to use flake8 as style
checker and IPython as shell interpreter and selects the Python interpreter by
means of src_emacs-lisp{(choose-common-python-interpreter)} defined in listing
lst:choose-common-python-interpreter. The pythonic and pyvenv packages provide support
to handle Python virtual environments within Emacs. The pyenv package provides
support to work with pyenv (eventually with pyenv-virtualenv) to select between
different python versions (eventually each with different environments). In the
end, all those packages do is to set python-shell-virtualenv-root
(in case of
pyenv and pythonic) and tweak the environment variables and restart the relevant
Python child processes (in case of pyvenv). Therefore, this setup replaces
those packages with listing lst:manage-pyenv to manage pyenv from within Emacs
and listing lst:setting-python-shell-virtualenv-root to set
python-shell-virtualenv-root
.
(with-eval-after-load 'python
(custom-set-variables
`(python-check-command ,(executable-find "flake8"))
`(python-flymake-command '(,(executable-find "flake8") "-"))
'(python-indent-guess-indent-offset nil)
'(python-shell-completion-native-disabled-interpreters '("ipython3" "pypy")))
(choose-common-python-interpreter 'python))
(defun choose-common-python-interpreter (&optional program)
"Choose Python interpreter PROGRAM for `ob-python' and `python-mode'."
(interactive)
(let* ((prompt (format "Choose Python (%s): "
(bound-and-true-p python-shell-interpreter)))
(choices '(ipython python))
(choice (if (member program choices)
(symbol-name program)
(completing-read prompt choices nil t))))
(when (boundp 'org-babel-python-command)
(pcase choice
("python"
(custom-set-variables
`(org-babel-python-command
,(concat (or (executable-find "python3")
(executable-find "python"))
" -E"))))
("ipython"
(custom-set-variables
`(org-babel-python-command
,(concat (or (executable-find "ipython3")
(executable-find "ipython"))
" --simple-prompt --HistoryAccessor.enabled=False"))))))
(when (boundp 'python-shell-interpreter)
(pcase choice
("python"
(custom-set-variables
`(python-shell-interpreter
,(or (executable-find "python3") (executable-find "python")))
'(python-shell-interpreter-args "-E -i")))
("ipython"
(custom-set-variables
`(python-shell-interpreter
,(or (executable-find "ipython3") (executable-find "ipython")))
'(python-shell-interpreter-args
"--simple-prompt --HistoryAccessor.enabled=False")))))))
(when (executable-find "pyenv")
(defun pyenv-full-path (version)
"Return the full path for VERSION."
(unless (string= version "system")
(concat (pyenv-root) (file-name-as-directory "versions") version)))
(defun pyenv-root ()
"Return \"pyenv root\" as a directory."
(cl-destructuring-bind (exit-code output)
(shell-command-with-exit-code "pyenv" "root")
(if (= 0 exit-code)
(file-name-as-directory (string-trim output))
(error "%s" (string-trim output)))))
(defun pyenv-version-name ()
"Return \"pyenv version-name\"."
(cl-destructuring-bind (exit-code output)
(shell-command-with-exit-code "pyenv" "version-name")
(if (= 0 exit-code)
(string-trim output)
(error "%s" (string-trim output)))))
(defun pyenv-versions ()
"Return \"pyenv versions --bare --skip-aliases\" as a list.
Complete the result with \"system\"."
(cl-destructuring-bind (exit-code output)
(shell-command-with-exit-code
"pyenv" "versions" "--bare" "--skip-aliases")
(if (= 0 exit-code)
(cons "system" (split-string output))
(error "%s" (string-trim output)))))
(defun pyenv-virtualenvs ()
"Return \"pyenv virtualenvs --bare --skip-aliases\" as a list."
(cl-destructuring-bind (exit-code output)
(shell-command-with-exit-code
"pyenv" "virtualenvs" "--bare" "--skip-aliases")
(if (= 0 exit-code)
(split-string output)
(error "%s" (string-trim output))))))
(with-eval-after-load 'python
(when (cl-every #'fboundp '(pyenv-full-path
pyenv-version-name
pyenv-versions
pyenv-virtualenvs))
(setq python-shell-virtualenv-root
(pyenv-full-path (or (car `(,(pyenv-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)))))
Eglot for python-mode
Listing lst:configure-eglot+pylsp-with-pyls-flake8 configures eglot for Python using the python-lsp-server with the pyls-flake8 plugin for the easiest way to let python-lsp-server use flake8. In order to enable all builtin python-lsp-server capabilities, ensure installation of the Python packages autopep8, flake8, pydocstyle, pylint, python-lsp-server, rope, and yapf. The latest python-lsp-server documentation tells to configure it for flake8 as in listing lst:configure-eglot+pylsp-sans-pyls-flake8. This method is under investigation (it feels flaky since it shows some error messages twice). Listing lst:eglot-directory-variables-for-python shows a proper .dir-locals.el file in the root directory of any Python project to start eglot automatically according to the configuration in listing lst:eglot-maybe-ensure. Type
(eval
to dump aJSON
representation of src_emacs-lisp{eglot-workspace-configuration} for debugging.
The comment in listing lst:ensure-eglot-installation also shows how to increase
the the verbosity of python-lsp-server log output for debugging.
(with-eval-after-load 'eglot
(setq-default
eglot-workspace-configuration
;; Disable the `:pyls_flake8' plugin to fall back to pycodestyle.
'(:pylsp (:plugins
(:pyls_flake8
(:enabled t)
:jedi
(:auto_import_modules ["numpy"])
:jedi_completion
(:cache_for ["astropy"]))))))
(with-eval-after-load 'eglot
(setq-default
eglot-workspace-configuration
;; How to use flake8 instead of pycodestyle officially, see:
;; https://github.com/python-lsp/python-lsp-server#configuration
'(:pylsp (:configurationSources
["flake8"]
:plugins
(:pycodestyle
(:enabled :json-false)
:mccabe
(:enabled :json-false)
:pyflakes
(:enabled
:json-false)
:flake8
(:enabled t)
:jedi
(:auto_import_modules ["numpy"])
:jedi_completion
(:cache_for ["astropy"]))))))
;; Proposal for a .dir-locals.el file in the root of any Python project.
((python-mode
. ((eglot-workspace-configuration
;; Disable the `:pyls_flake8' plugin to fall back to pycodestyle.
. (:pylsp (:plugins
(:pyls_flake8
(:enabled t)
:jedi
(:auto_import_modules ["numpy"])
:jedi_completion
(:cache_for ["astropy"]))))))))
command | key map | keys |
xref-find-definition | global-map | (eval |
xref-pop | global-map | (eval |
flymake-goto-next-error | eglot-mode-map | (eval |
flymake-goto-prev-error | eglot-mode-map | (eval |
eglot-rename | eglot-mode-map | (eval |
eldoc-doc-buffer | eglot-mode-map | (eval |
Listing lst:pyproject-toml-kick-off and lst:setup-cfg-kick-off implement the rules in using black with other tools in order to make flake8 or pycodestyle agree with black's uncompromising style.
[build-system]
requires = ["setuptools", "wheel"]
build-backend = "setuptools.build_meta"
[tool.black]
line-length = 88
# Local Variables:
# mode: conf-toml
# End:
[flake8]
max-line-length = 88
extend-ignore = E203
[pycodestyle]
ignore = E203
max-line-length = 88
# Local Variables:
# mode: conf-toml
# End:
Jedi provides grammar checking and completion candidates to python-lsp-server. Only jedi-0.18.1 works with for instance numpy-1.23.3 in the sense that it does not choke on universal functions provided that jedi does not parse but import numpy-1.23.3 (see jedi issue #1744, #1745, and #1746). Since the universal functions are neither builtin methods nor data instances but a kind of "callable instances", the Python inspect module also fails to handle the universal functions properly.
Listing lst:make-pylsp-server-patch generates and listing lst:show-pylsp-server-patch shows the patch to make jedi import numpy-1.23.3 in order to obtain the necessary information to make python-lsp-server handle universal functions.
git -C $HOME/VCS/python-lsp-server diff >pylsp-auto-import-modules.patch
Jedi
Listing lst:example-py is a Python example to test whether jedi in combination with eglot works when coding for instance numpy universal functions.
import numpy
import astropy.units as apu
a = numpy.arange(0, 11)
a = numpy.linspace(0, 10, num=11)
a = numpy.arccos(a)
q = apu.Quantity(a, apu.meter)
print(q)
Code-cells
Code-cells allows to edit IPython or Jupyter notebooks in Emacs with help from either Jupytext or Pandoc to translate between the ipynb format and different plain text formats. Sofar, I have used code-cells to open ipynb notebooks with
(eval
in Emacs for viewing. Listing lst:configure-code-cells configurescode-cells
.
(when (ensure-package-installation 'code-cells)
(with-eval-after-load 'code-cells
(let ((map code-cells-mode-map))
(define-key map (kbd "M-p") #'code-cells-backward-cell)
(define-key map (kbd "M-n") #'code-cells-forward-cell)
(define-key map (kbd "C-c C-c") #'code-cells-eval))))
TODO Look into: editing facilities
Libraries
Library dash.el (info)
The library dash.el (info) positions itself as a modern alternative for the list
processing API of cl. It is a requirement of several important packages in this
Emacs setup (for instance citeproc, magit, and smartparens). Listing
lst:configure-dash enables fontification of dash
macros and makes
info-lookup-symbol
take into account the dash
macros.
(when (fboundp #'global-dash-fontify-mode)
(global-dash-fontify-mode))
(when (fboundp #'dash-register-info-lookup)
(with-eval-after-load 'info-look
(dash-register-info-lookup)))
Minor Modes (info)
Synchronal multiple-region editing
(when (ensure-package-installation 'iedit)
(require 'iedit nil 'noedit))
Unobtrusive whitespace trimming
(when (and (ensure-package-installation 'ws-butler)
(require 'ws-butler nil 'noerror))
(custom-set-variables
'(ws-butler-keep-whitespace-before-point nil))
(add-hook 'prog-mode-hook #'ws-butler-mode)
(add-hook 'text-mode-hook #'ws-butler-mode))
Structural editing
Structural editing keeps character pairs (for instance parentheses, curly and
square brackets as well as single and double quotes) balanced to leave code (for
instance Lisp and Python) and text (for instance LaTeX and Org) structure
intact. I use smartparens which offers a normal mode (smartparens-mode
) and a
strict mode (smartparens-strict-mode
). Although both modes insert character
pairs, the normal mode allows to delete one of the paired characters easily
while the strict mode does not. Therefore, the strict mode is more for code
editing since it never breaks programming language rules and the normal mode is
more for text editing where structure is a matter of convention instead of
programming language rules.
For instance, the strict mode in Python allows to delete entire lists, tuples,
or the arguments after the cursor (what Emacs calls point
) in a function call
without breaking the character pair balance. In order to repair a broken
character pair balance, insert a single character by prefixing it with "C-q"
bound to quoted-insert
.
The smartparens documentation targets experienced Emacs users. The following links show how to put the documentation to practical use:
- Omar Antolin's gist "my-smartparens-config.el" is the first place to look for how to tweak smartparens. However, the gist may be partially obsolete, since it is not part of his current Emacs configuration.
- How to enable smartparens in the minibuffer after eval-expression explains
how the machinery after the first and after later usages of
eval-expression
differ and discusses options how to handle those differences.
Listing lst:configure-smartparens aims to configure smartparens for Elisp, LaTeX, Org, and Python.
Despite the provocative post "What if structural editing was a mistake?", smartparens is one of my favorite packages. In particular, smartparens handles matching pairs in Org-mode files with export and source blocks for multiple formats and languages correctly. Therefore, show-smartparens-mode highlights matching pairs immediately in front or after point in such files correctly, contrary to for instance rainbow-delimiters.
(when (and (ensure-package-installation 'smartparens)
;; Require `smartparens-config' instead of `smartparens' to
;; disable pairing of the quote character for lisp modes.
(require 'smartparens-config nil 'noerror))
(custom-set-variables
'(sp-base-key-bindings 'sp)
'(sp-override-key-bindings '(("C-(" . sp-backward-barf-sexp)
("C-)" . sp-forward-slurp-sexp))))
(dolist (hook '(prog-mode-hook text-mode-hook))
(add-hook hook #'smartparens-mode))
(dolist (hook '(emacs-lisp-mode-hook
ielm-mode-hook
lisp-data-mode-hook
lisp-mode-hook
python-mode-hook
sly-mrepl-mode-hook))
(add-hook hook #'smartparens-strict-mode))
;; https://xenodium.com/emacs-smartparens-auto-indent/index.html
(defun indent-between-pair (&rest _ignored)
(newline)
(indent-according-to-mode)
(forward-line -1)
(indent-according-to-mode))
(dolist (left '("(" "[" "{"))
(sp-local-pair 'prog-mode left
nil :post-handlers '((indent-between-pair "RET"))))
(show-smartparens-global-mode +1))
Electric operators
Listing lst:configure-electric-operator configures electric-operator-mode
to add spaces around operators for compatibility with for instance the Black
code formatter for Python.
(when (and (ensure-package-installation 'electric-operator)
(fboundp 'electric-operator-mode))
(add-hook 'c-mode-common #'electric-operator-mode)
(add-hook 'python-mode-hook #'electric-operator-mode))
Smart snippets
(when (ensure-package-installation 'yasnippet)
(custom-set-variables
;; Set `yas-alias-to-yas/prefix-p' before loading `yasnippet'.
'(yas-alias-to-yas/prefix-p nil))
(when (require 'yasnippet nil 'noerror)
(yas-global-mode +1)))
Tempo (info)
The official tempo (info) documentation is very short and the following links
are obsolete or do not reveal the full power of tempo (info) after its
adaptation to lexical-binding
:
- Tempo (Emacs Wiki)
- David Kågedal’s elisp
- Emacs's Native Snippets, Emacs's native templating and snippet functionality
Listing lst:configure-tempo-ui provide 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 that is part of Emacs-28.1
and later. Listing lst:setup-python-tempo configures tempo
for python-mode
.
(with-eval-after-load 'tempo
(custom-set-variables
'(tempo-interactive t))
(defun setup-local-tempo-key-bindings ()
"Initialize local `tempo' key bindings."
(local-set-key (kbd "M-n") #'tempo-forward-mark)
(local-set-key (kbd "M-p") #'tempo-backward-mark))
(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)))))
(with-eval-after-load 'latex
(unless (featurep 'tempo)
(require 'tempo))
(defun tempo-latex-environment-handler (element)
(when-let ((ok (eq element 'latex-environment))
(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 ()
(setup-local-tempo-key-bindings)
(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))
(add-hook 'LaTeX-mode-hook 'setup-tempo-latex))
(with-eval-after-load 'python
(unless (featurep 'tempo)
(require 'tempo))
(defun setup-tempo-python ()
(defvar python-tempo-tags nil)
(tempo-define-template
"python-base-class"
'("class " p ":" n >)
"clsb" "Define a base class" 'python-tempo-tags)
(tempo-define-template
"python-derived-class"
'("class " p "(" p "):" n >)
"clsd" "Define a derived class" 'python-tempo-tags)
(tempo-define-template
"python-class-method"
'("@classmethod" > n > "def " p "(cls, " p "):" n >)
"defc" "Define a class method" 'python-tempo-tags)
(tempo-define-template
"python-function"
'("def " p "(" p "):" n >)
"deff" "Define a function" 'python-tempo-tags)
(tempo-define-template
"python-normal-method"
'("def " p "(self, " p "):" n >)
"defm" "Define a normal method" 'python-tempo-tags)
(tempo-define-template
"python-static-method"
'("@staticmethod" > n > "def " p "(" p "):" n >)
"defs" "Define a static method" 'python-tempo-tags)
(tempo-use-tag-list 'python-tempo-tags)
(add-tempo-tags-to-abbrev-table python-tempo-tags python-mode-abbrev-table)
(setup-local-tempo-key-bindings))
(add-hook 'python-mode-hook 'setup-tempo-python))
Display (info)
Narrowing
Narrowing means focusing in on some portion of the buffer and widening means
focussing out on the whole buffer. This allows to concentrate temporarily on
for instance a particular function or paragraph by removing clutter. The "Do
What I Mean" narrow-or-widen-dwim function allows to toggle between narrowed and
widened buffer states. Here, the function narrow-or-widen-dwim
operates also
on any Org table, Org source block, Org block, or Org subtree.
(with-eval-after-load 'emacs
(autoload 'org-at-table-p "org-table")
(defun org-narrow-to-table ()
"Narrow buffer to table at point."
(interactive)
(if (org-at-table-p)
(narrow-to-region (org-table-begin) (org-table-end))
(user-error "Not in a table")))
(defun narrow-or-widen-dwim (p)
"Widen if buffer is narrowed, narrow \"Do What I Mean\" otherwise.
DWIM means: region, Org table, Org source block, Org block, Org
subtree, LaTeX environment, TeX group, or defun, whichever
applies first. Narrowing to org-src-block actually calls
`org-edit-src-code'.
With prefix P, don't widen, just narrow even if buffer is already
narrowed."
(interactive "P")
(declare (interactive-only))
(cond ((and (buffer-narrowed-p) (not p))
(widen))
((and (bound-and-true-p org-src-mode) (not p))
(org-edit-src-exit))
((region-active-p)
(narrow-to-region (region-beginning) (region-end)))
((derived-mode-p 'org-mode)
(or (with-demoted-errors "DWIM: %S" (org-narrow-to-table) t)
(with-demoted-errors "DWIM: %S" (org-edit-src-code) t)
(with-demoted-errors "DWIM: %S" (org-narrow-to-block) t)
(org-narrow-to-subtree)))
((derived-mode-p 'latex-mode)
(LaTeX-narrow-to-environment))
((derived-mode-p 'tex-mode)
(TeX-narrow-to-group))
(t
(narrow-to-defun))))
(define-key ctl-x-map (kbd "n t") #'org-narrow-to-table)
(define-key ctl-x-map (kbd "C-n") #'narrow-or-widen-dwim))
Visualize color codes and names
Listing lst:enable-rainbow-mode enables rainbow-mode
to colorize color codes
and names in buffers for debugging.
(when (and (ensure-package-installation 'rainbow-mode)
(fboundp 'rainbow-mode))
(custom-set-variables
'(rainbow-x-colors-major-mode-list
'(c++-mode
c-mode
emacs-lisp-mode
inferior-emacs-lisp-mode
java-mode
lisp-interaction-mode
org-mode
python-mode)))
(rainbow-mode +1))
Flash the line around point for visual feedback
Listing lst:flash-line-around-point implements flashing of the line around point for visual feedback after a selection of commands that make it hard to track point movements visually.
;; Ensure loading `pulse' with the dynamic variables `pulse-delay' and
;; `pulse-iterations' before masking them lexically instead of after
;; to prevent the warning triggered by lazy loading.
(when (require 'pulse nil 'noerror)
;; https://karthinks.com/software/batteries-included-with-emacs/
;; https://github.com/karthink/.emacs.d/blob/master/init.el#L2077
(defun flash-line-around-point (&rest _)
"Flash the line around point."
(let ((pulse-iterations 16)
(pulse-delay 0.1))
(pulse-momentary-highlight-one-line (point))))
(dolist (command '(scroll-up-command
scroll-down-command
recenter-top-bottom
other-window))
(advice-add command :after #'flash-line-around-point)))
Applications
Reading News and Mail (info)
- How to use Emacs as a USENET reader with Gnus
- A practical guide to Gnus
- Fast reading and searching of Gmane.io with Gnus
- Setting up Leafnode on macOS
- Gnus
- Stupid GMail hacks for Gnus
- More stupid Gnus hacks
command | map | keys |
---|---|---|
gnus-group-list-active | gnus-group-list-map | (eval |
gnus-group-list-all-groups | gnus-group-mode-map | (eval |
gnus-group-toggle-subscription | gnus-group-mode-map | (eval |
(with-eval-after-load 'emacs
(custom-set-variables
'(gnus-select-method '(nntp "news.gmane.io"))
(with-eval-after-load 'gnus-start
(custom-set-variables
'(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
(custom-set-variables
'(gnus-thread-hide-subtree t)))))
Sending Mail (info)
- Email setup in Emacs with Mu4e on macOS
- Setting up multiple IMAP and SMTP accounts in Gnus
- Emacs on Macos Monterey
(custom-set-variables
'(user-full-name "Gerard Vermeulen")
'(user-mail-address "gerard.vermeulen@neel.cnrs.fr"))
(with-eval-after-load 'message
(custom-set-variables
'(message-sendmail-envelope-from 'header)))
(with-eval-after-load 'sendmail
(custom-set-variables
'(mail-specify-envelope-from t)
'(mail-envelope-from 'header)
'(send-mail-function #'sendmail-send-it)
`(sendmail-program ,(executable-find "msmtp"))))
# Set default values for all accounts.
defaults
auth on
tls on
logfile ~/.msmtp.log
# CNRS on Darwin
# security add-generic-password -s cnrs -a gerard.vermeulen@neel.cnrs.fr -w
account cnrs
host smtps.grenoble.cnrs.fr
port 465
tls_starttls off
from gerard.vermeulen@neel.cnrs.fr
user gerard.vermeulen@neel.cnrs.fr
passwordeval security find-generic-password -s cnrs -w
# https://notmuchmail.org/emacstips/#index11h2
# https://wiki.debian.org/msmtp
# https://tushartyagi.com/blog/configure-mu4e-and-msmtp/
# https://www.emacswiki.org/emacs/GnusMSMTP
# https://www.ying-ish.com/essay/emacs-notmuch-mbsync-msmtp-email/
# Local Variables:
# mode: conf-unix
# End:
# Set default values for all accounts.
defaults
auth on
tls on
tls_trust_file /etc/ssl/certs/ca-certificates.crt
logfile ~/.msmtp.log
# CNRS on Linux
# secret-tool store --label=msmtp host smtps.grenoble.cnrs.fr service smtp user gerard.vermeulen@neel.cnrs.fr
account cnrs
host smtps.grenoble.cnrs.fr
port 465
tls_starttls off
from gerard.vermeulen@neel.cnrs.fr
user gerard.vermeulen@neel.cnrs.fr
# https://notmuchmail.org/emacstips/#index11h2
# https://wiki.debian.org/msmtp
# https://tushartyagi.com/blog/configure-mu4e-and-msmtp/
# https://www.emacswiki.org/emacs/GnusMSMTP
# https://www.ying-ish.com/essay/emacs-notmuch-mbsync-msmtp-email/
# Local Variables:
# mode: conf-unix
# End:
Elfeed: Emacs web feed reader
Listing lst:configure-elfeed configures elfeed
and makes a minimal attempt to
enable emms
.
(when (and (ensure-package-installation 'elfeed)
(fboundp 'elfeed))
(global-set-key (kbd "C-x w") #'elfeed)
(with-eval-after-load 'elfeed
(custom-set-variables
'(elfeed-feeds
'(("http://www.howardism.org/index.xml" h-abrams)
("https://bitspook.in/archive/feed.xml" c-singh)
("https://emacshorrors.com/feed.atom" v-schneidermann)
("https://emacsninja.com/emacs.atom" v-schneidermann)
("https://feeds.feedburner.com/InterceptedWithJeremyScahill"
j-scahill)
("https://frame.work/fr/fr/blog.rss" framework)
("https://nullprogram.com/feed/" c-wellons)
("https://oremacs.com/atom.xml" o-krehel)
("https://planet.emacslife.com/atom.xml" planet-emacs)
("https://protesilaos.com/codelog.xml" p-stavrou)
("https://sachachua.com/blog/category/emacs/feed" s-chua)
("https://sciencescitoyennes.org/feed/" sciences)
("https://updates.orgmode.org/feed/updates" org-updates)
("https://www.bof.nl/rss/" bof)
("https://www.democracynow.org/podcast-video.xml" dn)
("https://www.laquadrature.net/fr/rss.xml" lqdn)
("https://www.lemonde.fr/blog/huet/feed/" sciences)))))
(with-eval-after-load 'elfeed-show
(when (fboundp 'emms-all)
(emms-all))))
Emacs Multimedia System (info)
The link my emms and elfeed setup is a nice introduction to configuring and
using emms
with elfeed
. Listing lst:configure-emms configures emms
.
(when (ensure-package-installation 'emms)
(with-eval-after-load 'emms
(custom-set-variables
'(emms-player-list '(emms-player-mpd emms-player-mpv)))
(emms-all))
(with-eval-after-load 'emms-mode-line
(custom-set-variables
'(emms-mode-line-format "")))
(with-eval-after-load 'emms-player-mpd
(custom-set-variables
`(emms-player-mpd-music-directory ,(expand-file-name "~/Music"))
'(emms-player-mpd-server-name "localhost")
'(emms-player-mpd-server-port "6600")
'(emms-player-mpd-verbose t)))
(with-eval-after-load 'emms-player-mpv
(custom-set-variables
'(emms-player-mpv-ipc-method 'ipc-server)
'(emms-player-mpv-update-metadata t)))
(with-eval-after-load 'emms-playing-time
(custom-set-variables
'(emms-playing-time-display-format " %s ")))
(with-eval-after-load 'emms-playlist-mode
(custom-set-variables
'(emms-playlist-mode-center-when-go t)))
(with-eval-after-load 'emms-streams
(custom-set-variables
`(emms-streams-file
,(no-littering-expand-etc-file-name "emms/streams.emms")))))
Hiding spurious buffers
When emms-player-mpd
opens a connection to mpd
and mpd
is not running,
mpd
responds with a message of the form "OK MPD 0.23.5". Neither
emms-player-mpd
nor its support library tq
anticipate the "OK MPD 0.23.5"
message, but send such messages to a new intrusive buffer called *spurious*
.
Listing lst:hiding-spurious-buffers hides those *spurious*
buffers, but you
can always switch to them. See:
- The Zen of Buffer Display (info)
- Configuring the Emacs display system
- Demystifying Emacs's Window Manager
for technical information.
(with-eval-after-load 'emms-player-mpd
;; https://www.masteringemacs.org/article/demystifying-emacs-window-manager
;; Hide `*spurious*' buffers, but you can always switch to them:
(add-to-list 'display-buffer-alist
'("\\*spurious\\*.*" display-buffer-no-window
(allow-no-window . t))))
Music Player Daemon configuration
Listing lst:make-mpd-conf proposes a Music Player Daemon configuration file that
keeps each new connection for a day in order to reduce the rate of *spurious*
generation.
case "$(uname -s)" in
Darwin)
cat <<EOF > ~/.mpd/mpd.conf
music_directory "~/Music"
playlist_directory "~/.mpd/playlists"
db_file "~/.mpd/mpd.db"
log_file "~/.mpd/mpd.log"
pid_file "~/.mpd/mpd.pid"
state_file "~/.mpd/mpdstate"
follow_outside_symlinks "yes"
follow_inside_symlinks "yes"
bind_to_address "localhost"
port "6600"
connection_timeout "86400"
audio_output {
type "osx"
name "CoreAudio"
mixer_type "software"
}
EOF
;;
esac
Local variables linking to Latexmk save-compile-display-loop
Only the Org source file shows the local variables footer.
\printbibliography