49 KiB
Emacs setup for use with LaTeX, Org, and Python
- Quick start
- Introduction
- Early Init File (info)
- Init File (info) header
- Package bootstrapping
- Emacs Server (info)
- Completion
- Reading
- Writing
- Editing
- Coding
- Appearance
- Applications
- Init File (info) footer
- Local variables linking back to LaTeX compile-display-loop
Quick start
Backup your ~/.emacs.d
directory to execute the following commands:
cd ~
git clone ccdr@mercury.grenoble.cnrs.fr:SERVER/.emacs.d
make --directory=.emacs.d init
After its invokation, Emacs will install a minimal set of packages. Now, you
have the option to install all optional packages using the command
my-install-optional-packages
, but you can do this any time, or you can install
any package using the command package-install
whenever you like. Quit Emacs
and invoke Emacs again.
Introduction
This Emacs setup aims to install automatically a minimal set of extension packages that allows to handle my reports and presentations. The file format of the reports is Org Mode plain text with Python source code blocks and the file format of the presentations is LaTeX.
This org file (more precisely the original org source file of this file) illustrates my work-flow by showing:
- 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.
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:
- Omar Antolín Camarena's configuration
- 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 an example of producing the Emacs initialization files by tangling an org file. It gives me the impression that she is a very practical person trying to achieve her goals by the most efficient means. I have stolen her idea of using quelpa to install packages from any source.
- Steve Purcell's configuration is well organized and a show-case of readable
Emacs lisp (info) code. I have stolen his idea of versioning the
package-user-dir
variable to prevent clashes between the byte-compiler output of different Emacs versions. - Timothy E. Chapman
Early Init File (info)
The err Package Bootstrapping
The Basic Char Syntax (info) page
;;; early-init.el --- user early-init file -*- lexical-binding: t -*-
;;; Commentary:
;;; Code:
(setq package-enable-at-startup nil)
(setq-default load-prefer-newer t)
(provide 'early-init)
;; Emacs looks for "Local variables:" after the last "?\n?\f".
;; Local Variables:
;; indent-tabs-mode: nil
;; End:
;;; earl-init.el ends here
Init File (info) header
The quoting (info) and the backquote (info) pages explain how to understand the
reader macros '
(quote), `
(backquote), ,
(substitute) and @,
(splice)
in the custom-set-variable
function call below.
;;; init.el --- user init file -*- lexical-binding: t -*-
;;; Commentary:
;;; Code:
(require 'cl-lib)
;; https://with-emacs.com/posts/tutorials/almost-all-you-need-to-know-about-variables/
(defmacro csetq (sym val)
`(funcall (or (get ',sym 'custom-set) 'set-default) ',sym ,val))
(custom-set-variables
'(after-save-hook #'executable-make-buffer-file-executable-if-script-p)
'(column-number-mode t)
'(cursor-type 'box)
`(custom-file
,(locate-user-emacs-file
(format "custom-%s.%s.el" emacs-major-version emacs-minor-version)))
'(epg-pinentry-mode 'loopback)
'(global-hl-line-mode t)
'(global-hl-line-sticky-flag t)
'(history-delete-duplicates t)
'(history-length 500)
'(indent-tabs-mode nil)
'(inhibit-startup-buffer-menu t)
'(inhibit-startup-screen t)
'(initial-buffer-choice t)
'(initial-scratch-message "")
`(insert-directory-program ,(or (executable-find "gls")
(executable-find "ls")))
'(kill-ring-max 300)
'(package-archive-priorities '(("gnu" . 1)
("nongnu" . 1)))
'(package-archives '(("gnu" . "https://elpa.gnu.org/packages/")
("nongnu" . "https://elpa.nongnu.org/nongnu/")
("melpa" . "https://melpa.org/packages/")))
`(package-user-dir
,(locate-user-emacs-file
(format "elpa-%s.%s" emacs-major-version emacs-minor-version)))
'(python-indent-guess-indent-offset nil)
'(recentf-mode t)
'(save-place-mode t)
'(savehist-additional-variables
'(eww-history
kill-ring
regexp-search-string
search-ring
search-string))
'(savehist-mode t)
'(savehist-save-minibuffer-history 1)
'(scroll-bar-mode nil)
'(tab-always-indent 'complete)
'(tab-width 8)
'(tool-bar-mode nil)
'(url-cookie-trusted-urls nil)
'(url-cookie-untrusted-urls '(".*"))
'(use-dialog-box nil)
'(use-short-answer t)
'(view-read-only t))
(when (eq system-type 'darwin)
(custom-set-variables
'(ns-alternate-modifier nil)
'(ns-command-modifier 'meta)
'(ns-right-command-modifier 'super)))
(when (eq window-system 'ns)
(add-to-list 'initial-frame-alist '(height . 51))
(add-to-list 'initial-frame-alist '(width . 180)))
Package bootstrapping
Emacs installs packages from archives on the internet. This setup uses three archives in two decreasing levels of priority:
- The GNU Emacs Lisp Package Archive or the NonGNU Emacs Lisp Package Archive.
- The MELPA - Milkypostman’s Emacs Lisp Package Archive.
Finally, the quelpa tool allows to fetch code from any source and build a package on your computer before installation. It allows to install a package from MELPA instead of GNU ELPA or NonGNU ELPA, breaking the priority order.
The output of the byte-compiler may change with each new Emacs release. Therefore, in order to prevent collisions between different Emacs versions, the package-user-directory has a suffix containing the major- and minor-version numbers of Emacs.
The order of the next 1nd, 2nd, and 3rd package-bootstrapping blocks matters because each of those blocks prepares Emacs for the next block.
If present, the package no-littering helps to keep ~/.emacs.d
clean.
The code assumes that the package system is in a virgin state in case the package no-littering is not present. Refreshing the contents of available packages at least once is a requirement in order to be able to install and load any packages, hence also no-littering.
You have to refresh the list of available packages yourself before updating the installed packages.
Finally, my-install-packages
ensures installation of all packages in a list of
packages.
;; The is the 1st package bootstrapping block.
(require 'package)
(package-initialize)
(unless (require 'no-littering nil 'noerror)
(package-refresh-contents)
(package-install 'no-littering)
(require 'no-littering))
(defun my-install-packages (packages)
"Ensure installation of all packages in PACKAGES."
(dolist (package packages)
(unless (package-installed-p package)
(package-install package))))
Install the basic packages and in case this is Emacs-27.2, upgrade Org Mode for
compatibility with Emacs-28.1. The info:backquote page explains how to read the
`
and ,@
in the definition of my-packages
variable below.
;; The is the 2nd package bootstrapping block.
(defvar my-packages
`(
,@(when (version< emacs-version "28.0")
'(org)) ; plain text thought organizer
auctex ; Aalborg University Center TeX
blacken ; Black Python-code formatter client
citar ; bibliography handling
citeproc ; bibliography handling
consult ; consult completing-read
;; eglot ; Emacs polyGLOT LSP client
elpy ; Python development environment
embark ; act on any buffer selection
htmlize ; convert buffer contents to HTML
leuven-theme ; beautiful color theme
;; lsp-mode ; Language Server Protocol client
marginalia ; minibuffer margin notes
orderless ; Emacs completion style
pdf-tools ; interactive docview replacement
pyenv-mode ; Python environment selector
quelpa ; install Emacs packages from source
vertico) ; VERTical Interactive Completion
"List of packages required packages.")
(my-install-packages my-packages)
Install the following packages with quelpa:
lisp-ui
(includinglisp-mode
), becauselisp-ui
is yet available from GNU ELPA, NonGNU ELPA, or MELPA.eglot
to get the latest version from MELPA.
;; This is the 3rd package bootstrapping block.
(when (package-installed-p 'quelpa)
(defun my-install-sources (sources)
(mapc (lambda (source)
(unless (package-installed-p (car source))
(quelpa source)))
sources))
(my-install-sources
'((lsp-ui :fetcher github :repo "emacs-lsp/lsp-ui")
(eglot :fetcher github :repo "joaotavora/eglot"))))
Install the optional packages.
;; The is the 4th package bootstrapping block.
(defvar my-optional-packages
`(
,@(when (version< emacs-version "28.0")
'(modus-themes)) ; high foreground/background contrast themes
async ; asynchroneous processing
company ; complete anything
electric-operator ; automatic spacing around operators
elfeed ; web feed reader
emms ; Emacs Multi-Media System
iedit ; simultaneous multi-entity editing
laas ; LaTeX Auto-Activating Snippets
magit ; Git Text-based User Interface
nov ; EPUB reader
smartparens ; smart editing of character pairs
wgrep ; open a writable grep buffer
which-key ; on the fly key-binding help
wordnut ; WordNet lexical database
writegood-mode ; bullshit and weasel-word detector
ws-butler ; remove trailing whitespace
xr ; undo rx to grok regular expressions
yasnippet)) ; code or text template expansion
(defun my-install-optional-packages ()
(interactive)
(my-install-packages my-optional-packages))
Emacs Server (info)
Emacs can act as a server that listens to a socket to share its state (for
instance buffers and command history) with other programs by means of a shell
command emacsclient
.
(when window-system
(unless (or noninteractive (daemonp))
(add-hook 'after-init-hook #'server-start)))
LaTeX compile-display-loop
The latexmk
resource file in the next source code block shows how to use
emacsclient
to (re)display the PDF file in Emacs after each succesful
(re)compilation on condition that the settings of the compile-command
local
variable in section are compatible. The local variable compile-command
in the
Local Variables section (only visible in org
files, but not in html
and
pdf
files) shows how to use the latexmkrc
file..
# pdf previewer and update pdf previewer
$pdf_previewer = "emacsclient -e '(find-file-other-window %S)'";
$pdf_update_method = 4; # 4 runs a command to force the update
$pdf_update_command = "emacsclient -e '(with-current-buffer (find-buffer-visiting %S) (pdf-view-revert-buffer nil t))'";
# see for instance glossary.latexmkrc
add_cus_dep( 'acn', 'acr', 0, 'makeglossaries' );
add_cus_dep( 'glo', 'gls', 0, 'makeglossaries' );
$clean_ext .= " acr acn alg bbl glo gls glg ist run.xml";
sub makeglossaries {
my ($name, $path) = fileparse( $$Psource );
return system "makeglossaries -d '$path' '$name'";
}
# Emacs looks for "Local variables:" after the last "?\n?\f".
# Local Variables:
# mode: perl
# End:
Qutebrowser userscript
The next block contains an userscript that sends a store-link org-protocol
message with the url and the title from qutebrowser to emacsclient
. The
function urlencode
translates the url and the title for the message. The
Python urllib examples show how to use urlencode
. The final execvp
call
deals with a qutebrowser userscript requirement: the emacsclient
process must
get the PID of the userscript that must kill itself after the take-over.
Termination of the emacsclient
process hands control back to qutebrowser.
On a POSIX system, you can run the userscript from qutebrowser or from a terminal to see whether it works. In case you try to run it from Emacs, Emacs may hang or die.
#!/usr/bin/env python
from urllib.parse import urlencode
from os import environ, execvp
url = environ.get("QUTE_URL", "https://orgmode.org")
title = environ.get("QUTE_TITLE", "Org Mode")
parameters = urlencode({"url": url, "title": title})
print(payload := f"org-protocol://store-link?{parameters}")
execvp("emacsclient", ("-n", payload))
Completion
Vertico (info) provides a performant and minimalistic vertical completion UI based on the default completion system and behaves therefore correctly under all circumstances. Vertico integrates well with fully supported complementary packages to enrich the completion UI:
- Marginalia (info) for rich annotations in the minibuffer.
- Consult (info) for useful search and navigation commands.
- Embark (info) for minibuffer actions with context menus.
- Orderless (info) for an advanced completion style.
Using Vertico, Marginalia, Consult, and Embark
(unless noninteractive
(when (fboundp 'vertico-mode)
(vertico-mode +1)))
Marginalia (info) adds marginalia (margin annotations) to minibuffer completions.
(unless noninteractive
(when (fboundp 'marginalia-mode)
(marginalia-mode +1)))
Consult provides practical commands based on the Emacs completion function.
(unless noninteractive
(when (fboundp 'consult-apropos)
(custom-set-variables
'(consult-project-root-function #'vc-root-dir))
;; C-c bindings (mode-specific-map)
(global-set-key (kbd "C-c h") #'consult-history)
(global-set-key (kbd "C-c m") #'consult-mode-command)
;; C-x bindings (ctl-x-map)
(global-set-key (kbd "C-x M-:") #'consult-complex-command)
(global-set-key (kbd "C-x b") #'consult-buffer)
(global-set-key (kbd "C-x 4 b") #'consult-buffer-other-window)
(global-set-key (kbd "C-x 5 b") #'consult-buffer-other-frame)
(global-set-key (kbd "C-x r x") #'consult-register)
(global-set-key (kbd "C-x r b") #'consult-bookmark)
;; M-g bindings (goto-map)
(global-set-key (kbd "M-g g") #'consult-goto-line)
(global-set-key (kbd "M-g M-g") #'consult-goto-line)
(global-set-key (kbd "M-g o") #'consult-outline)
(global-set-key (kbd "M-g m") #'consult-mark)
(global-set-key (kbd "M-g k") #'consult-global-mark)
(global-set-key (kbd "M-g i") #'consult-imenu-project)
(global-set-key (kbd "M-g e") #'consult-error)
;; M-s bindings (search-map)
(global-set-key (kbd "M-s g") #'consult-git-grep)
(global-set-key (kbd "M-s f") #'consult-find)
(global-set-key (kbd "M-s k") #'consult-keep-lines)
(global-set-key (kbd "M-s l") #'consult-line)
(global-set-key (kbd "M-s m") #'consult-multi-occur)
(global-set-key (kbd "M-s u") #'consult-focus-lines)
;; Other bindings
(global-set-key (kbd "M-y") #'consult-yank-pop)
(global-set-key (kbd "<help> a") #'consult-apropos)
;; Tweak functions
(advice-add 'completing-read-multiple
:override #'consult-completing-read-multiple)
(fset 'multi-occur #'consult-multi-occur)))
(unless noninteractive
(when (cl-every #'fboundp '(embark-act embark-bindings embark-dwim))
(global-set-key (kbd "C-,") #'embark-act)
(global-set-key (kbd "C-:") #'embark-dwim)
(global-set-key (kbd "C-h B") #'embark-bindings)))
(unless noninteractive
(when (fboundp 'orderless-filter)
(custom-set-variables
;; https://github.com/purcell/emacs.d/issues/778
'(completion-styles '(basic completion-partial orderless))
'(completion-category-defaults nil)
'(completion-category-overrides
'((file (styles partial-completion)))))
(add-hook 'minibuffer-setup-hook
(defun my-on-minibuffer-setup-hook()
(setq-default completion-styles '(substring orderless))))))
company-mode: modular in-buffer completion framework for Emacs
(unless noninteractive
(when (fboundp 'company-mode)
(custom-set-variables
;; https://github.com/purcell/emacs.d/issues/778
'(company-transformers '(company-sort-by-occurrence)))
(dolist (hook '(LaTeX-mode-hook
org-mode-hook
emacs-lisp-mode-hook
lisp-interaction-mode-hook
python-mode-hook
ielm-mode-hook))
(add-hook hook #'company-mode))))
Reading
EPUB files
(when (fboundp 'nov-mode)
(add-to-list 'auto-mode-alist `(,(rx ".epub" eos) . nov-mode)))
PDF files
The pdf-tools package exploits the poppler library to render and to let you annotate PDF files. It also exploits the SyncTeX library to link anchors in PDF files produced with LaTeX to the original LaTeX sources.
In order to use pdf-tools, you have to type M-x pdf-tools-install
after
installation of pdf-tools from MELPA or after each update of poppler to build
or rebuild the epdfinfo
executable that serves the PDF files to Emacs.
(when (fboundp 'pdf-tools-install)
(autoload 'pdf-view-mode "pdf-view")
(add-to-list 'magic-mode-alist '("%PDF" . pdf-view-mode)))
Writing
LaTeX
Loading tex.el
immediately instead of lazily ensures proper initialization of
the AUCTeX. For instance, the TeX-master
safe local variable in the tex.el
elisp library file has no autoload cookie. Without prior loading of tex.el
,
Emacs will complain that TeX-master
is no safe local variable in case it
reads a LaTeX file that sets TeX-master
.
Out of the box, AUCTeX does not indent text between square brackets. The code
below corrects this by advising to override TeX-brace-count-line
with
my-TeX-brace-count-line
.
;; Try to get the `safe-local-variable` predicate for `TeX-master`
(when (require 'tex nil 'noerror)
;; https://emacs.stackexchange.com/questions/17396/indentation-in-square-brackets
(defun my-TeX-brace-count-line ()
"Count number of open/closed braces."
(save-excursion
(let ((count 0) (limit (line-end-position)) char)
(while (progn
(skip-chars-forward "^{}[]\\\\" limit)
(when (and (< (point) limit) (not (TeX-in-comment)))
(setq char (char-after))
(forward-char)
(cond ((eq char ?\{)
(setq count (+ count TeX-brace-indent-level)))
((eq char ?\})
(setq count (- count TeX-brace-indent-level)))
((eq char ?\[)
(setq count (+ count TeX-brace-indent-level)))
((eq char ?\])
(setq count (- count TeX-brace-indent-level)))
((eq char ?\\)
(when (< (point) limit)
(forward-char) t))))))
count)))
(advice-add 'TeX-brace-count-line :override #'my-TeX-brace-count-line))
Org-mode
Activation (info)
;; Inspect:
;; function with "C-h f"
;; symbols with "C-h o"
;; variables with "C-h v"
(global-set-key (kbd "C-c a") #'org-agenda)
(global-set-key (kbd "C-c c") #'org-capture)
(global-set-key (kbd "C-c l") #'org-store-link)
(global-set-key (kbd "C-c C-l") #'org-insert-link-global)
Customization
(custom-set-variables
'(org-babel-python-command "python -E")
'(org-babel-load-languages '((C . t)
(calc . t)
(dot . t)
(emacs-lisp . t)
(eshell . t)
(fortran . t)
(gnuplot . t)
(latex . t)
(lisp . t)
(maxima . t)
(org . t)
(perl . t)
(python . t)
(scheme . t)
(shell . t)))
'(org-cite-export-processors '((latex biblatex)
(t csl)))
'(org-cite-global-bibliography '("~/VCS/research/refs.bib"))
'(org-file-apps '((auto-mode . emacs)
(directory . emacs)
("\\.mm\\'" . default)
("\\.x?html?\\'" . default)
("\\.pdf\\'" . emacs)))
'(org-confirm-babel-evaluate nil)
'(org-latex-compiler "lualatex")
'(org-latex-hyperref-template nil)
'(org-latex-listings 'minted)
'(org-latex-logfiles-extensions '("blg" "lof" "log" "lot" "out" "toc"))
'(org-latex-prefer-user-labels t)
'(org-modules '(ol-bibtex
ol-doi
ol-eww
ol-info
org-id
org-protocol
org-tempo))
'(org-src-fontify-natively t)
'(org-src-window-setup 'current-window)
'(org-structure-template-alist
'(("a" . "export ascii")
("c" . "center")
("C" . "comment")
("e" . "example")
("E" . "export")
("h" . "export html")
("l" . "export latex")
("q" . "quote")
("s" . "src")
("p" . "src python :session :async")
("v" . "verse"))))
Citation export processors (info)
(with-eval-after-load 'oc
(require 'oc-biblatex)
(require 'oc-csl))
Translate capital keywords (old) to lower case (new)
(with-eval-after-load 'org
(defun org-syntax-convert-keyword-case-to-lower ()
"Convert all #+KEYWORDS to #+keywords."
(interactive)
(save-excursion
(goto-char (point-min))
(let ((count 0)
(case-fold-search nil))
(while (re-search-forward "^[ \t]*#\\+[A-Z_]+" nil t)
(unless (s-matches-p "RESULTS" (match-string 0))
(replace-match (downcase (match-string 0)) t)
(setq count (1+ count))))
(message "Replaced %d keywords" count)))))
Advanced Export Configuration (info)
Stolen from ox-extra.el
(with-eval-after-load 'ox
(defun my-org-export-ignore-headline-filter (_)
"Ignore all headlines with the \":ignore:\" tag."
(org-map-entries
(lambda ()
(delete-region (point) (line-beginning-position 2)))
":ignore:"))
(defun my-org-latex-header-blocks-filter (backend)
"Convert marked LaTeX export blocks to \"#+latex_header: \" lines.
The marker is a line \"#+header: :header yes\" preceding the block.
For instance, the LaTeX export block
,#+header: :header yes
,#+begin_export latex
% This line converts to a LaTeX header line.
,#+end_export
converts to
\"#+latex_header: % This line converts to a LaTeX header line.\"."
(when (org-export-derived-backend-p backend 'latex)
(let ((blocks
(org-element-map
(org-element-parse-buffer 'greater-element nil) 'export-block
(lambda (block)
(let ((type (org-element-property :type block))
(header (org-export-read-attribute :header block :header)))
(when (and (string= type "LATEX") (string= header "yes"))
block))))))
;; Set point to where to insert LaTeX header lines after
;; deleting the block.
(mapc (lambda (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 numeric
;; positions earlier in the file.
(reverse blocks)))))
(defun my-activate-buffer-local-org-export-filters ()
"Activate my export filters locally in the current buffer."
(add-hook 'org-export-before-parsing-hook
#'my-org-export-ignore-headline-filter nil 'local)
(add-hook 'org-export-before-parsing-hook
#'my-org-latex-header-blocks-filter nil 'local)))
(with-eval-after-load 'ox
(my-activate-buffer-local-org-export-filters))
(with-eval-after-load 'ox-latex
(mapc (lambda (item)
(add-to-list 'org-latex-classes item))
'(;; The postfixes +1, +2, +3, -1, -2, and -3 denote:
;; +1 => [DEFAULT-PACKAGES]
;; +2 => [PACKAGES]
;; +3 => [EXTRA]
;; -1 => [NO-DEFAULT-PACKAGES]
;; -2 => [NO-PACKAGES]
;; -3 => [NO-EXTRA]
("elsarticle-1+2+3" ; Elsevier journals
"\\documentclass{elsarticle}
[NO-DEFAULT-PACKAGES]
[PACKAGES]
[EXTRA]"
("\\section{%s}" . "\\section*{%s}")
("\\subsection{%s}" . "\\subsection*{%s}")
("\\subsubsection{%s}" . "\\subsubsection*{%s}")
("\\paragraph{%s}" . "\\paragraph*{%s}")
("\\subparagraph{%s}" . "\\subparagraph*{%s}"))
("article-1+2+3"
"\\documentclass{article}
[NO-DEFAULT-PACKAGES]
[PACKAGES]
[EXTRA]"
("\\section{%s}" . "\\section*{%s}")
("\\subsection{%s}" . "\\subsection*{%s}")
("\\subsubsection{%s}" . "\\subsubsection*{%s}")
("\\paragraph{%s}" . "\\paragraph*{%s}")
("\\subparagraph{%s}" . "\\subparagraph*{%s}"))
("report-1+2+3"
"\\documentclass[11pt]{report}
[NO-DEFAULT-PACKAGES]
[PACKAGES]
[EXTRA]"
("\\part{%s}" . "\\part*{%s}")
("\\chapter{%s}" . "\\chapter*{%s}")
("\\section{%s}" . "\\section*{%s}")
("\\subsection{%s}" . "\\subsection*{%s}")
("\\subsubsection{%s}" . "\\subsubsection*{%s}"))
("book-1+2+3"
"\\documentclass[11pt]{book}
[NO-DEFAULT-PACKAGES]
[PACKAGES]
[EXTRA]"
("\\part{%s}" . "\\part*{%s}")
("\\chapter{%s}" . "\\chapter*{%s}")
("\\section{%s}" . "\\section*{%s}")
("\\subsection{%s}" . "\\subsection*{%s}")
("\\subsubsection{%s}" . "\\subsubsection*{%s}")))))
Evaluate source blocks on loading
(defun my-org-eval-blocks-named (name)
"Evaluate all source blocks named NAME."
(when (eq major-mode 'org-mode)
(let ((blocks
(org-element-map
(org-element-parse-buffer 'greater-element nil) 'src-block
(lambda (block)
(when (string= name (org-element-property :name block))
block)))))
(dolist (block blocks)
(goto-char (org-element-property :begin block))
(org-babel-execute-src-block)))))
;; Emacs looks for "Local variables:" after the last "?\n?\f".
(add-to-list 'safe-local-eval-forms
'(apply 'my-org-eval-blocks-named '("emacs-lisp-setup")))
(add-to-list 'safe-local-eval-forms
'(apply 'my-org-eval-blocks-named '("python-setup")))
Citing bibliography
Citar provides a completing-read front-end to browse and act on BibTeX, BibLaTeX, and CSL JSON bibliographic data, and LaTeX, markdown, and org-cite editing support.
Citar – in combination with vertico, embark, and marginalia – provides quick filtering and selecting of bibliographic entries from the minibuffer, and the option to run different commands on those selections.
(when (cl-every #'fboundp '(citar-insert-citation
citar-insert-preset
citar-org-activate
citar-org-follow
citar-org-insert))
(custom-set-variables
'(org-cite-activate-processor 'citar)
'(org-cite-follow-processor 'citar)
'(org-cite-insert-processor 'citar))
(global-set-key (kbd "C-c b") #'citar-insert-citation)
(define-key minibuffer-local-map (kbd "M-b") #'citar-insert-preset))
Editing
Synchronal multiple-region editing
(unless noninteractive
(require 'iedit nil 'noerror))
Extraneous whitespace trimming
(unless noninteractive
(when (require 'ws-butler nil 'noerror)
(custom-set-variables
'(ws-butler-keep-whitespace-before-point nil))
(add-hook 'prog-mode-hook #'ws-butler-mode)
(add-hook 'text-mode-hook #'ws-butler-mode)))
Smart character-pair handling
(unless noninteractive
(when (require 'smartparens-config nil 'noerror)
;; Requiring smartparens-config disables pairing of the quote
;; character for lisp modes, contrary to requiring smartparens.
(custom-set-variables
'(sp-base-key-bindings 'sp)
'(sp-override-key-bindings '(("C-(" . sp-backward-barf-sexp)
("C-)" . sp-forward-slurp-sexp))))
(add-hook 'LaTeX-mode-hook #'turn-off-smartparens-mode)
(add-hook 'prog-mode-hook #'turn-on-smartparens-mode)
(add-hook 'text-mode-hook #'turn-on-smartparens-mode)
(add-hook 'emacs-lisp-mode-hook #'turn-on-smartparens-strict-mode)
(add-hook 'ielm-mode-hook #'turn-on-smartparens-strict-mode)
(add-hook 'python-mode-hook #'turn-on-smartparens-strict-mode)
;; https://xenodium.com/emacs-smartparens-auto-indent/index.html
(defun indent-between-pair (&rest _ignored)
(newline)
(indent-according-to-mode)
(forward-line -1)
(indent-according-to-mode))
(sp-local-pair 'prog-mode "(" nil :post-handlers '((indent-between-pair "RET")))
(sp-local-pair 'prog-mode "[" nil :post-handlers '((indent-between-pair "RET")))
(sp-local-pair 'prog-mode "{" nil :post-handlers '((indent-between-pair "RET")))
(show-smartparens-global-mode +1)))
Operators
(when (fboundp 'electric-operator-mode)
(add-hook 'c-mode-common #'electric-operator-mode)
(add-hook 'python-mode-hook #'electric-operator-mode))
Smart snippets
(when (require 'yasnippet nil 'noerror)
(custom-set-variables
'(yas-alias-to-yas/prefix-p nil))
(yas-global-mode +1))
Coding
(with-eval-after-load 'eglot
(add-to-list 'eglot-server-programs '(python-mode "pylsp")))
Python coding
Here, the focus is on three ways to extend Emacs's built-in python-mode
out of
the options listed on the Python Programming in Emacs wiki page:
-
Elpy is an opinionated Python integrated development environment with its own Python server and its own server protocol. Its main disadvantages are:
- It requires a high number of Elisp and Python packages.
- It imposes its way making it hard to do otherwise for beginners.
- It requires a new maintainer, because the current maintainer has no time anymore for adding new features or squashing hard bugs.
Its main advantages are:
- Its usability out of the box.
- Its documentation quality.
- Its compatibility with the Org source block editing mode.
- LSP Mode - Language Server Protocol support for Emacs with its LSP-UI user interface extensions. According to Literate programming using LSP and org-mode(alpha), this package tries to be compatible with the Org source block editing mode, but it is not ready for daily usage.
- 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 at Eglot github issue: org-mode source code blocks shows.
Does org have any "inverse-tangle" operations e.g. for collaborating with non-org users?
import numpy
import astropy.units as apu
a = numpy.linspace(0, 10, num=11)
q = apu.Quantity(a, apu.meter)
print(q)
(custom-set-variables
'(python-shell-interpreter-args "-i -E"))
(when (and (executable-find "pyenv")
(require 'pyenv-mode nil 'noerror))
(pyenv-mode +1)
(pyenv-mode-set "3.9.8/envs/python-3.9.8"))
(when (fboundp 'elpy-enable)
(custom-set-variables
'(elpy-modules '(elpy-module-sane-defaults
elpy-module-company
elpy-module-eldoc
elpy-module-pyvenv))
'(elpy-project-root-finder-functions '(elpy-project-find-python-root
elpy-project-find-git-root
elpy-project-find-hg-root))
`(elpy-rpc-ignored-buffer-size ,(lsh 1 18))
'(elpy-rpc-virtualenv-path 'current)))
(with-eval-after-load 'info
(add-to-list 'Info-directory-list
(expand-file-name "~/.local/share/info")))
Look into:
Appearance
(unless noninteractive
;; Set face attributes.
(cond
((eq system-type 'darwin)
(set-face-attribute 'default nil :family "Hack" :height 120)
(set-face-attribute 'fixed-pitch nil :family "Hack")
(set-face-attribute 'variable-pitch nil :family "FiraGo"))
((eq system-type 'gnu/linux)
(set-face-attribute 'default nil :family "Hack" :height 110)
(set-face-attribute 'fixed-pitch nil :family "Hack")
(set-face-attribute 'variable-pitch nil :family "FiraGo"))
(t
(set-face-attribute 'default nil :family "Hack" :height 110)
(set-face-attribute 'fixed-pitch nil :family "Hack")
(set-face-attribute 'variable-pitch nil :family "DejaVu Sans"))))
This setup prefers the leuven
and leuven-dark
themes from MELPA, because the
very popular modus-operandi
and modus-vivendi
themes feel quirky: for
instance those themes fail to display hl-line-mode
with Emacs-27.2 on Darwin.
(unless noninteractive
;; Try to detect `leuven-theme` from MELPA.
(when (fboundp 'leuven-scale-font)
(custom-set-variables
'(leuven-scale-org-agenda-structure nil)
'(leuven-scale-org-document-title nil)
'(leuven-scale-outline-headlines nil)
'(leuven-scale-volatile-highlight nil)))
(load-theme 'leuven 'no-confirm nil))
(unless noninteractive
(custom-set-variables
'(modus-themes-hl-line 'underline)
'(modus-themes-intense-markup 't))
(when (and (version< emacs-version "28.0")
(require 'modus-themes nil 'noerror))
(modus-themes-load-themes)))
(unless noninteractive
;; https://karthinks.com/software/batteries-included-with-emacs/
;; https://www.reddit.com/r/emacs/comments/jwhr6g/batteries_included_with_emacs/
(defun my-pulse-one-line (&rest _)
"Pulse the current line."
(let ((pulse-iterations 16)
(pulse-delay 0.1))
(pulse-momentary-highlight-one-line (point))))
(dolist (command '(scroll-up-command
scroll-down-command
recenter-top-bottom
other-window))
(advice-add command :after #'my-pulse-one-line)))
Applications
Feed reader
(autoload 'elfeed "elfeed" nil t)
(global-set-key (kbd "C-x w") #'elfeed)
(with-eval-after-load 'elfeed
(custom-set-variables
'(elfeed-feeds
'(("http://www.howardism.org/index.xml" h-abrams)
("https://ambrevar.xyz/atom.xml" p-neirhardt)
("https://emacshorrors.com/feed.atom" v-schneidermann)
("https://emacsninja.com/emacs.atom" v-schneidermann)
("https://feeds.feedburner.com/InterceptedWithJeremyScahill" j-scahill)
("https://nullprogram.com/feed/" c-wellons)
("https://oremacs.com/atom.xml" o-krehel)
("https://planet.emacslife.com/atom.xml" planet-emacs)
("https://protesilaos.com/codelog.xml" p-stavrou)
("https://sachachua.com/blog/category/emacs/feed" s-chua)
("https://sciencescitoyennes.org/feed/" sciences)
("https://updates.orgmode.org/feed/updates" org-updates)
("https://www.aclu.org/taxonomy/feed-term/2152/feed" aclu)
("https://www.bof.nl/rss/" bof)
("https://www.democracynow.org/podcast-video.xml" dn)
("https://www.laquadrature.net/fr/rss.xml" lqdn)
("https://www.lemonde.fr/blog/huet/feed/" sciences)))))
Multi-media system
(custom-set-variables
'(emms-mode-line-format "")
'(emms-player-list '(emms-player-mpd emms-player-mpv))
`(emms-player-mpd-music-directory ,(expand-file-name "~/Music"))
'(emms-player-mpd-server-name "localhost")
'(emms-player-mpd-server-port "6600")
'(emms-player-mpd-verbose t)
'(emms-playing-time-display-format " %s ")
'(emms-playlist-mode-center-when-go t))
(defun my-emms-print-metadata-find ()
(require 'find-func)
(locate-file
"emms-print-metadata"
(expand-file-name
"src"
(file-name-directory (find-library-name "emms")))
exec-suffixes #'file-executable-p))
(with-eval-after-load 'emms
(require 'emms-info-libtag)
(let ((emms-print-metadata (my-emms-print-metadata-find)))
(when emms-print-metadata
(custom-set-variables
'(emms-info-functions nil)
`(emms-info-libtag-program-name ,emms-print-metadata))
(add-hook 'emms-info-functions #'emms-info-libtag))))
(with-eval-after-load 'elfeed-show
(when (require 'emms-setup nil 'noerror)
(emms-all)))
(autoload 'emms-streams "emms-streams" nil 'interactive)
(with-eval-after-load 'emms-streams (emms-all))
Local variables linking back to LaTeX compile-display-loop
Only the Org source file shows the local variables footer.