5760 lines
245 KiB
Org Mode
5760 lines
245 KiB
Org Mode
#+title: Emacs setup for use with LaTeX, Org, and Python
|
||
#+author: Gerard Vermeulen
|
||
#+latex_class: article-local
|
||
#+latex_class_options: [11pt,a4paper,english,svgnames]
|
||
#+macro: kbd (eval (by-backend-kbd-org-macro $1))
|
||
#+options: ^:{}
|
||
#+property: header-args:emacs-lisp :exports code :results silent :tangle init.el
|
||
#+startup: showeverything
|
||
#+begin_src latex :noweb yes :results raw
|
||
,#+latex_header: <<latex-header-1>>
|
||
,#+latex_header: <<latex-header-2>>
|
||
,#+latex_header: <<latex-header-3>>
|
||
,#+latex_header: <<latex-header-4>>
|
||
,#+latex_header: <<latex-header-5>>
|
||
#+end_src
|
||
|
||
* Copying
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: sec:copying
|
||
:END:
|
||
|
||
This README contains my Emacs setup for use with LaTeX, Org, and Python.
|
||
|
||
Copyright \copy 2021-2023 Gerard Vermeulen.
|
||
|
||
#+BEGIN_QUOTE
|
||
Permission is granted to copy, distribute and/or modify this
|
||
document under the terms of the GNU Free Documentation License,
|
||
Version 1.3 or any later version published by the Free Software
|
||
Foundation; with no Invariant Sections, with no Front-Cover Texts,
|
||
and with no Back-Cover Texts. A copy of the license is included in
|
||
the section entitled "GNU Free Documentation License".
|
||
#+END_QUOTE
|
||
|
||
* Quick start
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: sec:quick-start
|
||
:END:
|
||
|
||
This setup requires at least Emacs-29.0.60. Backup the =user-emacs-directory=
|
||
(defaults often to =~/.emacs.d= on =Linux=, =Unix=, or =Darwin=) and execute the
|
||
commands in listing [[lst:prepare-user-emacs-directory-with-https][prepare the =user-emacs-directory= without =ssh= access]] or
|
||
in listing [[lst:prepare-user-emacs-directory-with-ssh][prepare the =user-emacs-directory= with =ssh= access]]. 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.
|
||
|
||
# #+caption[Prepare the user-emacs-directory]:
|
||
# #+caption: Clone and initialize the user-emacs-directory.
|
||
# #+name: lst:prepare-user-emacs-directory
|
||
# #+begin_src shell -n :noeval :tangle no
|
||
# cd ~
|
||
# git clone ccdr@mercury.grenoble.cnrs.fr:SERVER/emacs.d.git .emacs.d
|
||
# make --directory=.emacs.d init
|
||
# emacs &
|
||
# #+end_src
|
||
|
||
#+caption[Prepare the user-emacs-directory without =ssh= access]:
|
||
#+caption: Clone and initialize the user-emacs-directory without =ssh= access.
|
||
#+name: lst:prepare-user-emacs-directory-with-https
|
||
#+begin_src shell -n :noeval :tangle no
|
||
cd ~
|
||
git clone https://forge.chapril.org/gav451/emacs.d.git .emacs.d
|
||
make --directory=.emacs.d init
|
||
emacs &
|
||
#+end_src
|
||
|
||
#+caption[Prepare the =user-emacs-directory= with =ssh= access]:
|
||
#+caption: Clone and initialize the user-emacs-directory with =ssh= access.
|
||
#+name: lst:prepare-user-emacs-directory-with-ssh
|
||
#+begin_src shell -n :noeval :tangle no
|
||
cd ~
|
||
git clone ssh://gitea@forge.chapril.org:222/gav451/emacs.d.git .emacs.d
|
||
make --directory=.emacs.d init
|
||
emacs &
|
||
#+end_src
|
||
|
||
* Introduction
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: sec:introduction
|
||
:END:
|
||
|
||
This Emacs setup aims to install automatically a minimal set of extension
|
||
packages that allows to handle my reports and presentations. The file format of
|
||
the reports is [[https://orgmode.org/][Org Mode]] plain text with [[https://www.python.org/][Python]] source code blocks and the file
|
||
format of the presentations is [[https://www.latex-project.org/][LaTeX]].
|
||
|
||
This [[info:org#Top][org]] file (more precisely the original [[info:org#Top][org]] source file of this file)
|
||
illustrates three methods in my work-flow:
|
||
1. How to tangle (or export) source blocks from [[info:org#Top][org]] files. This file contains
|
||
source blocks to produce the files =early-init.el=, =init.el=, =latexmkrc=, =org-store-link=, and =example.py= by tangling.
|
||
2. How to export [[info:org#Top][org]] files to other formats such as [[https://en.wikipedia.org/wiki/HTML][HTML]], [[https://www.latex-project.org/][LaTeX]], and [[https://en.wikipedia.org/wiki/PDF][PDF]].
|
||
3. How [[info:org#Hyperlinks][org hyperlinks (info)]] allow to link inside and outside [[info:org#Top][Org Mode]]: hover
|
||
over or click on the links to experiment.
|
||
|
||
The [[https://en.wikipedia.org/wiki/AUCTeX][AUCTeX - Aalborg University Center TeX]] extension package provides a
|
||
powerful [[https://en.wikipedia.org/wiki/Text-based_user_interface][Text-based User Interface (TUI)]] environment to edit the [[https://www.latex-project.org/][LaTeX]]
|
||
presentations.
|
||
|
||
The [[https://github.com/bdarcus/citar][Citar]] extension package provides quick filtering and selecting of
|
||
bibliographic entries, and the option to run different commands on those
|
||
selections. [[https://github.com/bdarcus/citar][Citar]] exploits the extension packages [[https://github.com/minad/vertico][Vertico]], [[https://github.com/oantolin/embark][Embark]], [[https://github.com/minad/marginalia][Marginalia]],
|
||
and [[https://github.com/minad/consult][Consult]]. The [[https://github.com/andras-simonyi/citeproc-el][CiteProc]] extension package provides [[https://citationstyles.org/][CSL: citation style
|
||
language]] processing capabilities to [[https://github.com/bdarcus/citar][Citar]] and [[https://orgmode.org/][Org Mode]]. A curated repository
|
||
of CSL styles is the [[https://github.com/citation-style-language/styles#readme][citation style language repository]].
|
||
|
||
The [[https://github.com/vedang/pdf-tools][PDF-Tools]] extension package renders [[https://en.wikipedia.org/wiki/PDF][PDF]] file with the possibility to
|
||
annotate the file or to click on anchors in the [[https://en.wikipedia.org/wiki/PDF][PDF]] file that link back to the
|
||
original [[https://www.latex-project.org/][LaTeX]] file of the document. An example of my work-flow are the steps
|
||
to convert this [[info:org#Top][org]] file to [[https://en.wikipedia.org/wiki/PDF][PDF]] and to see the result with [[HTTPS://github.com/vedang/pdf-tools][PDF-Tools]] in Emacs:
|
||
execute the commands ~pdf-tools-install~, ~org-babel-tangle~,
|
||
~org-latex-export-latex-to-latex~, and ~compile~. This sets up an infinite
|
||
[[https://www.latex-project.org/][LaTeX]] compilation loop to update and redisplay the [[https://en.wikipedia.org/wiki/PDF][PDF]] file after excution of
|
||
the ~org-latex-export-latex-to-latex~ command in this buffer.
|
||
|
||
Here follows a list of interesting Emacs configurations:
|
||
1. [[https://github.com/alhassy/emacs.d][Musa Al-hassy's configuration]] is an impressive example of producing the Emacs
|
||
initialization files and other files by tangling an [[info:org#Top][org]] file. His methodology
|
||
is impressive, as his [[https://alhassy.github.io/ElispCheatSheet/][Elisp Cheat Sheet]] and [[https://alhassy.github.io/org-special-block-extras/][org-special-block-extra package]]
|
||
show. To me, this is a configuration to admire, but his methodology is way
|
||
over my head.
|
||
2. [[https://github.com/oantolin/emacs-config][Omar Antolin Camarena's configuration]] exploits built-in packages, Omar's own
|
||
small packages, and large external packages. Omar is the author of [[https://github.com/oantolin/orderless][orderless]]
|
||
and [[https://github.com/oantolin/embark][embark]]. My use =setopt= to set Emacs options instead of the customize
|
||
interface comes from his idea of using =custom-set-variables= although that
|
||
is [[https://www.masteringemacs.org/article/bad-emacs-advice][bad Emacs advice]] for new users.
|
||
3. [[https://gitlab.com/ambrevar/dotfiles][Pierre Neirhardt's configuration]] implements lazy loading without help of
|
||
external packages. I have stolen his approach of using lazy loading to
|
||
silently ignore the setup stanzas of uninstalled extension packages.
|
||
4. [[https://sachachua.com/dotemacs/][Sacha Chua's configuration]] is a practical example of producing the Emacs
|
||
initialization files by tangling an [[info:org#Top][org]] file. It gives me the impression
|
||
that she is a very practical person trying to achieve her goals by the most
|
||
efficient means.
|
||
5. [[https://github.com/purcell/emacs.d][Steve Purcell's configuration]] is well organized and a showcase of readable
|
||
code. Its commit and issue histories are also helpful: see for instance the
|
||
discussion [[https://github.com/purcell/emacs.d/issues/778][The order of company candidates is incorrect in Emacs lisp mode]].
|
||
|
||
Here follows a list of links on how to use Emacs and Elisp:
|
||
1. [[https://www.youtube.com/watch?v=lkIicfzPBys][A guided tour of Emacs]] is a link to a video tour pointing how [[info:emacs#Buffers][buffers (info)]],
|
||
[[info:emacs#Dired][dired (info)]], [[info:emacs#Help][documentation (info)]], [[info:elisp#Top][elisp (info)]], [[info:elisp#Debugging][elisp debugging (info)]],
|
||
[[info:eshell#Top][eshell (info)]], and [[info:emacs#Keyboard Macros][keyboard macros (info)]] turn Emacs in an powerful coding
|
||
and editing environment.
|
||
2. [[https://www.youtube.com/watch?v=6ZWp05OW1c0][Emergency Emacs]] is a link to a video on fundamental editing features and
|
||
principles with focus on [[info:ediff#Top][ediff (info)]], [[info:emacs#Undo][undo (info)]], [[info:emacs#Moving Point][moving point (info)]],
|
||
[[info:emacs#Erasing][erasing (info)]], and [[info:emacs#Dynamic Abbrevs][dynamic abbreviations (info)]].
|
||
2. [[https://endlessparentheses.com/][Endless Parentheses]] is a blog with many mindblowing code snippets.
|
||
3. [[https://www.murilopereira.com/how-to-open-a-file-in-emacs/][How to open a file in Emacs]] looks into [[info:elisp#Top][elisp (info)]] and
|
||
[[info:elisp#Debugging][elisp debugging (info)]] and then compares Emacs, [[https://www.vim.org/][Vim]],
|
||
[[https://neovim.io/][Neovim]], and [[https://code.visualstudio.com/][Visual Studio Code]] with respect to values and technology.
|
||
4. [[https://protesilaos.com/codelog/2022-01-31-learning-emacs/][Learning Emacs and Elisp]] is a link to a video tutorial with a transcript on
|
||
the best approach to learn Emacs and Elisp.
|
||
5. [[https://www.masteringemacs.org/][Mastering Emacs]] is a link to a blog with many interesting posts that promotes
|
||
a book on how to become a proficient Emacs user.
|
||
|
||
* [[info:emacs#Early Init File][Early Init File (info)]]
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: sec:early-init-file
|
||
:END:
|
||
|
||
Try to load [[https://github.com/emacscollective/no-littering][no-littering]] as early as possible, since it helps to keep
|
||
=~/.emacs.d= clean.
|
||
|
||
#+caption[Tangle the early-init-file]:
|
||
#+caption: Tangle the early-init-file.
|
||
#+name: lst:tangle-early-init-file
|
||
#+begin_src emacs-lisp -n :results silent :tangle early-init.el
|
||
;;; early-init.el --- user early-init file -*- lexical-binding: t -*-
|
||
;;; Commentary:
|
||
;;; Code:
|
||
(setq load-prefer-newer t)
|
||
|
||
(require 'no-littering nil 'noerror)
|
||
|
||
(provide 'early-init)
|
||
;; Emacs looks for "Local variables:" after the last "?\n?\f".
|
||
|
||
;; Local Variables:
|
||
;; indent-tabs-mode: nil
|
||
;; End:
|
||
;;; earl-init.el ends here
|
||
#+end_src
|
||
|
||
In order to get help in understanding the code block above in a buffer showing
|
||
the original [[info:org#Top][Org]] source file, type {{{kbd(C-c C-c)}}} after moving
|
||
point (or cursor) to one of the items of the list:
|
||
1. src_emacs-lisp{(describe-variable #'load-prefer-newer t)}
|
||
2. src_emacs-lisp{(apropos-library "no-littering")}
|
||
3. src_emacs-lisp{(find-function #'hack-local-variables)}
|
||
to execute the code between the curly braces for access to help. This shows
|
||
that *Emacs is a self-documenting editor.*
|
||
|
||
* [[info:emacs#Init File][Init File (info)]] header
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: sec:init-file-header
|
||
:END:
|
||
|
||
The =user-init-file= header requires =cl-lib= and sets Emacs options. It
|
||
consists of two parts in listing [[lst:1st-setopt-call]], and [[lst:2nd-setopt-call]] in
|
||
order to limit the length of the listings for exporting to LaTeX.
|
||
|
||
The [[info:elisp#Quoting][quoting (info)]] and the [[info:elisp#Backquote][backquote (info)]] pages explain how to understand the
|
||
~'~ (quote), ~`~ (backquote), ~,~ (substitute) and ~@,~ (splice) in in listing
|
||
[[lst:1st-setopt-call]] and [[lst:2nd-setopt-call]]. A tutorial of how to use those
|
||
reader macros is the [[https://mullikine.github.io/posts/macro-tutorial/][didactic emacs-lisp macro example]].
|
||
|
||
The [[info:emacs#Init File][init file (info)]] does not load the ~custom-file~ in spite of the
|
||
recommendation of [[info:emacs#Saving Customizations][saving customizations (info)]].
|
||
|
||
#+caption[Set the first set of Emacs options]:
|
||
#+caption: Set the first set of Emacs options.
|
||
#+name: lst:1st-setopt-call
|
||
#+begin_src emacs-lisp -n :results silent
|
||
;;; init.el --- user init file -*- lexical-binding: t -*-
|
||
;;; Commentary:
|
||
;;; Code:
|
||
(require 'cl-lib)
|
||
|
||
(when (version< emacs-version "29.0.60")
|
||
(error "This `init.el' requires at least Emacs-29.0.60"))
|
||
|
||
(setopt
|
||
after-save-hook #'executable-make-buffer-file-executable-if-script-p
|
||
column-number-mode t
|
||
cursor-type 'box
|
||
custom-file (locate-user-emacs-file "custom.el")
|
||
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
|
||
mode-line-compact 'long
|
||
next-error-message-highlight t
|
||
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
|
||
use-short-answers t
|
||
view-read-only t)
|
||
|
||
(when (eq system-type 'darwin)
|
||
(setopt ns-alternate-modifier nil
|
||
ns-command-modifier 'meta
|
||
ns-right-command-modifier 'super))
|
||
|
||
(when (eq window-system 'ns)
|
||
(add-to-list 'initial-frame-alist '(height . 51))
|
||
(add-to-list 'initial-frame-alist '(width . 180)))
|
||
#+end_src
|
||
|
||
#+caption[Set the second set of Emacs options]:
|
||
#+caption: Set the second set of Emacs options.
|
||
#+name: lst:2nd-setopt-call
|
||
#+begin_src emacs-lisp -n :results silent
|
||
(setopt
|
||
;; See https://github.com/melpa/melpa#mirrors for official Melpa mirrors.
|
||
;; ("melpa" . "https://www.mirrorservice.org/sites/melpa.org/packages/")
|
||
package-archives '(("gnu" . "https://elpa.gnu.org/packages/")
|
||
("gnu-devel" . "https://elpa.gnu.org/devel/")
|
||
("melpa" . "https://melpa.org/packages/")
|
||
("nongnu" . "https://elpa.nongnu.org/nongnu/"))
|
||
;; Pin packages to GNU ELPA for info or stability.
|
||
package-pinned-packages '((auctex . "gnu")
|
||
(compat . "gnu")
|
||
(consult . "gnu-devel")
|
||
(embark . "gnu-devel")
|
||
(embark-consult . "gnu-devel")
|
||
(engrave-faces . "gnu-devel")
|
||
(hyperbole . "gnu-devel")
|
||
(marginalia . "gnu-devel")
|
||
(org . "gnu-devel")
|
||
(queue . "gnu")
|
||
(rainbow-mode . "gnu")
|
||
(spinner . "gnu")
|
||
(xr . "gnu")
|
||
(vertico . "gnu-devel"))
|
||
package-selected-packages '(async ; asynchroneous processing
|
||
debbugs ; access the GNU bug tracker
|
||
no-littering ; keep `user-emacs-directory' clean
|
||
org ; thought organizer
|
||
wgrep)) ; open a writable grep buffer
|
||
#+end_src
|
||
|
||
* [[info:emacs#Package Installation][Install the selected packages (info)]]
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: sec:install-selected-packages
|
||
:END:
|
||
|
||
[[info:emacs#Package Installation][Emacs installs packages]] from archives on the internet. This setup uses three
|
||
archives:
|
||
1. The [[https://elpa.gnu.org/][GNU Emacs Lisp Package Archive]]
|
||
2. The [[https://elpa.nongnu.org/][NonGNU Emacs Lisp Package Archive]].
|
||
3. The [[https://melpa.org/#/][Milkypostman's Emacs Lisp Package Archive (MELPA)]] with its official
|
||
[[https://www.mirrorservice.org/sites/melpa.org/packages/][mirror]] in case [[https://downforeveryoneorjustme.com/melpa.org][is Melpa.org down?]] tells [[https://melpa.org/#/][MELPA]] is down for everyone.
|
||
The code in listing [[lst:install-selected-packages]] assumes that the package
|
||
system is in a *virgin* state if the package [[https://github.com/emacscollective/no-littering][no-littering]] is not present:
|
||
1. It installs and loads [[https://github.com/emacscollective/no-littering][no-littering]] after ensuring refreshing of the
|
||
contents of available packages.
|
||
2. It installs [[https://elpa.gnu.org/devel/org.html][Org (GNU-devel ELPA archive)]] early to shadow the built-in package
|
||
while preventing collisions between the snapshot and built-in packages.
|
||
3. It calls src_emacs-lisp{(package-install-selected-packages)} to check the
|
||
installation status of all packages in
|
||
src_emacs-lisp{package-selected-packages} and to install the missing packages
|
||
after the user has agreed to its prompt.
|
||
4. It defines a function to ensure the installation of packages in other source
|
||
blocks. This allows skipping installation in sections with a =:noexport:=
|
||
tag by disallowing tangling.
|
||
In case of normal Emacs usage, src_emacs-lisp{(package-list-packages)} refreshes
|
||
the contents of packages and allows to update packages to the latest version.
|
||
|
||
#+caption[Install the selected packages]:
|
||
#+caption: Install the selected packages.
|
||
#+name: lst:install-selected-packages
|
||
#+begin_src emacs-lisp -n :results silent
|
||
(unless noninteractive
|
||
(unless (require 'no-littering nil 'noerror)
|
||
(unless (bound-and-true-p package-archive-contents)
|
||
(package-refresh-contents))
|
||
;; Install and require `no-littering'.
|
||
(package-install 'no-littering)
|
||
(require 'no-littering)
|
||
;; 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)
|
||
;; Install the selected packages.
|
||
(package-install-selected-packages)))
|
||
|
||
(defun ensure-package-selection (package)
|
||
"Ensure selection of PACKAGE."
|
||
(when (and (package-installed-p package)
|
||
(bound-and-true-p package-selected-packages))
|
||
(cl-pushnew package package-selected-packages)))
|
||
|
||
(defun ensure-package-installation (&rest packages)
|
||
"Ensure installation of all packages in PACKAGES."
|
||
(let ((ok t))
|
||
(dolist (package packages)
|
||
(unless (package-installed-p package)
|
||
(package-install package))
|
||
(unless (ensure-package-selection package)
|
||
(setq ok nil)))
|
||
ok))
|
||
#+end_src
|
||
|
||
* [[https://git.savannah.gnu.org/cgit/emacs.git/tree/admin/notes/tree-sitter/starter-guide?h=feature/tree-sitter][Emacs Tree-sitter]]
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: sec:emacs-tree-sitter
|
||
:END:
|
||
|
||
#+caption[Setup Emacs Tree-sitter]:
|
||
#+caption: Setup Emacs Tree-sitter.
|
||
#+name: lst:setup-emacs-tree-sitter
|
||
#+begin_src emacs-lisp -n :results silent
|
||
(when (require 'treesit nil 'noerror)
|
||
(setopt
|
||
treesit-language-source-alist
|
||
'((bash "https://github.com/tree-sitter/tree-sitter-bash" "master" "src")
|
||
(c "https://github.com/tree-sitter/tree-sitter-c" "master" "src")
|
||
(cmake "https://github.com/uyha/tree-sitter-cmake" "master" "src")
|
||
(cpp "https://github.com/tree-sitter/tree-sitter-cpp" "master" "src")
|
||
(css "https://github.com/tree-sitter/tree-sitter-css" "master" "src")
|
||
(glsl "https://github.com/theHamsta/tree-sitter-glsl" "master" "src")
|
||
(go "https://github.com/tree-sitter/tree-sitter-go" "master" "src")
|
||
(html "https://github.com/tree-sitter/tree-sitter-html" "master" "src")
|
||
(java "https://github.com/tree-sitter/tree-sitter-java" "master" "src")
|
||
(javascript "https://github.com/tree-sitter/tree-sitter-javascript"
|
||
"master" "src")
|
||
(json "https://github.com/tree-sitter/tree-sitter-json" "master" "src")
|
||
(julia "https://github.com/tree-sitter/tree-sitter-julia" "master" "src")
|
||
(make "https://github.com/alemuller/tree-sitter-make" "main" "src")
|
||
(perl "https://github.com/ganezdragon/tree-sitter-perl" "master" "src")
|
||
(python "https://github.com/tree-sitter/tree-sitter-python" "master" "src")
|
||
(ruby "https://github.com/tree-sitter/tree-sitter-ruby" "master" "src")
|
||
(rust "https://github.com/tree-sitter/tree-sitter-rust" "master" "src")
|
||
(scala "https://github.com/tree-sitter/tree-sitter-scala" "master" "src")
|
||
(sql "https://github.com/m-novikov/tree-sitter-sql" "main" "src")
|
||
(swift "https://github.com/tree-sitter/tree-sitter-swift" "master" "src")
|
||
(toml "https://github.com/tree-sitter/tree-sitter-toml" "master" "src")
|
||
(tsx "https://github.com/tree-sitter/tree-sitter-typescript"
|
||
"master" "tsx/src")
|
||
(typescript "https://github.com/tree-sitter/tree-sitter-typescript"
|
||
"master" "typescript/src")
|
||
(yaml "https://github.com/ikatyang/tree-sitter-yaml" "master" "src")))
|
||
|
||
(add-to-list 'auto-mode-alist
|
||
`(,(rx ".py" (opt (any "iw")) eos) . python-ts-mode)))
|
||
#+end_src
|
||
|
||
* [[info:emacs#Faces][Text faces or styles (info)]]
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: sec:text-faces-or-styles
|
||
:END:
|
||
|
||
This setup does not use [[info:emacs#Custom Themes][custom themes (info)]] to avoid endless tweaking, but it
|
||
does a minimal setup of fonts and faces. See the [[https://protesilaos.com/codelog/2020-09-05-emacs-note-mixed-font-heights/][note on mixed font heights in
|
||
Emacs]] for how to setup fonts properly. It boils down to two rules:
|
||
1. The height of the default face must be an integer number to make the height a
|
||
physical quantity.
|
||
2. The heights of all other faces must be real numbers to scale those heights
|
||
with respect to the height of the face (those heights default to 1.0 for no
|
||
scaling).
|
||
The code in listing [[lst:configure-face-attributes]] 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]], [[lst:shadow-org-font-lock-faces]], and
|
||
[[lst:shadow-org-font-lock-faces]] show a few tweaks to improve visibility without
|
||
theming:
|
||
1. Fixing the =gtk= background color of the already loaded =region= face.
|
||
2. Toggling between a dark and light background by means of
|
||
src_emacs-lisp{(invert-default-face)}.
|
||
3. Shadowing the definition of faces before loading. The last item in the page
|
||
on [[https://orgmode.org/worg/org-contrib/babel/examples/fontify-src-code-blocks.html#org5c4406f][fontifying Org mode source code blocks]] describes this method.
|
||
|
||
#+caption[Configure =face-attributes=]:
|
||
#+caption: Configure =face-attributes=.
|
||
#+name: lst:configure-face-attributes
|
||
#+begin_src emacs-lisp -n :results silent
|
||
(with-eval-after-load 'emacs
|
||
;; Set face attributes.
|
||
(cond
|
||
((eq system-type 'darwin)
|
||
(set-face-attribute 'default nil :family "Hack" :height 120)
|
||
(set-face-attribute 'fixed-pitch nil :family "Hack")
|
||
(set-face-attribute 'variable-pitch nil :family "FiraGo"))
|
||
((eq system-type 'gnu/linux)
|
||
(set-face-attribute 'default nil :family "Hack" :height 110)
|
||
(set-face-attribute 'fixed-pitch nil :family "Hack")
|
||
(set-face-attribute 'variable-pitch nil :family "FiraGo"))
|
||
(t
|
||
(set-face-attribute 'default nil :family "Hack" :height 110)
|
||
(set-face-attribute 'fixed-pitch nil :family "Hack")
|
||
(set-face-attribute 'variable-pitch nil :family "DejaVu Sans"))))
|
||
#+end_src
|
||
|
||
#+caption[Implement =set-default-face-height=]:
|
||
#+caption: Implement =set-default-face-height=.
|
||
#+name: lst:set-default-face-height
|
||
#+begin_src emacs-lisp -n :results silent
|
||
(with-eval-after-load 'emacs
|
||
(defun set-default-face-height ()
|
||
"Set the default face height in all current and future frames.
|
||
|
||
Scale all other faces with a height that is a real number."
|
||
(interactive)
|
||
(let* ((prompt (format "face 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))))
|
||
#+end_src
|
||
|
||
#+caption[Use =buffer-face-mode= to set fixed or variable pitch face]:
|
||
#+caption: Use =buffer-face-mode= to set fixed or variable pitch face.
|
||
#+name: lst:use-buffer-face-mode-for-fixed-or-variable-pitch-face
|
||
#+begin_src emacs-lisp -n :results silent
|
||
;; Use proportional font faces in current buffer
|
||
(defun set-buffer-variable-pitch-face ()
|
||
"Set a variable width (proportional) font in current buffer."
|
||
(interactive)
|
||
(setq buffer-face-mode-face 'variable-pitch)
|
||
(buffer-face-mode))
|
||
|
||
;; Use monospaced font faces in current buffer
|
||
(defun set-buffer-fixed-pitch-face ()
|
||
"Set a fixed width (monospace) font in current buffer."
|
||
(interactive)
|
||
(setq buffer-face-mode-face 'fixed-pitch)
|
||
(buffer-face-mode))
|
||
|
||
(add-hook 'magit-mode-hook #'set-buffer-fixed-pitch-face)
|
||
(add-hook 'prog-mode-hook #'set-buffer-fixed-pitch-face)
|
||
#+end_src
|
||
|
||
#+caption[Fix a `gtk' color and implement =invert-default-face=]:
|
||
#+caption: Fix a `gtk' color and implement =invert-default-face=.
|
||
#+name: lst:fix-gtk-color-for-invert-default-face
|
||
#+begin_src emacs-lisp -n :results silent
|
||
(with-eval-after-load 'emacs
|
||
(defun fix-gtk-region-face-background-color ()
|
||
(when (featurep 'gtk)
|
||
(set-face-attribute
|
||
'region nil
|
||
:background (cdr (assoc (face-attribute 'default :background)
|
||
'(("white" . "LightGoldenrod2")
|
||
("black" . "blue3")))))))
|
||
|
||
(defun invert-default-face ()
|
||
"Invert the default face."
|
||
(interactive)
|
||
(invert-face 'default)
|
||
(fix-gtk-region-face-background-color))
|
||
|
||
(fix-gtk-region-face-background-color))
|
||
#+end_src
|
||
|
||
#+caption[Shadow Org font-lock faces to improve the readability]:
|
||
#+caption: Shadow Org font-lock faces to improve the readability.
|
||
#+name: lst:shadow-org-font-lock-faces
|
||
#+begin_src emacs-lisp -n :results silent
|
||
(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."))
|
||
#+end_src
|
||
|
||
#+caption[Shadow Emacs font-lock faces to improve the readability]:
|
||
#+caption: Shadow Emacs font-lock faces to improve the readability.
|
||
#+name: lst:shadow-emacs-font-lock-faces
|
||
#+begin_src emacs-lisp -n :results silent
|
||
(with-eval-after-load 'emacs
|
||
;; Shadow one definition in sh-script.el:
|
||
(defface sh-heredoc
|
||
'((((class color) (background dark))
|
||
(:foreground "yellow"))
|
||
(((class color) (background light))
|
||
(:foreground "magenta"))
|
||
(t
|
||
(:weight bold)))
|
||
"My face to show a here-document."))
|
||
#+end_src
|
||
|
||
* [[info:emacs#Windows][Window management (info)]]
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: sec:window-management
|
||
:END:
|
||
|
||
Mickey Peterson's post [[https://www.masteringemacs.org/article/demystifying-emacs-window-manager][Demystifying Emacs's Window Manager]] invites to improve
|
||
window placement. Listing [[lst:1st-window-management]], [[lst:2nd-window-management]]
|
||
and [[lst:3rd-window-management]] implement a selection of his recommendations.
|
||
|
||
#+caption[Window management functions and key bindings]:
|
||
#+caption: Window management functions and key bindings.
|
||
#+name: lst:1st-window-management
|
||
#+begin_src emacs-lisp -n :results silent
|
||
(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))
|
||
#+end_src
|
||
|
||
#+caption[Window management modes]:
|
||
#+caption: Window management modes.
|
||
#+name: lst:2nd-window-management
|
||
#+begin_src emacs-lisp -n :results silent
|
||
(with-eval-after-load 'emacs
|
||
(winner-mode +1))
|
||
#+end_src
|
||
|
||
#+caption[Window management variables]:
|
||
#+caption: Window management variables.
|
||
#+name: lst:3rd-window-management
|
||
#+begin_src emacs-lisp -n :results silent
|
||
(with-eval-after-load 'emacs
|
||
;; https://www.masteringemacs.org/article/demystifying-emacs-window-manager
|
||
(when (version< "29.0.0" emacs-version)
|
||
(setopt 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)))
|
||
(add-to-list 'display-buffer-alist
|
||
'("^\\*Dictionary\\*" display-buffer-in-side-window
|
||
(side . left)
|
||
(window-width . 70))))
|
||
#+end_src
|
||
|
||
* [[info:elisp#Advising Functions][Advising functions (info)]]
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: sec:advising-function
|
||
:END:
|
||
|
||
#+caption[Toggle any advice]:
|
||
#+caption: Toggle any advice.
|
||
#+name: lst:toggle-any-advice
|
||
#+begin_src emacs-lisp -n :results silent
|
||
(with-eval-after-load 'emacs
|
||
(defun advice-toggle (symbol where function &optional props)
|
||
"Toggle between states after `advice-remove' and `advice-add'."
|
||
(let ((how "%s `%s' advice `%s' %s `%s'"))
|
||
(if (advice-member-p function symbol)
|
||
(progn
|
||
(message how "Removal of" where function "from" symbol)
|
||
(advice-remove symbol function))
|
||
(message how "Addition of" where function "to" symbol)
|
||
(advice-add symbol where function props)))))
|
||
#+end_src
|
||
|
||
#+caption[Toggle specific advice]:
|
||
#+caption: Toggle specific advice.
|
||
#+name: lst:toggle-specific-advice
|
||
#+begin_src emacs-lisp -n :results silent
|
||
(with-eval-after-load 'emacs
|
||
(defun toggle-eww-display-pdf-around ()
|
||
"Toggle `eww-display-advice' advice."
|
||
(interactive)
|
||
(advice-toggle 'eww-display-pdf :around #'eww-display-pdf-as-binary))
|
||
|
||
(defun toggle-ilog-timer-function-after ()
|
||
"Toggle `ilog-timer-function' advice."
|
||
(interactive)
|
||
(advice-toggle 'ilog-timer-function :after #'ilog-ensure-ilog-buffer-window))
|
||
|
||
(defun toggle-keycast-log-update-buffer-override ()
|
||
"Toggle `keycast-log-update-buffer' advice."
|
||
(interactive)
|
||
(advice-toggle 'keycast-log-update-buffer
|
||
:override #'keycast-log-update-buffer-plain))
|
||
|
||
(defun toggle-org-babel-python-format-session-value-override ()
|
||
"Toggle `org-babel-python-format-session-value' advice."
|
||
(interactive)
|
||
(advice-toggle 'org-babel-python-format-session-value
|
||
:override #'org-babel-python-format-session-value-override)))
|
||
#+end_src
|
||
|
||
* [[info:emacs#Dired][Dired: directory editor as file manager (info)]]
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: sec:file-manager
|
||
:END:
|
||
|
||
[[info:emacs#Dired][Dired (info)]] and the [[https://github.com/ranger/ranger#readme][ranger]] file manager offer similar capabilities for copying,
|
||
deleting, opening, renaming, and viewing files and directories, but the
|
||
integration of [[info:emacs#Dired][dired (info)]] in Emacs is obviously much better than the
|
||
integration of [[https://github.com/ranger/ranger#readme][ranger]] in Emacs.
|
||
|
||
For instance, this setup allows to insert an =org-mode= link to an poorly
|
||
identified file containing a specific image into an =org-mode= buffer by means
|
||
of the facilities of [[info:emacs#Dired][dired (info)]] as follows:
|
||
1. Type {{{kbd(C-x d)}}} to open the directory containing the pooly identified
|
||
file with the specific image in a =dired-mode= buffer.
|
||
2. Start searching for the specific image by navigating to the name of any image
|
||
file.
|
||
3. Type {{{kbd(v)}}} to switch to view the file in an =image-mode= buffer.
|
||
4. In the =image-mode= buffer, continue searching for the specific image by
|
||
typing:
|
||
- {{{kbd(n)}}} to view the next image in the =dired-mode= buffer,
|
||
- {{{kbd(p)}}} to view the previous image in the =dired-mode= buffer, and
|
||
- {{{kbd(q)}}} to quit the =image-mode= buffer and to go back to the =dired-mode= buffer.
|
||
5. After finding the specific image, use {{{kbd(C-c l)}}} in the =image-mode=
|
||
buffer or the =dired-mode= buffer to store the =org-link=.
|
||
6. Switch to the =org-mode= buffer and use {{{kbd(C-c C-l)}}} to insert the =org-link= into the buffer.
|
||
|
||
Listing [[lst:set-dired-options]] makes the directory editor sort entries
|
||
alphabetically after grouping the directories before grouping the files by
|
||
extension.
|
||
|
||
#+caption[Set =dired= options]:
|
||
#+caption: Set =dired= options.
|
||
#+name: lst:set-dired-options
|
||
#+begin_src emacs-lisp -n :results silent
|
||
(with-eval-after-load 'dired
|
||
(setopt dired-dwim-target t
|
||
;; | switch | action |
|
||
;; |--------+----------------------------------------|
|
||
;; | -a | also list hidden entries |
|
||
;; | -l | use a long listing format |
|
||
;; | -X | sort alphabetically by entry extension |
|
||
;; | -G | skip long listing format group names |
|
||
;; | -1 | list one entry per line |
|
||
dired-listing-switches "-alGX1 --group-directories-first"
|
||
dired-recursive-copies 'always
|
||
dired-recursive-deletes 'always))
|
||
|
||
(with-eval-after-load 'files
|
||
;; Ensure the use of `GNU-ls' from `coreutils' on darwin.
|
||
(setopt insert-directory-program (or (executable-find "gls")
|
||
(executable-find "ls"))))
|
||
|
||
(with-eval-after-load 'wdired
|
||
(setopt wdired-allow-to-change-permissions t))
|
||
#+end_src
|
||
|
||
Listing [[lst:extra-dired-key-bindings]] adds new key bindings to =dired-mode-map= to:
|
||
1. Open files with the [[https://en.wikipedia.org/wiki/Eww_(web_browser)][Emacs Web Wowser]] inside Emacs.
|
||
2. Let [[https://en.wikipedia.org/wiki/Rsync][rsync]] copy marked files outside Emacs to a local or remote directory.
|
||
The link [[https://truongtx.me/tmtxt-dired-async.html][asynchoronous execution library for Emacs Dired]] is the original source
|
||
for the idea of using [[https://en.wikipedia.org/wiki/Rsync][rsync]] and the blog articles [[https://oremacs.com/2016/02/24/dired-rsync/][using rsync in dired]] and
|
||
[[https://vxlabs.com/2018/03/30/asynchronous-rsync-with-emacs-dired-and-tramp/][asynchronous rsync with Emacs, dired and tramp]] are vivid recommendations written
|
||
by experienced Emacs users.
|
||
|
||
#+caption[Extra =dired= key bindings]:
|
||
#+caption: Extra =dired= key bindings.
|
||
#+name: lst:extra-dired-key-bindings
|
||
#+begin_src emacs-lisp -n :results silent
|
||
(with-eval-after-load 'dired
|
||
(defun dired-eww-open-file ()
|
||
"In Dired, open the regular file named on this line with eww"
|
||
(interactive)
|
||
(let ((file (dired-get-file-for-visit)))
|
||
(if (file-regular-p file)
|
||
(eww-open-file file)
|
||
(error "Eww rejects `%s', since it is not a regular file" file))))
|
||
|
||
;; 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))
|
||
#+end_src
|
||
|
||
* [[info:emacs#Completion Styles][Minibuffer completion styles (info)]]
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: sec:minibuffer-completion-styles
|
||
:END:
|
||
|
||
Listing [[lst:set-minibuffer-options][set minibuffer options]] implements ideas of the post [[https://www.masteringemacs.org/article/understanding-minibuffer-completion][Understanding
|
||
minibuffer completion]].
|
||
|
||
#+caption[Set =minibuffer= options]:
|
||
#+caption: Set =minibuffer= options.
|
||
#+name: lst:set-minibuffer-options
|
||
#+begin_src emacs-lisp -n :results silent
|
||
(when (require 'minibuffer nil 'noerror)
|
||
;; https://www.masteringemacs.org/article/understanding-minibuffer-completion
|
||
(setopt
|
||
completion-category-overrides '((file (styles basic substring)))
|
||
completion-ignore-case nil
|
||
completion-styles '(basic flex partial-completion substring)))
|
||
#+end_src
|
||
|
||
* [[info:elisp#Processes][Processes (info)]]
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: sec:processes
|
||
:END:
|
||
|
||
Listing [[lst:process-utilities]] defines a function to run a (command-line) program
|
||
with arguments and to obtain a list with its numeric exit status as well as its
|
||
output to =stdout=.
|
||
|
||
#+caption[Process utilities]:
|
||
#+caption: Process utilities.
|
||
#+name: lst:process-utilities
|
||
#+begin_src emacs-lisp -n :results silent
|
||
;; https://gitlab.com/howardabrams/spacemacs.d/blob/master/layers/ha-org/funcs.el#L418
|
||
(defun shell-command-with-exit-code (program &rest args)
|
||
"Run PROGRAM with ARGS and return exit-code and output in a list."
|
||
(with-temp-buffer
|
||
(list (apply 'call-process program nil (current-buffer) nil args)
|
||
(buffer-substring-no-properties (point-min) (point-max)))))
|
||
#+end_src
|
||
|
||
* [[info:emacs#Help][Help (info)]]
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: sec:help
|
||
:END:
|
||
|
||
Table [[tab:help-key-bindings]] lists a number of key bindings to start playing with
|
||
the help facilities of Emacs. Try {{{kbd(C-h o)}}} instead of {{{kbd(C-h f)}}}
|
||
or {{{kbd(C-h v)}}}.
|
||
|
||
#+attr_latex: :booktabs yes :float table
|
||
#+caption[Help key bindings]:
|
||
#+caption: Help key bindings.
|
||
#+name: tab:help-key-bindings
|
||
|------------------------------+----------+------------------|
|
||
| command | key map | keys |
|
||
|------------------------------+----------+------------------|
|
||
| Info-goto-emacs-command-node | help-map | {{{kbd(C-h F)}}} |
|
||
| describe-function | help-map | {{{kbd(C-h f)}}} |
|
||
| describe-key | help-map | {{{kbd(C-h k)}}} |
|
||
| describe-symbol | help-map | {{{kbd(C-h o)}}} |
|
||
| describe-variable | help-map | {{{kbd(C-h v)}}} |
|
||
| info | help-map | {{{kbd(C-h i)}}} |
|
||
|------------------------------+----------+------------------|
|
||
|
||
#+begin_src emacs-lisp -n :results silent :tangle no
|
||
(defun org-key-binding-macro (cmd)
|
||
(let* ((sym (intern-soft cmd))
|
||
(keys (and (commandp sym) (where-is-internal sym nil 'first-only)))
|
||
(prefix (seq-take keys (1- (length keys))))
|
||
(keymap (key-binding prefix 'accept-default)))
|
||
(format "%s %s %s {{{kbd(%s)}}}"
|
||
cmd keymap (help--binding-locus keys nil) (key-description keys))))
|
||
|
||
(org-key-binding-macro 'consult-buffer)
|
||
#+end_src
|
||
|
||
** [[info:emacs#Name Help][Shortdoc-display-group (info)]]
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: sec:shortdoc-display-group
|
||
:END:
|
||
|
||
Listing [[lst:configure-shortdoc]] binds {{{kbd(C-h y)}}} to
|
||
=short-doc-display-group= and defines a short documentation group for functions
|
||
defined in this Org file.
|
||
|
||
#+caption[Configure =shortdoc=]:
|
||
#+caption: Configure =shortdoc=.
|
||
#+name: lst:configure-shortdoc
|
||
#+begin_src emacs-lisp -n :results silent
|
||
(when (fboundp 'shortdoc-display-group)
|
||
(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"
|
||
(advice-toggle :no-manual t)
|
||
(toggle-eww-display-pdf-around :no-manual t)
|
||
(toggle-ilog-timer-function-after :no-manual t)
|
||
(toggle-keycast-log-update-buffer-override :no-manual t)
|
||
(toggle-org-babel-python-format-session-value-override :no-manual t)
|
||
"Face"
|
||
(invert-default-face :no-manual t)
|
||
(set-default-face-height :no-manual t)
|
||
"Interaction"
|
||
(enable-this-command :no-manual t)
|
||
(narrow-or-widen-dwim :no-manual t)
|
||
(org-narrow-to-table :no-manual t)
|
||
"LaTeX"
|
||
(biber-delete-cache :no-manual t)
|
||
(update-lualatex-opentype-font-name-database :no-manual t)
|
||
"Org"
|
||
(by-backend :no-manual t)
|
||
(by-backend-kbd-org-macro :no-manual t)
|
||
(org-active-current-time-stamp :nomanual t)
|
||
(org-babel-execute:latex-extra-header :no-manual t)
|
||
(org-babel-execute:latex-header :no-manual t)
|
||
(org-electric-dollar :no-manual t)
|
||
(org-eval-emacs-lisp-setup-blocks :no-manual t)
|
||
(org-eval-python-setup-blocks :no-manual t)
|
||
(org-eval-infixed-blocks :no-manual t)
|
||
(org-latex-engraved-source-block-filter :no-manual t)
|
||
(org-inactive-current-time-stamp :nomanual t)
|
||
(org-syntax-convert-keyword-case-to-lower :no-manual t)
|
||
(smart-latex-engrave-org-source-blocks :no-manual t)
|
||
(zero-all-org-src-blocks-indentation :no-manual t)
|
||
"Python"
|
||
(choose-common-python-interpreter :no-manual t)
|
||
(choose-common-python-linter :no-manual t))))
|
||
#+end_src
|
||
|
||
** [[info:info#Top][Info (info)]]
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: sec:info
|
||
:END:
|
||
|
||
Listing [[lst:configure-info]] adds the required paths to the places where =info=
|
||
looks for files.
|
||
|
||
#+caption[Configure =info=]:
|
||
#+caption: Configure =info=.
|
||
#+name: lst:configure-info
|
||
#+begin_src emacs-lisp -n :results silent
|
||
(with-eval-after-load 'info
|
||
;; Make Emacs find ALL "*.info" files in `package-user-dir' on Gentoo Linux.
|
||
(when (eq system-type 'gnu/linux)
|
||
(dolist (path
|
||
(nreverse
|
||
(mapcar
|
||
(lambda (name)
|
||
(expand-file-name (file-name-directory name)))
|
||
(directory-files-recursively package-user-dir "\\.info\\'"))))
|
||
(add-to-list 'Info-directory-list path nil #'file-equal-p)))
|
||
;; Make Emacs find my "python.info" file.
|
||
(add-to-list 'Info-directory-list
|
||
(expand-file-name "~/.local/share/info")))
|
||
#+end_src
|
||
|
||
* [[info:emacs#Key Bindings][Key bindings (info)]]
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: sec:key-bindings
|
||
:END:
|
||
|
||
#+attr_latex: :booktabs yes :float table
|
||
#+caption[Basic key bindings]:
|
||
#+caption: Basic key bindings.
|
||
#+name: tab:basic-key-bindings
|
||
|--------------------+------------+------------------------|
|
||
| command | key map | keys |
|
||
|--------------------+------------+------------------------|
|
||
| undo | global-map | {{{kbd(s-z)}}} |
|
||
| backward-kill-word | global-map | {{{kbd(C-backspace)}}} |
|
||
| backward-char | global-map | {{{kbd(C-b)}}} |
|
||
| forward-char | global-map | {{{kbd(C-f)}}} |
|
||
| backward-word | global-map | {{{kbd(M-b)}}} |
|
||
| forward-word | global-map | {{{kbd(M-f)}}} |
|
||
| backward-sentence | global-map | {{{kbd(M-a)}}} |
|
||
| forward-sentence | global-map | {{{kbd(M-e)}}} |
|
||
|--------------------+------------+------------------------|
|
||
|
||
** [[info:emacs#Disabling][Disabling Commands (info)]]
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: sec:enable-disabled-commands
|
||
:END:
|
||
|
||
Execute src_emacs-lisp{(find-library "novice")} to see how Emacs prevents new
|
||
users from shooting themselves in the feet. Listing
|
||
[[lst:configure-disabled-command-function]] enables [[https://www.emacswiki.org/emacs/DisabledCommands][disabled commands on the fly]].
|
||
|
||
#+caption[Configure the =disabled-command-function=]:
|
||
#+caption: Configure the =disabled-command-function=.
|
||
#+name: lst:configure-disabled-command-function
|
||
#+begin_src emacs-lisp -n :results silent
|
||
(with-eval-after-load 'emacs
|
||
(setq disabled-command-function
|
||
(defun enable-this-command (&rest _args)
|
||
"Called when a disabled command is executed.
|
||
Enable it and re-execute it."
|
||
(put this-command 'disabled nil)
|
||
(message "You typed %s. %s was disabled until now."
|
||
(key-description (this-command-keys)) this-command)
|
||
(sit-for 0)
|
||
(call-interactively this-command))))
|
||
#+end_src
|
||
|
||
** [[https://github.com/michael-heerdegen/interaction-log.el#readme][Interaction-log]]
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: sec:interaction-log
|
||
:END:
|
||
|
||
#+caption[Configure =interaction-log=]:
|
||
#+caption: Configure =interaction-log=.
|
||
#+name: lst:configure-interaction-log
|
||
#+begin_src emacs-lisp -n :results silent
|
||
(when (and (ensure-package-installation 'interaction-log)
|
||
(require 'interaction-log nil 'noerror))
|
||
|
||
;; https://www.skybert.net/emacs/show-shortcuts-when-giving-presentations/
|
||
(defun ilog-ensure-ilog-buffer-window ()
|
||
"Ensure the visibility of the `ilog' buffer, when it exists."
|
||
(when (and (get-buffer ilog-buffer-name)
|
||
(not (get-buffer-window ilog-buffer-name)))
|
||
(display-buffer (get-buffer ilog-buffer-name))))
|
||
|
||
(define-key help-map (kbd "C-l") #'interaction-log-mode)
|
||
(advice-add 'ilog-timer-function :after #'ilog-ensure-ilog-buffer-window))
|
||
#+end_src
|
||
|
||
|
||
** [[https://github.com/tarsius/keycast#readme][Keycast]] :noexport:
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: sec:keycast
|
||
:header-args:emacs-lisp: :tangle no :eval never-export
|
||
:END:
|
||
|
||
Listing [[lst:configure-keycast]] configures =keycast=.
|
||
|
||
#+caption[Configure =keycast=]:
|
||
#+caption: Configure =keycast=.
|
||
#+name: lst:configure-keycast
|
||
#+begin_src emacs-lisp -n :results silent
|
||
;; Make `keycast-log-update-buffer' use a buffer similar to the
|
||
;; control buffer `ediff-setup-windows-plain' returns.
|
||
(when (and (ensure-package-installation 'keycast)
|
||
(require 'keycast nil 'noerror))
|
||
(setopt keycast-mode-line-window-predicate 'keycast-bottom-right-window-p)
|
||
|
||
(defun keycast-log-update-buffer-plain ()
|
||
(let ((buffer (get-buffer keycast-log-buffer-name)))
|
||
(unless (buffer-live-p buffer)
|
||
(setq buffer (get-buffer-create keycast-log-buffer-name))
|
||
(with-current-buffer buffer
|
||
(setq buffer-read-only t)))
|
||
(unless (get-buffer-window buffer)
|
||
(display-buffer buffer '(display-buffer-at-bottom
|
||
(dedicated . t)
|
||
(window-height . 10))))
|
||
(when-let ((output (keycast--format keycast-log-format)))
|
||
(with-current-buffer buffer
|
||
(goto-char (if keycast-log-newest-first (point-min) (point-max)))
|
||
(let ((inhibit-read-only t))
|
||
(when (and (> keycast--command-repetitions 0)
|
||
(string-match-p "%[rR]" keycast-log-format))
|
||
(unless keycast-log-newest-first
|
||
(backward-char))
|
||
(ignore-errors
|
||
(delete-region (line-beginning-position)
|
||
(1+ (line-end-position)))))
|
||
(insert output))
|
||
(goto-char (if keycast-log-newest-first (point-min) (point-max)))))))
|
||
|
||
(advice-add 'keycast-log-update-buffer
|
||
:override #'keycast-log-update-buffer-plain))
|
||
#+end_src
|
||
|
||
* [[info:emacs#Emacs Server][Using Emacs as a server (info)]]
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: sec:using-emacs-server
|
||
:END:
|
||
|
||
Emacs can act as a server that listens to a socket to share its state (for
|
||
instance buffers and command history) with other programs by means of a shell
|
||
command =emacsclient=. Section [[#sec:latexmk-save-compile-display-loop]] and
|
||
[[#sec:qutebrowser-userscript]] show how to use ~emacsclient~ to:
|
||
1. Install an asynchronous (or background) loop of saving a LaTeX file,
|
||
compiling it, and redisplaying the output in Emacs.
|
||
2. Make [[https://qutebrowser.org][qutebrowser]] send html links with document titles to Emacs.
|
||
The code in listing [[lst:start-emacs-server]] starts the Emacs server.
|
||
|
||
#+caption[Start the Emacs server]:
|
||
#+caption: Start the Emacs server.
|
||
#+name: lst:start-emacs-server
|
||
#+begin_src emacs-lisp -n :results silent
|
||
(when window-system
|
||
(unless (or noninteractive (daemonp))
|
||
(add-hook 'after-init-hook #'server-start)))
|
||
#+end_src
|
||
|
||
** Latexmk save-compile-display loop
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: sec:latexmk-save-compile-display-loop
|
||
:END:
|
||
|
||
The =latexmk= resource file in the next source code block shows how to use
|
||
=emacsclient= to (re)display the PDF file in Emacs after each successful
|
||
(re)compilation on condition that the local ~compile-command~ variable is
|
||
correct. The local ~compile-command~ variable in the [[#sec:local-variables][local variables]] section
|
||
(only visible in =org= files, but not in =html= and =pdf= files) shows how to
|
||
make use of the =latexmkrc= file.
|
||
|
||
#+attr_latex: :options breaklines
|
||
#+caption[Tangle the =Latexmk= resource file]:
|
||
#+caption: Tangle the =Latexmk= resource file.
|
||
#+name: lst:latexmkrc
|
||
#+begin_src perl -n :tangle latexmkrc :comments no
|
||
# pdf creator
|
||
$pdf_mode = 4; # 4 means lualatex
|
||
# pdf previewer and update pdf previewer
|
||
$pdf_previewer = "emacsclient -e '(find-file-other-window %S)'";
|
||
$pdf_update_method = 4; # 4 runs $pdf_update_command to force the update
|
||
$pdf_update_command = "emacsclient -e '(with-current-buffer (find-buffer-visiting %S) (pdf-view-revert-buffer nil t))'";
|
||
# see for instance glossary.latexmkrc
|
||
add_cus_dep( 'acn', 'acr', 0, 'makeglossaries' );
|
||
add_cus_dep( 'glo', 'gls', 0, 'makeglossaries' );
|
||
# use ".=" to append to $clean_ext
|
||
$clean_ext .= " acr acn alg bbl dvi glo gls glg ist lol lst";
|
||
$clean_ext .= " nav run.xml snm synctex.gz";
|
||
sub makeglossaries {
|
||
my ($name, $path) = fileparse( $$Psource );
|
||
return system "makeglossaries -d '$path' '$name'";
|
||
}
|
||
# Emacs looks for "Local variables:" after the last "newline-formfeed".
|
||
|
||
# Local Variables:
|
||
# mode: perl
|
||
# End:
|
||
#+end_src
|
||
|
||
** [[https://qutebrowser.org/doc/userscripts.html][Qutebrowser userscript]]
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: sec:qutebrowser-userscript
|
||
:END:
|
||
|
||
The next block contains an userscript that sends a [[info:org#The store-link protocol][store-link org-protocol]]
|
||
message with the url and the title from [[https://qutebrowser.org][qutebrowser]] to =emacsclient=. The
|
||
function =urlencode= translates the url and the title for the message. The
|
||
[[info:python#Examples<22>][Python urllib examples]] show how to use =urlencode=. The final =execvp= call
|
||
deals with a [[https://qutebrowser.org][qutebrowser]] userscript requirement: the =emacsclient= process must
|
||
get the PID of the userscript that must kill itself after the take-over.
|
||
Termination of the =emacsclient= process hands control back to [[https://qutebrowser.org][qutebrowser]].
|
||
|
||
On a [[https://en.wikipedia.org/wiki/POSIX][POSIX]] system, you can run the userscript from [[https://qutebrowser.org][qutebrowser]] or from a
|
||
terminal to see whether it works. In case you try to run it from Emacs, Emacs
|
||
may hang or die.
|
||
|
||
#+caption[Tangle a qutebrowser userscript]:
|
||
#+caption: Tangle a qutebrowser userscript.
|
||
#+header: :comments no
|
||
#+header: :tangle-mode (identity #o755)
|
||
#+name: lst:qutebrowser-userscript
|
||
#+begin_src python -n :noeval :tangle org-store-link
|
||
#!/usr/bin/env python
|
||
from urllib.parse import urlencode
|
||
from os import environ, execvp
|
||
|
||
url = environ.get("QUTE_URL", "https://orgmode.org")
|
||
title = environ.get("QUTE_TITLE", "Org Mode")
|
||
parameters = urlencode({"url": url, "title": title})
|
||
print(payload := f"org-protocol://store-link?{parameters}")
|
||
execvp("emacsclient", ("-n", payload))
|
||
#+end_src
|
||
|
||
** TODO Look into: org-protocol handling with other browser on Darwin
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: sec:org-protocol-darwin
|
||
:END:
|
||
|
||
1. [[http://rwx.io/posts/osx-uri-protocol-handler/][Patrick Goddi: macOS URI protocol handler]] and his [[https://github.com/fooqri/uri-handler][URI-Handler]].
|
||
2. [[https://www.hammerspoon.org/][Hammerspoon]]
|
||
3. [[https://vritser.github.io/posts/capture-anywhere/][Emacs Capture Anywhere]]
|
||
|
||
** [[https://en.wikipedia.org/wiki/AppleScript][AppleScript]] :noexport:
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: sec:applescript
|
||
:header-args:emacs-lisp: :tangle no
|
||
:END:
|
||
|
||
#+caption[Ensure =applescript-mode= installation]:
|
||
#+caption: Ensure =applescript-mode= installation.
|
||
#+name: lst:ensure-applescript-mode-installation
|
||
#+begin_src emacs-lisp -n :results silent
|
||
(when (ensure-package-installation 'applescript-mode))
|
||
#+end_src
|
||
|
||
#+caption[Applescript code to register =org-protocol= on Darwin]:
|
||
#+caption: Applescript code to register =org-protocol= on Darwin.
|
||
#+name: lst:applescript-org-protocol
|
||
#+begin_src applescript :tangle no
|
||
on emacsclient(input)
|
||
do shell script "/usr/local/bin/emacsclient -n -c '" & input & "'"
|
||
tell application "Emacs" to activate
|
||
end emacsclient
|
||
|
||
on open location input
|
||
emacsclient(input)
|
||
end open location
|
||
|
||
on open inputs
|
||
repeat with raw_input in inputs
|
||
set input to POSIX path of raw_input
|
||
emacsclient(input)
|
||
end repeat
|
||
end open
|
||
|
||
on run
|
||
do shell script emacsclient("")
|
||
end run
|
||
#+end_src
|
||
|
||
* Completion
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: sec:completion
|
||
:END:
|
||
|
||
[[info:vertico#Top][Vertico (info)]] provides a performant and minimalistic vertical completion UI
|
||
based on the default completion system and behaves therefore correctly under all
|
||
circumstances. [[https://cestlaz.github.io/post/using-emacs-80-vertico/][Using Vertico, Marginalia, Consult, and Embark]] links to a video
|
||
demonstration. Vertico integrates well with fully supported complementary
|
||
packages to enrich the completion UI:
|
||
1. [[info:embark#Top][Embark (info)]] for minibuffer actions with context menus,
|
||
2. [[info:marginalia#Top][Marginalia (info)]] for rich annotations in the minibuffer, and
|
||
3. [[info:consult#Top][Consult (info)]] for useful search and navigation commands,
|
||
where the order is that of [[https://github.com/bdarcus/citar#installation][enhancing citar's experience]] and the configuration
|
||
steps below.
|
||
|
||
Finally, [[https://company-mode.github.io/][company: a modular complete-anything framework for Emacs]] provides
|
||
completion in any buffer.
|
||
|
||
** [[info:company#Top][Company (info)]]
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: sec:company
|
||
:END:
|
||
|
||
[[info:company#Top][Company (info)]] is a modular completion framework and the the snippet [[lst:ensure-company-installation][below]]
|
||
ensures its installation.
|
||
|
||
#+caption[Ensure =company= installation]:
|
||
#+caption: Ensure =company= installation.
|
||
#+name: lst:ensure-company-installation
|
||
#+begin_src emacs-lisp -n :results silent
|
||
(ensure-package-installation 'company)
|
||
#+end_src
|
||
|
||
** [[info:vertico#Top][Vertico (info)]]
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: sec:vertico-configuration
|
||
:END:
|
||
|
||
Listing [[lst:enable-vertico-mode]] configures and enables =savehist-mode= and
|
||
enables =vertico-mode=. The documentation src_emacs-lisp{(describe-function
|
||
'savehist-mode)} why it is best to turn on =savehist-mode= in the Emacs init
|
||
file.
|
||
|
||
#+caption[Enable =savehist-mode= and =vertico-mode=]:
|
||
#+caption: Enable =savehist-mode= and =vertico-mode=.
|
||
#+name: lst:enable-vertico-mode
|
||
#+begin_src emacs-lisp -n :results silent
|
||
(when (require 'savehist nil 'noerror)
|
||
(setopt savehist-additional-variables '(eww-history
|
||
kill-ring
|
||
regexp-search-string
|
||
search-ring
|
||
search-string))
|
||
(savehist-mode +1))
|
||
(when (and (ensure-package-installation 'vertico)
|
||
(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))
|
||
#+end_src
|
||
|
||
#+attr_latex: :booktabs yes :float table
|
||
#+caption[Vertico key map bindings]:
|
||
#+caption: Vertico key map bindings.
|
||
#+name: tab:vertico-keymap-bindings
|
||
|-------------------------------+----------------------------------+---------------------|
|
||
| command | remap | keys |
|
||
|-------------------------------+----------------------------------+---------------------|
|
||
| vertico-directory-delete-char | | {{{kbd(DEL)}}} |
|
||
| vertico-directory-delete-word | | {{{kbd(M-DEL)}}} |
|
||
| vertico-directory-enter | | {{{kbd(RET)}}} |
|
||
| vertico-exit | exit-minibuffer | {{{kbd(C-j)}}} |
|
||
| vertico-exit-input | | {{{kbd(C-RET)}}} |
|
||
| vertico-first | beginning-of-buffer | {{{kbd(M-<)}}} |
|
||
| vertico-first | minibuffer-beginning-of-buffer | {{{kbd(M-<)}}} |
|
||
| vertico-insert | | {{{kbd(TAB)}}} |
|
||
| vertico-last | end-of-buffer | {{{kbd(M->)}}} |
|
||
| vertico-next-group | forward-paragraph | {{{kbd(C-<down>)}}} |
|
||
| vertico-next | next-line-or-history-element | {{{kbd(<down>)}}} |
|
||
| vertico-next | next-line | {{{kbd(C-n)}}} |
|
||
| vertico-previous-group | backward-paragraph | {{{kbd(C-<up>)}}} |
|
||
| vertico-previous | previous-line-or-history-element | {{{kbd(<up>)}}} |
|
||
| vertico-previous | previous-line | {{{kbd(C-p)}}} |
|
||
| vertico-save | kill-ring-save | {{{kbd(M-w)}}} |
|
||
| vertico-scroll-down | scroll-down-command | {{{kbd(M-v)}}} |
|
||
| vertico-scroll-up | scroll-up-command | {{{kbd(C-v)}}} |
|
||
|-------------------------------+----------------------------------+---------------------|
|
||
|
||
** [[info:orderless#Top][Orderless (info)]] :noexport:
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: sec:orderless-configuration
|
||
:header-args:emacs-lisp: :tangle no
|
||
:END:
|
||
|
||
Listing [[lst:configure-orderless]] configures [[info:orderless#Company][orderless for company (info)]]. Note:
|
||
*Python editing is orders of magnitude faster after removal of Orderless*.
|
||
|
||
#+caption[Configure =orderless=]:
|
||
#+caption: Configure =orderless=.
|
||
#+name: lst:configure-orderless
|
||
#+begin_src emacs-lisp -n :results silent
|
||
(when (ensure-package-installation 'marginalia)
|
||
(with-eval-after-load 'orderless
|
||
(setopt orderless-component-separator " +")
|
||
|
||
(defun just-one-face (fn &rest args)
|
||
(let ((orderless-match-faces [completions-common-part]))
|
||
(apply fn args)))
|
||
|
||
(advice-add 'company-capf--candidates :around #'just-one-face)))
|
||
#+end_src
|
||
|
||
** [[info:embark#Top][Embark (info)]]
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: sec:embark-configuration
|
||
:END:
|
||
|
||
Listing [[lst:bind-embark-commands][bind =embark= commands]] binds =embark= commands to the global key map:
|
||
1. =embark-act= prompts the user for an action and performs it.
|
||
2. Except for highlighting of email and web URLs, =embark-dwim= englobes the
|
||
functionality of src_emacs-lisp{(find-library "goto-addr")} that activates
|
||
mail and web URLs to turn them into clickable buttons. Since =embark-dwim=
|
||
acts on a superset of target types, it renders =goto-addr= superfluous. See:
|
||
1. [[info:emacs#Goto Address mode][Goto Address mode (info)]].
|
||
2. [[info:embark#The default action on a target][The default (embark-dwim) action on a target (info)]].
|
||
3. =embark-bindings= allows to explore all current command key bindings in the
|
||
minibuffer.
|
||
4. The initialization src_emacs-lisp{(setq prefix-help-command
|
||
#'embark-prefix-help-command)} enables minibuffer help after a prefix key
|
||
(for instance {{{kbd(C-x)}}} or {{{kbd(C-c)}}}) as typing {{{kbd(C-x C-h)}}}
|
||
or {{{kbd(C-c C-h)}}} shows.
|
||
|
||
#+caption[Bind =embark= commands globally]:
|
||
#+caption: Bind =embark= commands globally.
|
||
#+name: lst:bind-embark-commands
|
||
#+begin_src emacs-lisp -n :results silent
|
||
(when (ensure-package-installation 'embark 'embark-consult)
|
||
(when (fboundp 'embark-act)
|
||
(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)))
|
||
#+end_src
|
||
|
||
** [[info:marginalia#Top][Marginalia (info)]]
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: sec:marginalia-configuration
|
||
:END:
|
||
|
||
Listing [[lst:enable-marginalia-mode]] enables =marginalia-mode=.
|
||
|
||
#+caption[Enable =marginalia-mode=]:
|
||
#+caption: Enable =marginalia-mode=.
|
||
#+name: lst:enable-marginalia-mode
|
||
#+begin_src emacs-lisp -n :results silent
|
||
(when (and (ensure-package-installation 'marginalia)
|
||
(fboundp 'marginalia-mode))
|
||
(marginalia-mode +1))
|
||
#+end_src
|
||
|
||
** [[info:consult#Top][Consult (info)]]
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: sec:consult-setup
|
||
:END:
|
||
|
||
[[info:consult#Top][Consult (info)]] provides practical commands based on the Emacs minibuffer
|
||
completion function [[info:elisp#Minibuffer Completion][completing-read]]. Listing [[lst:bind-consult-commands][bind =consult= commands]] binds
|
||
=consult= commands to different key maps.
|
||
|
||
#+attr_latex: :booktabs yes :float table
|
||
#+caption[Configuration specific key bindings]:
|
||
#+caption: Configuration specific key-bindings.
|
||
#+name: tab:configuration-specific-key-bindings
|
||
|-----------------------------+----------------------+--------------------|
|
||
| command | key map | keys |
|
||
|-----------------------------+----------------------+--------------------|
|
||
| consult-bookmark | ctl-x-r-keymap | {{{kbd(C-x r b)}}} |
|
||
| consult-buffer-other-frame | ctl-x-5-keymap | {{{kbd(C-x 5 b)}}} |
|
||
| consult-buffer-other-window | ctl-x-4-keymap | {{{kbd(C-x 4 b)}}} |
|
||
| consult-buffer | ctl-x-keymap | {{{kbd(C-x b)}}} |
|
||
| consult-compile-error | goto-map | {{{kbd(M-g e)}}} |
|
||
| consult-complex-command | ctl-x-keymap | {{{kbd(C-x M-:)}}} |
|
||
| consult-find | search-map | {{{kbd(M-s f)}}} |
|
||
| consult-focus-lines | search-map | {{{kbd(M-s u)}}} |
|
||
| consult-git-grep | search-map | {{{kbd(M-s g)}}} |
|
||
| consult-global-mark | goto-map | {{{kbd(M-g k)}}} |
|
||
| consult-goto-line | goto-map | {{{kbd(M-g M-g)}}} |
|
||
| consult-goto-line | goto-map | {{{kbd(M-g g)}}} |
|
||
| consult-history | global-map | {{{kbd(C-c h)}}} |
|
||
| consult-imenu | goto-map | {{{kbd(M-g i)}}} |
|
||
| consult-keep-lines | search-map | {{{kbd(M-s k)}}} |
|
||
| consult-line | search-map | {{{kbd(M-s l)}}} |
|
||
| consult-mark | goto-map | {{{kbd(M-g m)}}} |
|
||
| consult-mode-command | global-map | {{{kbd(C-c m)}}} |
|
||
| consult-multi-occur | search-map | {{{kbd(M-s m)}}} |
|
||
| consult-outline | goto-map | {{{kbd(M-g o)}}} |
|
||
| consult-register | ctl-x-r-keymap | {{{kbd(C-x r x)}}} |
|
||
| consult-yank-pop | global-map | {{{kbd(M-y)}}} |
|
||
|-----------------------------+----------------------+--------------------|
|
||
| deadgrep | search-map | {{{kbd(M-s d)}}} |
|
||
| elfeed | global-map | {{{kbd(C-x w)}}} |
|
||
| embark-act | global-map | {{{kbd(C-\,)}}} |
|
||
| embark-bindings | global-map | {{{kbd(C-h B)}}} |
|
||
| embark-dwim | global-map | {{{kbd(C-:)}}} |
|
||
| iedit-mode | global-map | {{{kbd(C-;)}}} |
|
||
| minibuffer-complete-history | minibuffer-local-map | {{{kbd(C-<tab>)}}} |
|
||
| narrow-or-widen-dwim | ctl-x-keymap | {{{kbd(C-x C-n)}}} |
|
||
| org-agenda | global-map | {{{kbd(C-c a)}}} |
|
||
| org-capture | global-map | {{{kbd(C-c c)}}} |
|
||
| org-cite | org-mode-map | {{{kbd(C-c b)}}} |
|
||
| org-insert-link-global | global-map | {{{kbd(C-c C-l)}}} |
|
||
| org-narrow-to-table | ctl-x-keymap | {{{kbd(C-x n t)}}} |
|
||
| org-store-link | global-map | {{{kbd(C-c l)}}} |
|
||
|-----------------------------+----------------------+--------------------|
|
||
|
||
|
||
#+caption[Bind =consult= commands]:
|
||
#+caption: Bind =consult= commands.
|
||
#+name: lst:bind-consult-commands
|
||
#+begin_src emacs-lisp -n :results silent
|
||
(when (ensure-package-installation 'consult)
|
||
;; C-c bindings (current-global-map)
|
||
(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)
|
||
(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))
|
||
#+end_src
|
||
|
||
** [[https://company-mode.github.io/][Company: a modular complete anything framework for Emacs]]
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: sec:company-setup
|
||
:END:
|
||
|
||
Listing [[lst:setup-company]] configures =company=.
|
||
|
||
#+caption[Setup =company=]:
|
||
#+caption: Setup =company=.
|
||
#+name: lst:setup-company
|
||
#+begin_src emacs-lisp -n :results silent
|
||
(when (fboundp 'company-mode)
|
||
;; https://github.com/purcell/emacs.d/issues/778
|
||
(setopt company-transformers '(company-sort-by-occurrence))
|
||
(dolist (hook '(LaTeX-mode-hook
|
||
org-mode-hook
|
||
emacs-lisp-mode-hook
|
||
lisp-interaction-mode-hook
|
||
lisp-mode-hook
|
||
python-mode-hook
|
||
ielm-mode-hook
|
||
sly-mrepl-mode-hook))
|
||
(add-hook hook #'company-mode)))
|
||
#+end_src
|
||
|
||
* [[info:emacs#Search][Search and replace (info)]]
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: sec:search-replace
|
||
:END:
|
||
|
||
** [[info:emacs#Regexp Replace][Regexp Replace (info)]]
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: sec:regexp-replace
|
||
:END:
|
||
|
||
Executing {{{kbd(M-x replace-regexp)}}} prompts fo a regular expression =REGEXP=
|
||
and a substitution =NEWSTRING= to replace =REGEXP= with =NEWSTRING=. The tools
|
||
in section [[#sec:narrowing][Narrowing]] allow to limit the scope of {{{kbd(M-x replace-regexp)}}}.
|
||
A list pointing to relevant documentation is:
|
||
1. [[info:emacs#Regexp Replace][Regexp Replace (info)]] explains how the substitution handles references to
|
||
regular expression groups and lisp expressions.
|
||
2. [[info:emacs#Regexps][Syntax of Regular Expressions (info)]] describes regular expression features
|
||
for users.
|
||
3. [[info:elisp#Regular Expressions][Emacs Lisp Regular Expressions (info)]] describes regular expression features
|
||
for programmers.
|
||
4. [[https://www.emacswiki.org/][Emacs Wiki]] has an article on [[https://www.emacswiki.org/emacs/ReplaceRegexpWithLispExpressions][replacing regexp with lisp expressions]].
|
||
Table [[tab:replace-regexp-tranform]] tabulates how to perfom example tasks by means
|
||
of {{{kbd(M-x replace-regexp)}}} using =(regular-expression transform)= pairs.
|
||
|
||
#+attr_latex: :booktabs yes :float table
|
||
#+caption[Example =replace-regexp= (regular-expression substitution) pairs]:
|
||
#+caption: Example =replace-regexp= (regular-expression substitution) pairs.
|
||
#+name: tab:replace-regexp-tranform
|
||
| task | regular expression | substitution |
|
||
|-----------------------------+---------------------------------+---------------|
|
||
| move dot two digits forward | =0\.\([[:digit:]][[:digit:]]\)= | =0\1.= |
|
||
| move dot two digits forward | =0\.\([0-9][0-9]\)= | =0\1.= |
|
||
| renumber grep/occur matches | =\(.+:\)= | =\,(1+ \#)._= |
|
||
|-----------------------------+---------------------------------+---------------|
|
||
|
||
** [[https://github.com/Wilfred/deadgrep#readme][Deadgrep]]
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: sec:deadgrep
|
||
:END:
|
||
|
||
[[https://github.com/Wilfred/deadgrep#readme][Deadgrep]] uses [[https://github.com/BurntSushi/ripgrep#readme][ripgrep]] for superfast text searching in the default directory or
|
||
the current [[https://en.wikipedia.org/wiki/Version_control][VCS]] directory tree. Listing [[lst:bind-deadgrep-commands][setup =deadgrep= commands]] binds
|
||
=deadgrep= globally to {{{kbd(M-s d)}}} and =deadgrep-edit-mode= locally to
|
||
{{{kbd(C-c C-w)}}}.
|
||
|
||
#+caption[Bind =deadgrep= commands]:
|
||
#+caption: Bind =deadgrep= commands.
|
||
#+name: lst:bind-deadgrep-commands
|
||
#+begin_src emacs-lisp -n :results silent
|
||
(when (and (ensure-package-installation 'deadgrep)
|
||
(fboundp 'deadgrep))
|
||
(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)))
|
||
#+end_src
|
||
|
||
** [[https://www.genivia.com/get-ugrep.html][Ugrep]]
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: sec:ugrep
|
||
:END:
|
||
|
||
[[https://www.genivia.com/get-ugrep.html][Ugrep]] is an ultra fast grep with interactive query UI and fuzzy search. The
|
||
pages [[https://github.com/Genivia/ugrep#emacs][Using ugrep within Emacs]] and [[https://manueluberti.eu/emacs/2022/08/07/emacs-ugrep/][Ugrep in Emacs]] show how to make Emacs use it
|
||
with [[info:emacs#Grep Searching][lgrep]] and [[info:emacs#Xref][xref]]. Listing [[lst:set-grep+xref-options][set =grep= and =xref= options]] shows my
|
||
implementation of the suggestions on those pages.
|
||
|
||
#+caption[Set =grep= and =xref= options to use =ugrep= when available]:
|
||
#+caption: Set =grep= and =xref= options to use =ugrep= when available.
|
||
#+name: lst:set-grep+xref-options
|
||
#+begin_src emacs-lisp -n :results silent
|
||
(when (executable-find "ugrep")
|
||
(with-eval-after-load 'grep
|
||
(setopt grep-template (string-join '("ugrep"
|
||
"--color=always"
|
||
"--decompress"
|
||
"--ignore-binary"
|
||
"--ignore-case"
|
||
"--include=\"<F>\""
|
||
"--line-number"
|
||
"--null"
|
||
"--recursive"
|
||
"--regexp=<R>")
|
||
" ")))
|
||
(with-eval-after-load 'xref
|
||
(setopt
|
||
xref-search-program 'ugrep
|
||
xref-search-program-alist
|
||
`((grep . "xargs -0 grep <C> --null -snHE -e <R>")
|
||
(ripgrep . ,(concat "xargs -0 rg <C> --null -nH"
|
||
" --no-heading --no-messages -g '!*/' -e <R>"))
|
||
(ugrep . "xargs -0 ugrep <C> --null -ns -e <R>")))))
|
||
#+end_src
|
||
|
||
** [[https://github.com/mattiase/xr#readme][XR - Emacs regexp parser and analyzer]]
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: sec:ensure-xr-installation
|
||
:END:
|
||
|
||
[[https://github.com/mattiase/xr#readme][XR]] converts Emacs regular expressions to the structured =rx= form, thus being an
|
||
inverse of =rx=. It can also find mistakes and questionable constructs inside
|
||
regexp strings.
|
||
|
||
#+caption[Ensure =xr= installation]:
|
||
#+caption: Ensure =xr= installation.
|
||
#+name: lst:ensure-xr-installation
|
||
#+begin_src emacs-lisp -n :results silent
|
||
(when (ensure-package-installation 'xr))
|
||
#+end_src
|
||
|
||
* [[info:emacs#Version Control][Version Control (info)]]
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: sec:version-control
|
||
:END:
|
||
|
||
** [[info:ediff#Top][Ediff (info)]]
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: sec:ediff
|
||
:END:
|
||
|
||
Video links to complete the [[info:ediff#Top][ediff (info)]] manual are:
|
||
1. [[https://www.youtube.com/watch?v=6ZWp05OW1c0][Emergency Emacs]].
|
||
2. [[https://protesilaos.com/codelog/2020-04-10-emacs-smerge-ediff/][Use =smerge= and =ediff= to resolve file conflicts]].
|
||
Listing [[lst:setup-ediff]] configures =ediff= to display all its buffers in a
|
||
single frame and to make all text visible prior to ediffing Org buffers.
|
||
|
||
#+caption[Setup =ediff=]:
|
||
#+caption: Setup =ediff=.
|
||
#+name: lst:setup-ediff
|
||
#+begin_src emacs-lisp -n :results silent
|
||
(with-eval-after-load 'emacs
|
||
(setopt ediff-merge-split-window-function #'split-window-horizontally
|
||
ediff-split-window-function #'split-window-horizontally
|
||
ediff-window-setup-function #'ediff-setup-windows-plain))
|
||
(with-eval-after-load 'org
|
||
;; https://github.com/oantolin/emacs-config#readme
|
||
(defun ediff-with-org-show-all ()
|
||
"Expand all headings prior to ediffing org buffers."
|
||
(add-hook 'ediff-prepare-buffer-hook #'org-fold-show-all nil t))
|
||
|
||
(add-hook 'org-mode-hook #'ediff-with-org-show-all))
|
||
#+end_src
|
||
|
||
** [[https://git-scm.com/book/en/v2][Git]]
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: sec:git
|
||
:END:
|
||
|
||
[[https://www.theserverside.com/blog/Coffee-Talk-Java-News-Stories-and-Opinions/git-push-new-branch-remote-github-gitlab-upstream-example][How to push new branches to remote repositories on Github or Gitlab]] explains how
|
||
to do this from the command line. Listing
|
||
[[lst:forking-and-branching-remote-repositories]] shows an example work flow of
|
||
command line =git= and =nano= invocations. I did yet not figure out how to do
|
||
this by means of [[info:magit#Top][Magit (info)]]: I still have to understand [[info:magit#Pushing][Magit pushing (info)]]
|
||
and to figure out how to use src_emacs-lisp{(magit-push-current-to-pushremote)}
|
||
or src_emacs-lisp{(magit-push-current-to-upstream)}.
|
||
|
||
#+caption[Forking and branching on Github or Gitlab]:
|
||
#+caption: Forking and branching on Github or Gitlab.
|
||
#+name: lst:forking-and-branching-remote-repositories
|
||
#+begin_src shell -n :noeval
|
||
# Clone the repository
|
||
git clone git@github.com:gav451/engrave-faces engrave-faces-fork
|
||
cd engrave-face-fork
|
||
# Handle 1st branch:
|
||
# - Create and switch to a new branch with either:
|
||
git switch -c latex-engrave-symbolic-color-names
|
||
# or: git checkout -b latex-engrave-symbolic-color-names
|
||
# - Use "--set-upstream origin" as a 1st-time branch push:
|
||
git push --set-upstream origin latex-engrave-symbolic-color-names
|
||
# - Create, add, and commit, and push changes in this branch:
|
||
nano engrave-faces-latex.el
|
||
git add engrave-faces-latex.el
|
||
git commit -m 'Commit latex-engrave-dont-symbolic-color-names changes'
|
||
git push
|
||
# - Switch to master branch:
|
||
git checkout master
|
||
# Handle 2nd branch:
|
||
# - Create and switch to a new branch with either:
|
||
git switch -c latex-engrave-dont-wrap-white-space
|
||
# - or: git checkout -b latex-engrave-dont-wrap-white-space
|
||
# - Use "--set-upstream origin" as a 1st-time branch push:
|
||
git push --set-upstream origin latex-engrave-dont-wrap-white-space
|
||
# - Create, add, commit, and push changes in this branch:
|
||
nano engrave-faces-latex.el
|
||
git add engrave-faces-latex.el
|
||
git commit -m 'Commit latex-engrave-dont-wrap-white-space changes'
|
||
git push
|
||
# - Switch to master branch:
|
||
git checkout master
|
||
#+end_src
|
||
|
||
*** [[https://www.theserverside.com/blog/Coffee-Talk-Java-News-Stories-and-Opinions/git-push-new-branch-remote-github-gitlab-upstream-example][List all Git branches]]
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: sec:list-all-git-branches
|
||
:END:
|
||
|
||
#+caption[List all =git= branches]:
|
||
#+caption: List all =git= branches.
|
||
#+name: lst:list-all-git-branches
|
||
#+begin_src shell -n :exports both :results verbatim
|
||
git -C ~/VCS/engrave-faces-fork branch -a
|
||
#+end_src
|
||
|
||
#+RESULTS: lst:list-all-git-branches
|
||
: * gav
|
||
: latex-engrave-dont-wrap-white-space
|
||
: latex-engrave-symbolic-color-names
|
||
: master
|
||
: remotes/origin/HEAD -> origin/master
|
||
: remotes/origin/gav
|
||
: remotes/origin/latex-engrave-dont-wrap-white-space
|
||
: remotes/origin/latex-engrave-symbolic-color-names
|
||
: remotes/origin/master
|
||
|
||
*** [[info:magit#Top][Magit (info)]]
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: sec:magit
|
||
:END:
|
||
|
||
[[info:magit#Top][Magit (info)]] is an Emacs interface to the version control system [[https://git-scm.com/book/en/v2][Git]] and the
|
||
snippet [[lst:ensure-magit-installation][below]] ensures its installation.
|
||
|
||
#+caption[Ensure =Magit= installation]:
|
||
#+caption: Ensure =Magit= installation
|
||
#+name: lst:ensure-magit-installation
|
||
#+begin_src emacs-lisp -n :results silent
|
||
(ensure-package-installation 'magit)
|
||
#+end_src
|
||
|
||
* Reading
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: sec:reading
|
||
:END:
|
||
|
||
** Reading [[https://en.wikipedia.org/wiki/DjVu][DjVu]] files
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: sec:reading-djvu-files
|
||
:END:
|
||
|
||
This setup relies on [[info:emacs#Document View][Document View (info)]] to facilitate reading [[https://en.wikipedia.org/wiki/DjVu][DjVu]] files.
|
||
Reading the code shown by src_emacs-lisp{(find-function
|
||
'doc-view-djvu->tiff-converter-ddjvu)} shows that reading [[https://en.wikipedia.org/wiki/DjVu][DjVu]] files requires
|
||
the command line [[https://en.wikipedia.org/wiki/DjVu][DjVu]] decoder =ddjvu= from the [[http://djvu.sourceforge.net/][DjVuLibre]] utilities.
|
||
|
||
** Reading EPUB files
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: sec:reading-epub-files
|
||
:END:
|
||
|
||
The package [[https://depp.brause.cc/nov.el/][nov.el]] provides a major mode for reading EPUB files in Emacs.
|
||
Listing [[lst:configure-nov]] configures [[https://depp.brause.cc/nov.el/][nov.el]].
|
||
|
||
#+caption[Configure =nov=]:
|
||
#+caption: Configure =nov=.
|
||
#+name: lst:configure-nov
|
||
#+begin_src emacs-lisp -n :results silent
|
||
(when (and (ensure-package-installation 'nov)
|
||
(fboundp 'nov-mode))
|
||
(add-to-list 'auto-mode-alist `(,(rx ".epub" eos) . nov-mode)))
|
||
#+end_src
|
||
|
||
** Reading PDF files
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: sec:reading-pdf-files
|
||
:END:
|
||
|
||
The [[https://github.com/vedang/pdf-tools][pdf-tools]] package exploits the [[https://github.com/freedesktop/poppler][poppler]] library to render and to let you
|
||
annotate [[https://en.wikipedia.org/wiki/PDF][PDF]] files. It also exploits the [[https://wiki.contextgarden.net/SyncTeX][SyncTeX]] library to link anchors in [[https://en.wikipedia.org/wiki/PDF][PDF]]
|
||
files produced with LaTeX to the original LaTeX sources. The [[https://github.com/nicolaisingh/saveplace-pdf-view#readme][saveplace-pdf-view]]
|
||
package saves =pdf-view= and =doc-view= places.
|
||
|
||
In order to use [[https://github.com/vedang/pdf-tools][pdf-tools]], you have to type {{{kbd(M-x pdf-tools-install)}}} after
|
||
installation of [[https://github.com/vedang/pdf-tools][pdf-tools]] from [[https://melpa.org/][MELPA]] or after each update of [[https://github.com/freedesktop/poppler][poppler]] to build or
|
||
rebuild the =epdfinfo= executable that serves the [[https://en.wikipedia.org/wiki/PDF][PDF]] files to Emacs.
|
||
|
||
#+caption[Setup =pdf-tools=]:
|
||
#+caption: Setup =pdf-tools=.
|
||
#+name: lst:setup-pdf-tools
|
||
#+begin_src emacs-lisp -n :results silent
|
||
(when (and (ensure-package-installation 'pdf-tools '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
|
||
(setopt pdf-view-display-size 'fit-page
|
||
pdf-view-use-scaling (eq system-type 'darwin))
|
||
(require 'saveplace-pdf-view nil 'noerror)))
|
||
#+end_src
|
||
|
||
* Writing
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: sec:writing
|
||
:END:
|
||
|
||
** Writing LaTeX files
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: sec:writing-latex-files
|
||
:END:
|
||
|
||
Loading =tex.el= immediately instead of lazily ensures proper initialization of
|
||
[[https://en.wikipedia.org/wiki/AUCTeX][AUCTeX]]. In particular, the ~TeX-master~ safe local variable in the =tex.el=
|
||
elisp library file has no autoload cookie. Without prior loading of =tex.el=,
|
||
Emacs will complain that ~TeX-master~ is no safe local variable in case it reads
|
||
a LaTeX file that sets ~TeX-master~. The list below summarizes the main [[https://en.wikipedia.org/wiki/AUCTeX][AUCTeX]]
|
||
configuration objectives:
|
||
1. Listing [[lst:set-tex-options]] ensures installation of [[https://en.wikipedia.org/wiki/AUCTeX][AUCTeX]] and initializes
|
||
[[https://en.wikipedia.org/wiki/AUCTeX][AUCTeX]] properly for =LuaTeX= or =LuaLaTeX=.
|
||
2. Listing [[lst:set-tex-options]] also enables [[https://git.savannah.gnu.org/cgit/auctex.git/patch/?id=f464242eab092e610dda6b654e6fd197ef649d3eAA][indenting between square brackets]]
|
||
which is a recent feature.
|
||
3. Listing [[lst:update-lualatex-opentype-font-name-database]] defines a function to
|
||
update the =OpenType Font= name database for =LuaLaTeX= when the output of =LuaLaTeX= shows missing fonts.
|
||
4. Listing [[lst:set-bibtex-options]] configures the Emacs =bibtex= library to use the
|
||
LaTeX =BiBTeX= dialect for backwards compatibility.
|
||
5. Listing [[lst:set-font-latex-options]] disables font scaling of section titles.
|
||
6. Listing [[lst:set-latex-options]] configures =latex= for a full featured =LaTeX-section-command=.
|
||
|
||
#+caption[Ensure =AUCTeX= installation and set =TeX= option]:
|
||
#+caption: Ensure =AUCTeX= installation and set =TeX= option.
|
||
#+name: lst:set-tex-options
|
||
#+begin_src emacs-lisp -n :results silent
|
||
(when (ensure-package-installation 'auctex)
|
||
;; Use `require' to make `TeX-master' a safe local variable.
|
||
(when (require 'tex nil 'noerror)
|
||
(setopt
|
||
TeX-auto-save t
|
||
TeX-engine 'luatex
|
||
TeX-indent-close-delimiters "]"
|
||
TeX-indent-open-delimiters "["
|
||
TeX-install-font-lock #'font-latex-setup
|
||
TeX-parse-self t
|
||
;; Disable `TeX-electric-math' to prevent collisions with `smartparens'.
|
||
TeX-electric-math nil)))
|
||
#+end_src
|
||
|
||
#+caption[Update the =LuaLaTeX OpenType Font= name database]:
|
||
#+caption: Update the =LuaLaTeX= =OpenType Font= name database.
|
||
#+name: lst:update-lualatex-opentype-font-name-database
|
||
#+begin_src emacs-lisp -n :results silent
|
||
(with-eval-after-load 'emacs
|
||
(defun update-lualatex-opentype-font-name-database ()
|
||
"Update the \"OpenType Font\" name database for \"LuaLaTeX\"."
|
||
(interactive)
|
||
(cl-destructuring-bind (exit-code output)
|
||
(shell-command-with-exit-code
|
||
"luaotfload-tool" "-vv" "--update" "--force")
|
||
(if (= 0 exit-code)
|
||
(message "%s" (string-trim output))
|
||
(error "%s" (string-trim output))))))
|
||
#+end_src
|
||
|
||
#+caption[Set =bibtex= options]:
|
||
#+caption: Set =bibtex= options.
|
||
#+name: lst:set-bibtex-options
|
||
#+begin_src emacs-lisp -n :results silent
|
||
(with-eval-after-load 'bibtex
|
||
(setopt bibtex-dialect 'BibTeX))
|
||
#+end_src
|
||
|
||
#+caption[Set =font-latex= options]:
|
||
#+caption: Set =font-latex= options.
|
||
#+name: lst:set-font-latex-options
|
||
#+begin_src emacs-lisp -n :results silent
|
||
(with-eval-after-load 'font-latex
|
||
(setopt font-latex-fontify-sectioning 1.0))
|
||
#+end_src
|
||
|
||
#+caption[Set =latex= options]:
|
||
#+caption: Set =latex= options.
|
||
#+name: lst:set-latex-options
|
||
#+begin_src emacs-lisp -n :results silent
|
||
(with-eval-after-load 'latex
|
||
(setopt LaTeX-electric-left-right-brace t
|
||
LaTeX-section-hook '(LaTeX-section-heading
|
||
LaTeX-section-title
|
||
LaTeX-section-toc
|
||
LaTeX-section-section
|
||
LaTeX-section-label)))
|
||
#+end_src
|
||
|
||
*** [[https://gitlab.com/matsievskiysv/math-preview/-/blob/master/README.md][Math-preview]]
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: sec:math-preview
|
||
:END:
|
||
|
||
[[https://gitlab.com/matsievskiysv/math-preview/-/blob/master/README.md][Math-preview]] uses [[https://docs.mathjax.org/en/latest/][Mathjax-3]] to display [[https://www.latex-project.org/][LaTeX]], [[https://www.w3.org/Math/][MathML]],and [[http://asciimath.org/][asciimath]] in Emacs
|
||
buffers with help of an external [[https://nodejs.org][node.js]] program.
|
||
|
||
#+caption[Setup =math-preview=]:
|
||
#+caption: Setup =math-preview=.
|
||
#+name: lst:setup-math-preview
|
||
#+begin_src emacs-lisp -n :results silent
|
||
(when (ensure-package-installation 'math-preview)
|
||
(with-eval-after-load 'math-preview
|
||
;; https://docs.mathjax.org/en/latest/input/tex/extensions/physics.html
|
||
(let ((physics (split-string "
|
||
abs absolutevalue acomm acos acosecant acosine acot acotangent
|
||
acsc admat anticommutator antidiagonalmatrix arccos arccosecant
|
||
arccosine arccot arccotangent arccsc arcsec arcsecant arcsin
|
||
arcsine arctan arctangent asec asecant asin asine atan atangent
|
||
bmqty bqty Bqty bra braket comm commutator cos cosecant cosh
|
||
cosine cot cotangent coth cp cross crossproduct csc csch curl dd
|
||
derivative det determinant diagonalmatrix diffd differential div
|
||
divergence dmat dotproduct dv dyad erf ev eval evaluated exp
|
||
expectationvalue exponential expval fderivative fdv flatfrac
|
||
functionalderivative grad gradient gradientnabla hypcosecant
|
||
hypcosine hypcotangent hypsecant hypsine hyptangent
|
||
identitymatrix Im imaginary imat innerproduct ip ket ketbra
|
||
laplacian ln log logarithm matrixdeterminant matrixel
|
||
matrixelement matrixquantity mdet mel mqty naturallogarithm norm
|
||
op order outerproduct partialderivative paulimatrix pb
|
||
pderivative pdv pmat pmqty Pmqty poissonbracket pqty Pr
|
||
principalvalue Probability pv PV qall qand qas qassume qc qcc
|
||
qcomma qelse qeven qfor qgiven qif qin qinteger qlet qodd qor
|
||
qotherwise qq qqtext qsince qthen qty quantity qunless qusing
|
||
rank Re real Res Residue sbmqty sec secant sech sin sine sinh
|
||
smallmatrixquantity smdet smqty spmqty sPmqty svmqty tan tangent
|
||
tanh tr Tr trace Trace va var variation vb vdot vectorarrow
|
||
vectorbold vectorunit vmqty vnabla vqty vu xmat xmatrix
|
||
zeromatrix zmat")))
|
||
(cl-pushnew `("physics" . ,physics)
|
||
math-preview-tex-packages-autoload-packages))
|
||
(setq math-preview-raise 0.5
|
||
math-preview-scale 1
|
||
math-preview-tex-default-packages '("autoload" "ams" "physics"))
|
||
(let ((command (executable-find "~/node_modules/.bin/math-preview")))
|
||
(if command
|
||
(setq math-preview-command command)
|
||
;; https://stackoverflow.com/a/17509764 answers:
|
||
;; How to install an npm package from github directly?
|
||
(unless (file-directory-p "~/node_modules")
|
||
(make-directory "~/node_modules"))
|
||
(cl-destructuring-bind (exit-code output)
|
||
(shell-command-with-exit-code
|
||
"npm" "install"
|
||
"git+https://gitlab.com/matsievskiysv/math-preview.git")
|
||
(if (= 0 exit-code)
|
||
(message "%s" (string-trim output))
|
||
(error "%s" (string-trim output))))))))
|
||
#+end_src
|
||
|
||
*** TODO Improve the AUCTeX configuration slowly
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: sec:setup-auctex-slowly
|
||
:END:
|
||
|
||
[[https://github.com/thisirs/dotemacs/blob/master/lisp/init-auctex.el][AUCTeX setup of an experienced user]]
|
||
|
||
** Writing [[https://www.markdownguide.org/][Markdown]] files
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: sec:writing-markdown-files
|
||
:END:
|
||
|
||
#+caption[Configure =markdown-mode=]:
|
||
#+caption: Configure =markdown-mode=.
|
||
#+name: lst:configure-markdown-mode
|
||
#+begin_src emacs-lisp -n :results silent
|
||
(when (ensure-package-installation 'markdown-mode))
|
||
#+end_src
|
||
|
||
** Writing [[info:org#Top][Org (info)]] files
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: sec:writing-org-files
|
||
:END:
|
||
|
||
The [[https://org-babel.readthedocs.io/en/latest/][Org Babel reference card]] complements section [[info:org#Working with Source Code][Working with Source Code (info)]]
|
||
of the [[info:org#Top][Org (info)]] manual.
|
||
|
||
*** [[info:org#Activation][Org activation (info)]]
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: sec:activate-org
|
||
:END:
|
||
|
||
#+caption[Bind =Org= commands globally]:
|
||
#+caption: Bind =Org= commands globally.
|
||
#+name: lst:bind-org-commands
|
||
#+begin_src emacs-lisp -n :results silent
|
||
(with-eval-after-load 'emacs
|
||
(global-set-key (kbd "C-c a") #'org-agenda)
|
||
(global-set-key (kbd "C-c c") #'org-capture)
|
||
(with-eval-after-load 'ol
|
||
(global-set-key (kbd "C-c l") #'org-store-link)
|
||
(global-set-key (kbd "C-c C-l") #'org-insert-link-global)))
|
||
#+end_src
|
||
|
||
*** Setup Org
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: sec:setup-org
|
||
:END:
|
||
|
||
I have split the initial [[https://orgmode.org/][Org-mode]] setup over several listings. Here, follows a
|
||
list detailing and motivating each listing:
|
||
1. Listing [[lst:set-org-options]] handles basic [[https://orgmode.org/][Org-mode]] options.
|
||
2. Listing [[lst:setup-org-babel]] handles basic [[https://orgmode.org/][Org-mode]] options specific to
|
||
[[info:org#Working with Source Code][work with source code (info)]].
|
||
3. Listing [[lst:set-org-link-options]] handles [[https://orgmode.org/][Org-mode]] options specific to
|
||
[[info:org#Hyperlinks][hyperlinks (info)]].
|
||
4. Listing [[lst:setup-org-mode-map]] extends the =org-mode-map= with useful
|
||
key-bindings.
|
||
5. Listing [[lst:setup-org-src]] facilitates [[info:org#Editing Source Code][editing Python source code blocks]],
|
||
and it provides a function to remove the indentation of all =org-src-mode=
|
||
blocks without =-i= switch.
|
||
6. Listing [[lst:setup-ob-python]] allows to pretty-print Python session source
|
||
block values with [[https://github.com/psf/black#readme][black]] instead of [[https://docs.python.org/3/library/pprint.html][pprint]].
|
||
7. Listing [[lst:set-org-export-options]] selects the =non-intrusive= expert user
|
||
interface for export dispatching.
|
||
8. Listing [[lst:setup-org-for-lualatex-export]] and
|
||
[[lst:set-ox-latex-options-for-lualatex-export]] configure [[https://orgmode.org/][Org-mode]] to generate
|
||
output for the LuaLaTeX compiler.
|
||
9. Listing [[lst:setup-org-latex-classes]] defines [[info:org#LaTeXspecificexportsettings][org-latex-classes (info)]] for
|
||
backward compatibility. See table [[tab:org-latex-class-tag-placeholder]] and
|
||
type {{{kbd(C-hv org-latex-classes)}}} for an explanation of the code in
|
||
listing [[lst:setup-org-latex-classes]].
|
||
|
||
#+caption[Set basic =Org= options]:
|
||
#+caption: Set basic =Org= options.
|
||
#+name: lst:set-org-options
|
||
#+begin_src emacs-lisp -n :results silent
|
||
(with-eval-after-load 'org
|
||
(setopt
|
||
org-babel-load-languages
|
||
`((C . t)
|
||
(calc . t)
|
||
(dot . ,(fboundp 'grapviz-dot-mode))
|
||
(emacs-lisp . t)
|
||
;; (eshell . t) ; bug
|
||
(fortran . t)
|
||
(gnuplot . ,(fboundp 'gnuplot-mode))
|
||
(js . t)
|
||
(latex . t)
|
||
(lisp . t)
|
||
;; (lua . ,(fboundp 'lua-mode)) ; bug
|
||
(maxima . ,(fboundp 'maxima-mode))
|
||
(org . t)
|
||
(perl . t)
|
||
;; Postpone Python activation to prevent calling still unbound functions.
|
||
(python . nil)
|
||
(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 -n -i :session")
|
||
("v" . "verse"))))
|
||
#+end_src
|
||
|
||
#+caption[Setup =org-babel=]:
|
||
#+caption: Setup =org-babel=.
|
||
#+name: lst:setup-org-babel
|
||
#+begin_src emacs-lisp -n :results silent
|
||
(with-eval-after-load 'ob-core
|
||
(setopt org-confirm-babel-evaluate nil))
|
||
|
||
(with-eval-after-load 'ob-latex
|
||
(setopt
|
||
org-babel-latex-preamble (lambda (_)
|
||
"\\documentclass[preview]{standalone}\n")
|
||
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
|
||
(setopt org-babel-lisp-eval-fn #'sly-eval)))
|
||
|
||
(with-eval-after-load 'emacs
|
||
(defun set-org-babel-language-activity (lang active)
|
||
"Set the `org-babel' activity of language LANG to ACTIVE.
|
||
|
||
Let `org-babel-do-load-languages' load ob-LANG when ACTIVE is t or
|
||
unbind the `org-babel' interface functions when ACTIVE is nil."
|
||
(if (locate-library (format "ob-%s" lang))
|
||
(or (org-babel-do-load-languages
|
||
'org-babel-load-languages
|
||
(cons (cons lang active)
|
||
(assq-delete-all
|
||
lang (default-toplevel-value 'org-babel-load-languages))))
|
||
org-babel-load-languages)
|
||
(warn "Can't locate `ob-%s', hence don't set `%s' activity" lang lang)))
|
||
|
||
(defun activate-org-babel-language (lang)
|
||
"Activate LANG for `org-babel'."
|
||
(interactive "SActivate language: ")
|
||
(if (set-org-babel-language-activity lang t)
|
||
(message "Did activate `%s' for `org-babel'" lang)
|
||
(message "Can't activate `%s' for `org-babel'" lang)))
|
||
|
||
(defun deactivate-org-babel-language (lang)
|
||
"Activate LANG for `org-babel'."
|
||
(interactive "SDeactivate language: ")
|
||
(if (set-org-babel-language-activity lang nil)
|
||
(message "Did deactivate `%s' for `org-babel'" lang)
|
||
(message "Can't deactivate `%s' for `org-babel'" lang)))
|
||
|
||
;; Work around a "(defcustom org-babel-load-languages)" call bug.
|
||
(set-org-babel-language-activity 'eshell t)
|
||
(set-org-babel-language-activity 'lua (fboundp 'lua-mode)))
|
||
#+end_src
|
||
|
||
#+caption[Set =org-link= options]:
|
||
#+caption: Set =org-link= options.
|
||
#+name: lst:set-org-link-options
|
||
#+begin_src emacs-lisp -n :results silent
|
||
(with-eval-after-load 'ol
|
||
(setopt org-link-file-path-type 'relative))
|
||
#+end_src
|
||
|
||
#+caption[Setup =org-mode-map=]:
|
||
#+caption: Setup =org-mode-map=.
|
||
#+name: lst:setup-org-mode-map
|
||
#+begin_src emacs-lisp -n :results silent
|
||
(with-eval-after-load 'emacs
|
||
;; From: "Nicolas Richard" <theonewiththeevillook@yahoo.fr>
|
||
;; Date: Fri, 08 Mar 2013 16:23:02 +0100 [thread overview]
|
||
;; Message-ID: <87vc913oh5.fsf@yahoo.fr> (raw)
|
||
(defun org-electric-dollar ()
|
||
"When called once, insert \\(\\) and leave point in between.
|
||
When called twice, replace the previously inserted \\(\\) by one $."
|
||
(interactive)
|
||
(if (and (looking-at "\\\\)") (looking-back "\\\\(" (- (point) 2)))
|
||
(progn (delete-char 2)
|
||
(delete-char -2)
|
||
(insert "$"))
|
||
(insert "\\(\\)")
|
||
(backward-char 2)))
|
||
|
||
(defun org-active-current-time-stamp ()
|
||
"Insert an active date-time stamp of the current time."
|
||
(interactive)
|
||
(if (fboundp 'org-insert-time-stamp)
|
||
(org-insert-time-stamp (current-time) 'with-hm)
|
||
(let ((stamp (format-time-string "<%Y-%m-%d %a %H:%M>" (current-time))))
|
||
(insert stamp)
|
||
stamp)))
|
||
|
||
(defun org-inactive-current-time-stamp ()
|
||
"Insert an inactive date-time stamp of the current time."
|
||
(interactive)
|
||
(if (fboundp 'org-insert-time-stamp)
|
||
(org-insert-time-stamp (current-time) 'with-hm 'inactive)
|
||
(let ((stamp (format-time-string "[%Y-%m-%d %a %H:%M]" (current-time))))
|
||
(insert stamp)
|
||
stamp)))
|
||
|
||
(with-eval-after-load 'org
|
||
(setopt org-return-follows-link t)
|
||
(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)))
|
||
#+end_src
|
||
|
||
#+caption[Setup =org-src=]:
|
||
#+caption: Setup =org-src=.
|
||
#+name: lst:setup-org-src
|
||
#+begin_src emacs-lisp -n :results silent
|
||
(with-eval-after-load 'emacs
|
||
(with-eval-after-load 'org-src
|
||
(setopt org-src-preserve-indentation t))
|
||
|
||
(declare-function org-babel-map-src-blocks "ext:ob-core" (file &rest body))
|
||
(declare-function org-do-remove-indentation "ext:org-macs" (n skip-fl))
|
||
|
||
(defun zero-all-org-src-blocks-indentation ()
|
||
"Remove the indentation of all `org-src-mode' blocks without `-i' switch."
|
||
(interactive)
|
||
(when (derived-mode-p 'org-mode)
|
||
(save-excursion
|
||
(org-babel-map-src-blocks
|
||
nil
|
||
(unless (or (string-match "-i " switches)
|
||
(string-match (rx (or bos bol) graph) body))
|
||
(when-let ((new (with-temp-buffer
|
||
(insert body)
|
||
(org-do-remove-indentation)
|
||
(buffer-string))))
|
||
(goto-char beg-body)
|
||
(delete-region beg-body end-body)
|
||
(insert new))))))))
|
||
#+end_src
|
||
|
||
#+caption[Setup =ob-python=]:
|
||
#+caption: Setup =ob-python=.
|
||
#+name: lst:setup-ob-python
|
||
#+begin_src emacs-lisp -n :results silent
|
||
(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")))
|
||
|
||
(advice-add 'org-babel-python-format-session-value
|
||
:override #'org-babel-python-format-session-value-override))
|
||
#+end_src
|
||
|
||
#+caption[Set =org-export= options]:
|
||
#+caption: Set =org-export= options.
|
||
#+name: lst:set-org-export-options
|
||
#+begin_src emacs-lisp -n :results silent
|
||
(with-eval-after-load 'ox
|
||
(setopt org-export-dispatch-use-expert-ui t))
|
||
#+end_src
|
||
|
||
#+caption[Setup =org= for export to LuaLaTeX]:
|
||
#+caption: Setup =org= for export to LuaLaTeX.
|
||
#+name: lst:setup-org-for-lualatex-export
|
||
#+begin_src emacs-lisp -n :results silent
|
||
(with-eval-after-load 'org
|
||
;; https://list.orgmode.org/87o84fd4oi.fsf@posteo.net/
|
||
(add-to-list
|
||
'org-preview-latex-process-alist
|
||
'(luamagick
|
||
:programs ("lualatex" "convert")
|
||
:description "pdf > png"
|
||
:message "you need to install lualatex and imagemagick."
|
||
:image-input-type "pdf"
|
||
:image-output-type "png"
|
||
:image-size-adjust (1.0 . 1.0)
|
||
:post-clean nil
|
||
:latex-header nil
|
||
:latex-compiler ("lualatex -interaction nonstopmode -output-directory %o %f")
|
||
:image-converter ("convert -density %D -trim -antialias %f -quality 100 %O")))
|
||
(setopt
|
||
org-preview-latex-default-process 'luamagick
|
||
org-latex-default-packages-alist '(("" "fontspec" t ("lualatex"))
|
||
("AUTO" "inputenc" t ("pdflatex"))
|
||
("T1" "fontenc" t ("pdflatex"))
|
||
("" "graphicx" t)
|
||
("" "longtable" nil)
|
||
("" "wrapfig" nil)
|
||
("" "rotating" nil)
|
||
("normalem" "ulem" t)
|
||
("" "amsmath" t)
|
||
("" "amssymb" t)
|
||
("" "capt-of" nil)
|
||
("" "hyperref" nil))))
|
||
#+end_src
|
||
|
||
#+caption[Set =ox-latex= options for export to LuaLaTeX]:
|
||
#+caption: Set =ox-latex= options for export to LuaLaTeX.
|
||
#+name: lst:set-ox-latex-options-for-lualatex-export
|
||
#+begin_src emacs-lisp -n :results silent
|
||
(with-eval-after-load 'ox-latex
|
||
(setopt
|
||
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-src-block-backend '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)
|
||
;; https://tecosaur.github.io/emacs-config/#compiling
|
||
org-latex-pdf-process (list (concat "latexmk -f -pdf -%latex"
|
||
" -interaction=nonstopmode"
|
||
" -shell-escape -outdir=%o %f"))
|
||
org-latex-prefer-user-labels t
|
||
org-latex-subtitle-separate t))
|
||
#+end_src
|
||
|
||
#+caption[Setup =org-latex-classes= for backwards compatibility]:
|
||
#+caption: Setup =org-latex-classes= for backwards compatibility.
|
||
#+name: lst:setup-org-latex-classes
|
||
#+begin_src emacs-lisp -n :results silent
|
||
(with-eval-after-load 'ox-latex
|
||
(mapc (function (lambda (element)
|
||
(add-to-list 'org-latex-classes element)))
|
||
(nreverse
|
||
(quote (("elsarticle-1+2+3" ; Elsevier journals
|
||
"\\documentclass{elsarticle}
|
||
[NO-DEFAULT-PACKAGES]
|
||
[PACKAGES]
|
||
[EXTRA]"
|
||
("\\section{%s}" . "\\section*{%s}")
|
||
("\\subsection{%s}" . "\\subsection*{%s}")
|
||
("\\subsubsection{%s}" . "\\subsubsection*{%s}")
|
||
("\\paragraph{%s}" . "\\paragraph*{%s}")
|
||
("\\subparagraph{%s}" . "\\subparagraph*{%s}"))
|
||
("article-1+2+3"
|
||
"\\documentclass{article}
|
||
[NO-DEFAULT-PACKAGES]
|
||
[PACKAGES]
|
||
[EXTRA]"
|
||
("\\section{%s}" . "\\section*{%s}")
|
||
("\\subsection{%s}" . "\\subsection*{%s}")
|
||
("\\subsubsection{%s}" . "\\subsubsection*{%s}")
|
||
("\\paragraph{%s}" . "\\paragraph*{%s}")
|
||
("\\subparagraph{%s}" . "\\subparagraph*{%s}"))
|
||
("report-1+2+3"
|
||
"\\documentclass[11pt]{report}
|
||
[NO-DEFAULT-PACKAGES]
|
||
[PACKAGES]
|
||
[EXTRA]"
|
||
("\\part{%s}" . "\\part*{%s}")
|
||
("\\chapter{%s}" . "\\chapter*{%s}")
|
||
("\\section{%s}" . "\\section*{%s}")
|
||
("\\subsection{%s}" . "\\subsection*{%s}")
|
||
("\\subsubsection{%s}" . "\\subsubsection*{%s}"))
|
||
("book-1+2+3"
|
||
"\\documentclass[11pt]{book}
|
||
[NO-DEFAULT-PACKAGES]
|
||
[PACKAGES]
|
||
[EXTRA]"
|
||
("\\part{%s}" . "\\part*{%s}")
|
||
("\\chapter{%s}" . "\\chapter*{%s}")
|
||
("\\section{%s}" . "\\section*{%s}")
|
||
("\\subsection{%s}" . "\\subsection*{%s}")
|
||
("\\subsubsection{%s}" . "\\subsubsection*{%s}")))))))
|
||
#+end_src
|
||
|
||
#+attr_latex: :booktabs yes :float table
|
||
#+caption[The relation tag-placeholder in listing [[lst:setup-org-latex-classes]]]:
|
||
#+caption: The relation tag-placeholder in listing [[lst:setup-org-latex-classes]].
|
||
#+name: tab:org-latex-class-tag-placeholder
|
||
| tag | placeholder |
|
||
|-----+-----------------------|
|
||
| +1 | [DEFAULT-PACKAGES] |
|
||
| +2 | [PACKAGES] |
|
||
| +3 | [EXTRA] |
|
||
| -1 | [NO-DEFAULT-PACKAGES] |
|
||
| -2 | [NO-PACKAGES] |
|
||
| -3 | [NO-EXTRA] |
|
||
|
||
*** [[https://github.com/bdarcus/citar][Citar: citing bibliography]] with [[https://orgmode.org/][Org Mode]]
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: sec:citing-bibliography
|
||
:END:
|
||
|
||
[[https://github.com/bdarcus/citar][Citar]] is a completing-read front-end to browse and act on BibTeX, BibLaTeX, as
|
||
well as CSL JSON bibliographic data with LaTeX, markdown, and org-cite editing
|
||
support. In combination with vertico, embark, marginalia, and consult, [[https://github.com/bdarcus/citar][Citar]]
|
||
provides quick filtering and selecting of bibliographic entries from the
|
||
minibuffer as well as the option to run different commands on those
|
||
selections. The article [[https://kristofferbalintona.me/posts/202206141852/][Citations in org-mode: Org-cite and Citar]] tries to walk
|
||
you from understanding the general context (bibliography producer, text
|
||
processor, text to product convertor) to an Emacs setup. Listing
|
||
[[lst:set-org-cite+citar-options]] shows my =org-cite=, =citar=, and =org= setup.
|
||
|
||
#+caption[Set =org-cite= and =citar= options]:
|
||
#+caption: Set =org-cite= and =citar= options.
|
||
#+name: lst:set-org-cite+citar-options
|
||
#+begin_src emacs-lisp -n
|
||
(with-eval-after-load 'oc
|
||
;; Org-9.5 needs the requirements, but Org-9.6 does not.
|
||
(when (version< (org-version) "9.6")
|
||
(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)))
|
||
|
||
(when (require 'citar nil 'noerror)
|
||
(setopt citar-bibliography '("~/VCS/research/refs.bib")
|
||
citar-library-file-extensions '("djvu" "pdf")
|
||
citar-library-paths '("~/VCS/research/papers/")))
|
||
|
||
(with-eval-after-load 'oc
|
||
(setopt org-cite-export-processors '((latex biblatex)
|
||
(t csl))
|
||
org-cite-global-bibliography '("~/VCS/research/refs.bib"))
|
||
|
||
(when (require 'citar nil 'noerror)
|
||
(setopt org-cite-activate-processor 'citar
|
||
org-cite-follow-processor 'citar
|
||
org-cite-insert-processor 'citar))))
|
||
#+end_src
|
||
|
||
Ref. [cite:@Schulte.MCSE.2011.41] shows that [[https://orgmode.org/][Org-mode]] is a simple, plain-text
|
||
markup language for hierarchical documents allowing intermingling of data, code,
|
||
and prose. It serves as an example link to to show how to use [[https://github.com/bdarcus/citar][Citar]] within this
|
||
setup, provided that =citar-bibliography= and =citar-library-paths= point to
|
||
valid directories and files. In an [[https://orgmode.org/][Org-mode]] buffer this setup allows to:
|
||
1. Insert one or more [[https://orgmode.org/][Org-mode]] cite links:
|
||
1. Type {{{kbd(C-c b)}}} or {{{kbd(M-x org-cite-insert)}}}.
|
||
2. Navigate to a bibliographic entry to insert:
|
||
1. Select it with {{{kbd(TAB)}}} to select more entries.
|
||
2. Select it with {{{kbd(RET)}}} to select the last entry.
|
||
2. View an electronic copy of an [[https://orgmode.org/][Org-mode]] cite link:
|
||
1. Move point to the [[https://orgmode.org/][Org-mode]] cite link.
|
||
2. Type {{{kbd(C-:)}}} or {{{kbd(M-x embark-dwim)}}}.
|
||
3. Navigate to the corresponding library file.
|
||
4. Select it.
|
||
3. Open the DOI of an [[https://orgmode.org/][Org-mode]] cite link:
|
||
1. Move point to the [[https://orgmode.org/][Org-mode]] cite link.
|
||
2. Type {{{kbd(C-:)}}} or {{{kbd(M-x embark-dwim)}}}.
|
||
3. Navigate to the corresponding link.
|
||
4. Select it.
|
||
|
||
*** TODO Compare bibtex and biblatex
|
||
1. [[https://www.economics.utoronto.ca/osborne/latex/BIBTEX.HTM][Using bibtex: a short guide]]
|
||
2. [[https://www.tug.org/texlive//devsrc/Master/texmf-dist/doc/latex/biblatex/biblatex.pdf][The biblatex package]]
|
||
3. [[https://github.com/yuchenlin/rebiber][Rebiber: A tool for normalizing bibtex with official info]]
|
||
4. [[https://github.com/josephwright/biblatex-phys][A biblatex implementation of the AIP and APS bibliography style]]
|
||
|
||
#+caption[Delete =Biber= cache when =Biber= fails to make a bibliography]:
|
||
#+caption: Delete =Biber= cache when =Biber= fails to make a bibliography.
|
||
#+name: lst:delete-biber-cache
|
||
#+begin_src emacs-lisp -n :results silent
|
||
(with-eval-after-load 'emacs
|
||
;; https://tex.stackexchange.com/a/579356 answers
|
||
;; "How to solve Biber exiting with error code 2 but no error messages?"
|
||
(defun biber-delete-cache ()
|
||
"Delete the `Biber' cache to get rid of `Biber' exit code 2."
|
||
(interactive)
|
||
(cl-destructuring-bind (exit-code output)
|
||
(shell-command-with-exit-code "rm" "-rf" "$(biber --cache)")
|
||
(if (= 0 exit-code)
|
||
(message "%s" (string-trim output))
|
||
(error "%s" (string-trim output))))))
|
||
#+end_src
|
||
|
||
*** [[https://github.com/tecosaur/engrave-faces#readme][Engrave Faces]]
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: sec:engrave-faces
|
||
:END:
|
||
|
||
This package has a front-end that processes fontified buffers and pipes its
|
||
output into one of three back-ends for export to LaTeX, HTML and text with ANSI
|
||
escape codes. It is an alternative to =minted= in case of LaTeX export or to
|
||
=htmlized= in case of HTML export.
|
||
|
||
The listings below work around one issue with spacing in the =pdf= output
|
||
resulting from use of the =engraved= source block back-end during =LaTeX=
|
||
export. They increase the quality of the export results of this [[file:README.org][README.org]] and
|
||
the user-friendliness of this facility to a level acceptable for my workflow:
|
||
1. The default Org export configuration for engraving listings produces =pdf=
|
||
output with a non-equidistant interline spacing in floating listings (those
|
||
with captions) and equidistant interline spacing in non-floating =tcolorbox=
|
||
environments (those without captions). I have traced the non-equidistant
|
||
interline spacing back to the =breakable= option of =Code= environments
|
||
inside =listing= environments. However, =tcolorbox= requires =breakable= for
|
||
listings that do not fit on a page. The filter =org-latex-engraved-source-block-filter= in listing
|
||
[[lst:org-latex-engraved-source-block-filter]] and the =org-latex-engraved-preamble= customization in listing
|
||
[[lst:smart-latex-engrave-org-source-blocks]] allow to engrave =org-mode= source
|
||
blocks to "floating unbreakable with caption" or "non-floating breakable
|
||
without caption" LaTeX environments. This is a partial work-around, but part
|
||
of my workflow.
|
||
2. Listing [[lst:ox-engraved-emacs-lisp-setup]] shows how to use this configuration.
|
||
|
||
#+caption[Engrave to floating unbreakable or non-floating breakable environments]:
|
||
#+caption: Define an =org-export= filter function to engrave =org-src-mode=
|
||
#+caption: blocks to floating unbreakable LaTeX environments or non-floating
|
||
#+caption: breakable LaTeX environments.
|
||
#+name: lst:org-latex-engraved-source-block-filter
|
||
#+begin_src emacs-lisp -n :results silent
|
||
(ensure-package-installation 'engrave-faces)
|
||
|
||
(defun org-latex-engraved-source-block-filter (data _backend _info)
|
||
"Replace \"Code\" with \"Breakable\" in non-floating environments.
|
||
|
||
Set `org-latex-engraved-preamble' to define a Breakable (non-floating)
|
||
environment and an unbreakable Code (floating) environment."
|
||
(unless (string-match "^\\\\DeclareTColorBox\\[\\]{Breakable}"
|
||
org-latex-engraved-preamble)
|
||
(user-error "`org-latex-engraved-preamble' defines no `Breakable' environment"))
|
||
(when (eq org-latex-src-block-backend 'engraved)
|
||
(let ((enter "^\\\\begin{Code}\n\\\\begin{Verbatim}")
|
||
(leave "^\\\\end{Verbatim}\n\\\\end{Code}"))
|
||
;; Transform only blocks matching the enter regexp at position 0.
|
||
;; Do not transform blocks that are listing environments.
|
||
(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)))))
|
||
#+end_src
|
||
|
||
#+caption[Smart LaTeX engraving of =org-src-mode= blocks]:
|
||
#+caption: Smart LaTeX engraving of =org-src-mode= blocks.
|
||
#+name: lst:smart-latex-engrave-org-source-blocks
|
||
#+begin_src emacs-lisp -n :results silent
|
||
(defun smart-latex-engrave-org-source-blocks ()
|
||
"Enable smart LaTeX engraving of `org-src-mode' blocks.
|
||
|
||
Sets backend and preamble locally to support floating unbreakable LaTeX
|
||
environments and non-floating breakable LaTeX environments by means of
|
||
`org-latex-engraved-source-block-filter'."
|
||
(interactive)
|
||
(when (require 'ox-latex nil 'noerror)
|
||
(setq-local org-latex-src-block-backend 'engraved
|
||
org-latex-engraved-preamble "\\usepackage{fvextra}
|
||
[FVEXTRA-SETUP]
|
||
|
||
% Make code and line numbers normalsize. Make line numbers grey.
|
||
\\renewcommand\\theFancyVerbLine{
|
||
\\normalsize\\color{black!40!white}\\arabic{FancyVerbLine}}
|
||
% In case engrave-faces-latex-gen-preamble has not been run.
|
||
\\usepackage{xcolor}
|
||
\\providecolor{EfD}{HTML}{f7f7f7}
|
||
\\providecolor{EFD}{HTML}{28292e}
|
||
% To use \\DeclareTColorBox from the tcolorbox package:
|
||
\\usepackage[breakable,xparse]{tcolorbox}
|
||
% Define a Breakable environment to fontify code outside floats.
|
||
\\DeclareTColorBox[]{Breakable}{o}{
|
||
colback=EfD, colframe=EFD, colupper=EFD,
|
||
fontupper=\\normalsize\\setlength{\\fboxsep}{0pt},
|
||
IfNoValueTF={#1}{
|
||
boxsep=1pt, arc=1pt, outer arc=1pt, boxrule=0.5pt,
|
||
}{
|
||
boxsep=1pt, arc=0pt, outer arc=0pt, boxrule=0pt, leftrule=1pt
|
||
},
|
||
left=2pt, right=2pt, top=1pt, bottom=1pt, breakable
|
||
}
|
||
% Define an unbreakable Code environment to fontify code inside floats.
|
||
\\DeclareTColorBox[]{Code}{o}{
|
||
colback=EfD, colframe=EFD, colupper=EFD,
|
||
fontupper=\\normalsize\\setlength{\\fboxsep}{0pt},
|
||
IfNoValueTF={#1}{
|
||
boxsep=1pt, arc=1pt, outer arc=1pt, boxrule=0.5pt
|
||
}{
|
||
boxsep=1pt, arc=0pt, outer arc=0pt, boxrule=0pt, leftrule=1pt
|
||
},
|
||
left=2pt, right=2pt, top=1pt, bottom=1pt
|
||
}
|
||
|
||
[LISTINGS-SETUP]")))
|
||
#+end_src
|
||
|
||
#+caption[Emacs setup to use =engrave-faces-latex= in =org-mode= smartly]:
|
||
#+caption: Emacs setup to use =engrave-faces-latex= in =org-mode= smartly.
|
||
#+name: lst:ox-engraved-emacs-lisp-setup
|
||
#+begin_src emacs-lisp -n :results silent :tangle no
|
||
(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)))
|
||
#+end_src
|
||
|
||
*** [[info:org#Adding Hyperlink Types][Making Org hyperlink types (info)]]
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: sec:make-org-hyperlink-types
|
||
:END:
|
||
|
||
The listings below implement or reimplement three groups of =org-link= types:
|
||
1. Listing [[lst:org-ref-like-org-link-types]] defines =org-link= types for
|
||
backwards compatibility with [[https://github.com/jkitchin/org-ref][org-ref]].
|
||
2. Listing [[lst:define-org-pdfview-link-type]] uses ideas from the definition of =docview-org-link= and =doi-org-link= types to define an =pdfview-org-link=
|
||
type for use with [[https://github.com/vedang/pdf-tools][pdf-tools]].
|
||
3. Listing [[lst:patch-org-info-link-type]] patches the function =org-info-export=
|
||
and listing [[lst:emacs-lisp-setup-buffer-local-ol-info]] sets the constants ~org-info-emacs-documents~ and ~org-info-other-documents~ as buffer local
|
||
variables in order to export the =info-org-link= types in this document to =html= and LaTeX correctly.
|
||
|
||
#+caption[Define =org-link= types for backwards compatibility with =org-ref=]:
|
||
#+caption: Define =org-link= types for backwards compatibility with =org-ref=.
|
||
#+name: lst:org-ref-like-org-link-types
|
||
#+begin_src emacs-lisp -n :results silent
|
||
(with-eval-after-load 'ol
|
||
(org-link-set-parameters "ac*" :export #'org-ref-ac*-export)
|
||
(org-link-set-parameters "cite" :export #'org-ref-cite-export)
|
||
(org-link-set-parameters "eqref" :export #'org-ref-eqref-export)
|
||
(org-link-set-parameters "hyperlink" :export #'org-ref-hyperlink-export)
|
||
(org-link-set-parameters "label" :export #'org-ref-label-export)
|
||
(org-link-set-parameters "ref" :export #'org-ref-ref-export)
|
||
|
||
(defun org-ref-ac*-export (path _desc backend _info)
|
||
(pcase backend
|
||
(`latex (format "\\gls*{%s}" path))
|
||
(_ path)))
|
||
|
||
(defun org-ref-cite-export (path _desc backend _info)
|
||
(pcase backend
|
||
(`latex (format "\\cite{%s}" path))
|
||
(_ path)))
|
||
|
||
(defun org-ref-eqref-export (path _desc backend _info)
|
||
(pcase backend
|
||
(`latex (format "\\eqref{%s}" path))
|
||
(_ path)))
|
||
|
||
(defun org-ref-hyperlink-export (path desc backend _info)
|
||
(pcase backend
|
||
(`latex (format "\\hyperlink{%s}{%s}" path desc))
|
||
(_ path)))
|
||
|
||
(defun org-ref-label-export (path _desc backend _info)
|
||
(pcase backend
|
||
(`latex (format "\\label{%s}" path))
|
||
(_ path)))
|
||
|
||
(defun org-ref-ref-export (path _desc backend _info)
|
||
(pcase backend
|
||
(`latex (format "\\ref{%s}" path))
|
||
(_ path))))
|
||
#+end_src
|
||
|
||
#+caption[Define an =org-link= type for =pdf-tools=]:
|
||
#+caption: Define an =org-link= type for =pdf-tools=.
|
||
#+name: lst:define-org-pdfview-link-type
|
||
#+begin_src emacs-lisp -n :results silent
|
||
(with-eval-after-load 'ol
|
||
(autoload 'pdf-view-goto-page "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)))))
|
||
#+end_src
|
||
|
||
#+caption[Patch =ol-info=]:
|
||
#+caption: Patch =ol-info=.
|
||
#+name: lst:patch-org-info-link-type
|
||
#+begin_src emacs-lisp -n :exports code :results silent
|
||
(with-eval-after-load 'ol-info
|
||
(defun org-info-export (path desc format)
|
||
"Export an info link.
|
||
See `org-link-parameters' for details about PATH, DESC and FORMAT."
|
||
(let* ((parts (split-string path "#\\|::"))
|
||
(manual (car parts))
|
||
(node (or (nth 1 parts) "Top")))
|
||
(pcase format
|
||
(`html
|
||
(format "<a href=\"%s#%s\">%s</a>"
|
||
(org-info-map-html-url manual)
|
||
(org-info--expand-node-name node)
|
||
(or desc path)))
|
||
(`latex
|
||
(format "\\href{%s\\#%s}{%s}"
|
||
(org-info-map-html-url manual)
|
||
(org-info--expand-node-name node)
|
||
(or desc path)))
|
||
(`texinfo
|
||
(let ((title (or desc "")))
|
||
(format "@ref{%s,%s,,%s,}" node title manual)))
|
||
(_ nil)))))
|
||
#+end_src
|
||
|
||
#+attr_latex: :options breaklines
|
||
#+caption[Patch buffer local =ol-info=]:
|
||
#+caption: Patch buffer local =ol-info=.
|
||
#+name: lst:emacs-lisp-setup-buffer-local-ol-info
|
||
#+begin_src emacs-lisp -n :exports code :results silent :tangle no
|
||
(with-eval-after-load 'ol-info
|
||
(make-variable-buffer-local 'org-info-emacs-documents)
|
||
(setq org-info-emacs-documents (delete "org" org-info-emacs-documents))
|
||
(make-variable-buffer-local 'org-info-other-documents)
|
||
(setq org-info-other-documents
|
||
(cl-union
|
||
'(("consult" . "https://github.com/minad/consult")
|
||
("embark" . "https://github.com/oantolin/embark")
|
||
("magit" . "https://magit.vc/manual/magit.html")
|
||
("marginalia" . "https://github.com/minad/marginalia")
|
||
("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)))
|
||
#+end_src
|
||
|
||
*** [[https://bitspook.in/blog/extending-org-mode-to-handle-youtube-links/][Extending org-mode to handle YouTube links]]
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: sec:make-org-yt-link-type
|
||
:END:
|
||
|
||
Listing [[lst:define-org-yt-link-type][define org-yt-link type]] implements code to open the link to the
|
||
following YouTube video [[yt:eaZUZCzaIgw#6s#10s][Extending org-mode to handle YouTube links.]]
|
||
|
||
The following posts provide programming information:
|
||
1. [[https://developers.google.com/youtube/v3/docs][YouTube API reference]].
|
||
2. [[https://developers.google.com/youtube/iframe_api_reference][YouTube Player API reference for iframe embeds]].
|
||
3. [[https://www.alphr.com/how-to-link-specific-timestamp-youtube/][How to link to a specific timestamp in a YouTube video]].
|
||
|
||
#+begin_src emacs-lisp -n :exports none :tangle no
|
||
;; https://www.youtube.com/embed/M03bTkQxVUg?start=30&end=120
|
||
|
||
(org-yt-seconds-to-hms (+ (* 3600 120) (* 60 9) 7))
|
||
(org-yt-seconds-to-hms 3661)
|
||
|
||
(org-yt-time-to-seconds "")
|
||
(org-yt-time-to-seconds "1")
|
||
(org-yt-time-to-seconds "1:1")
|
||
(org-yt-time-to-seconds "1:1:1")
|
||
(org-yt-time-to-seconds "1h1m1s")
|
||
(org-yt-time-to-seconds "1mX")
|
||
(org-yt-time-to-seconds "1h")
|
||
(org-yt-time-to-seconds "1H1m")
|
||
(org-yt-time-to-seconds "1h1s")
|
||
(org-yt-time-to-seconds "1m1s")
|
||
(org-yt-time-to-seconds "1h1s")
|
||
(org-yt-time-to-seconds "1h1M1s")
|
||
|
||
(org-yt-format-open-url "WKwZHSbHmPg" "")
|
||
(org-yt-format-open-url "WKwZHSbHmPg" nil)
|
||
(org-yt-format-open-url "WKwZHSbHmPg" '("1m1s"))
|
||
(org-yt-format-open-url "WKwZHSbHmPg" '("0:2:44" "0:2:50"))
|
||
|
||
(org-yt-open "WKwZHSbHmPg#0:2:44#0:2:50" t)
|
||
|
||
(org-yt-export "WKwZHSbHmPg#0:2:44#0:2:50" "Title" 'html nil)
|
||
(org-yt-export "WKwZHSbHmPg#0h2m40s#0h2m50s" "Title" 'html nil)
|
||
(org-yt-export "WKwZHSbHmPg#2M40S#2M50S" "Title" 'html nil)
|
||
|
||
(org-yt-export "WKwZHSbHmPg#0:2:44#0:2:50" "Title" 'latex nil)
|
||
(org-yt-export "WKwZHSbHmPg#0h2m40s#0h2m50s" "Title" 'latex nil)
|
||
(org-yt-export "WKwZHSbHmPg#2M40S#2M50S" "Title" 'latex nil)
|
||
#+end_src
|
||
|
||
#+caption[Parsing function for the =org-yt= link type]:
|
||
#+caption: Parsing function for the =org-yt= link type.
|
||
#+name: lst:org-yt-parsing
|
||
#+begin_src emacs-lisp -n :results silent
|
||
(defun org-yt-time-to-seconds (text)
|
||
"Convert TEXT of the form `12:34:56' or `12h34m56s' to seconds as integers."
|
||
(let* ((case-fold-search t)
|
||
(hms (< (length (split-string text "[:]"))
|
||
(length (split-string text "[HhMmSs]"))))
|
||
(regexp (if hms
|
||
(rx (opt (group (+ digit) "h"))
|
||
(opt (group (+ digit) "m"))
|
||
(opt (group (+ digit) "s")))
|
||
(rx (opt (group (+ digit)))
|
||
(opt ":" (group (+ digit)))
|
||
(opt ":" (group (+ digit))))))
|
||
(_ (string-match regexp text))
|
||
(md (match-data))
|
||
(found (substring text (pop md) (pop md)))
|
||
(seconds 0)
|
||
lot)
|
||
(if (string< found text)
|
||
(user-error "Found `%s' which is a prefix of `%s'" found text)
|
||
(if hms
|
||
(while md
|
||
(setq lot (substring text (or (pop md) 0) (or (pop md) 0)))
|
||
(cond
|
||
((or (string-suffix-p "h" lot) (string-suffix-p "H" lot))
|
||
(setq seconds (+ seconds (* 3600 (string-to-number lot)))))
|
||
((or (string-suffix-p "m" lot) (string-suffix-p "M" lot))
|
||
(setq seconds (+ seconds (* 60 (string-to-number lot)))))
|
||
((or (string-suffix-p "s" lot) (string-suffix-p "S" lot))
|
||
(setq seconds (+ seconds (string-to-number lot))))))
|
||
(while md (push (string-to-number
|
||
(substring text (or (pop md) 0) (or (pop md) 0)))
|
||
lot))
|
||
(cond
|
||
((length= lot 1) (setq seconds (car lot)))
|
||
((length= lot 2) (setq seconds (+ (* 60 (cadr lot))
|
||
(car lot))))
|
||
((length= lot 3) (setq seconds (+ (* 3600 (caddr lot))
|
||
(* 60 (cadr lot))
|
||
(car lot)))))))
|
||
seconds))
|
||
#+end_src
|
||
|
||
#+caption[Formatting functions to open =org-yt= links]:
|
||
#+caption: Formatting functions to open =org-yt= links.
|
||
#+name: lst:org-yt-open-formatting
|
||
#+begin_src emacs-lisp -n :results silent
|
||
(defun org-yt-seconds-to-hms (seconds)
|
||
"Convert seconds as integers to text of the form `12h34m56s'."
|
||
(let ((h (/ seconds 3600))
|
||
(m (/ (% seconds 3600) 60))
|
||
(s (% seconds 60)))
|
||
(concat (and (> h 0) (format "%dh" h))
|
||
(if (> h 0)
|
||
(format "%02dm" m)
|
||
(and (> m 0) (format "%dm" m)))
|
||
(if (or (> h 0) (> m 0))
|
||
(format "%02ds" s)
|
||
(format "%ds" s)))))
|
||
|
||
(defconst org-yt-start
|
||
"https://www.youtube.com/watch?v=%s&t=%s"
|
||
"Format string for `org-yt-format-open-url' to fill in ID and TIMING.")
|
||
|
||
(defconst org-yt-chunk
|
||
"https://www.youtube.com/embed/%s?autoplay=1&start=%d&end=%d"
|
||
"Format string for `org-yt-format-open-url' to fill in ID and TIMING.")
|
||
|
||
(defun org-yt-format-open-url (id timing)
|
||
"Convert an \"yt\" type link to an URL for opening."
|
||
(cond
|
||
((not timing)
|
||
(format org-yt-start id "0s"))
|
||
((length= timing 1)
|
||
(format org-yt-start id
|
||
(org-yt-seconds-to-hms (org-yt-time-to-seconds (car timing)))))
|
||
((length= timing 2)
|
||
(format org-yt-chunk id
|
||
(org-yt-time-to-seconds (car timing))
|
||
(org-yt-time-to-seconds (cadr timing))))))
|
||
#+end_src
|
||
|
||
#+caption[Formatting function for HTML export of =org-yt= links]:
|
||
#+caption: Formatting function for HTML export of =org-yt= links.
|
||
#+name: lst:org-yt-html-export-formatting
|
||
#+begin_src emacs-lisp -n :results silent
|
||
(defconst org-yt-iframe
|
||
"<iframe
|
||
frameborder=\"0\" width=\"600\" height=\"450\"
|
||
src=\"https://www.youtube.com/embed/%s%s\"
|
||
title=\"%s\"
|
||
allowfullscreen></iframe>"
|
||
"Format string for `org-yt-iframe' to fill in ID, TIMING, and TITLE.")
|
||
|
||
(defun org-yt-format-iframe (id timing title)
|
||
"Convert an \"yt\" type link to an inline frame element for export to HTML."
|
||
(format org-yt-iframe id
|
||
(cond
|
||
((not timing) "")
|
||
((length= timing 1)
|
||
(format "?start=%d" id (org-yt-time-to-seconds (car timing))))
|
||
((length= timing 2)
|
||
(format "?start=%d&end=%d"
|
||
(org-yt-time-to-seconds (car timing))
|
||
(org-yt-time-to-seconds (cadr timing)))))
|
||
title))
|
||
#+end_src
|
||
|
||
#+caption[Define an =org-link= type for =YouTube=]:
|
||
#+caption: Define an =org-link= type for =YouTube=.
|
||
#+name: lst:define-org-yt-link-type
|
||
#+begin_src emacs-lisp -n :results silent
|
||
(defun org-yt-open (path prefix)
|
||
"Open an \"yt\" type link with `browse-url', `emms', or \"mpv\".
|
||
PATH is a string containing the video ID and optional timing information.
|
||
Open the link with `browse-url' if PREFIX else with `emms' or \"mpv\"."
|
||
(let* ((parts (split-string path "[#]+" t))
|
||
(id (car parts))
|
||
(timing (cdr parts))
|
||
(url (org-yt-format-open-url id timing)))
|
||
(cond
|
||
(prefix
|
||
(browse-url url))
|
||
((require 'emms nil 'noerror)
|
||
(emms-add-url url)
|
||
(with-current-emms-playlist
|
||
(save-excursion
|
||
(emms-playlist-last)
|
||
(emms-playlist-mode-play-current-track))))
|
||
((executable-find "mpv")
|
||
(let ((display-buffer-alist
|
||
`((,shell-command-buffer-name-async . (display-buffer-no-window)))))
|
||
(async-shell-command (format "\"%s\" \"%s\"" url)))
|
||
(message "Launched mpv with \"%s\"" url))
|
||
(t
|
||
(user-error "Can't open `%s': install `emms' and/or `mpv'" url)))))
|
||
|
||
(defun org-yt-export (path desc backend _)
|
||
"Export an \"yt\" type link."
|
||
(let* ((parts (split-string path "[#]+" t))
|
||
(id (car parts))
|
||
(timing (cdr parts)))
|
||
(pcase backend
|
||
(`html
|
||
(org-yt-format-iframe id timing desc))
|
||
(`latex
|
||
(format "\\href{%s}{%s}" (org-yt-format-open-url id timing) desc))
|
||
(_ id))))
|
||
|
||
(with-eval-after-load 'ol
|
||
;; https://bitspook.in/blog/extending-org-mode-to-handle-youtube-paths/
|
||
(org-link-set-parameters "yt"
|
||
:follow #'org-yt-open
|
||
:export #'org-yt-export))
|
||
#+end_src
|
||
|
||
*** [[https://tecosaur.github.io/emacs-config/#translate-capital-keywords][Translate capital keywords (old) to lower case (new)]]
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: sec:convert-upper-to-lower-case-keywords
|
||
:END:
|
||
|
||
#+caption[Convert upper to lower case keywords]:
|
||
#+caption: Convert upper to lower case keywords.
|
||
#+name: lst:convert-upper-to-lower-case-keywords
|
||
#+begin_src emacs-lisp -n :results silent
|
||
(with-eval-after-load 'emacs
|
||
;; https://tecosaur.github.io/emacs-config/#translate-capital-keywords
|
||
(defun org-syntax-convert-keyword-case-to-lower ()
|
||
"Convert all #+KEYWORDS to #+keywords."
|
||
(interactive)
|
||
(when (derived-mode-p 'org-mode)
|
||
(save-excursion
|
||
(goto-char (point-min))
|
||
(let ((count 0)
|
||
(case-fold-search nil))
|
||
(while (re-search-forward "^[ \t]*#\\+[A-Z_]+" nil t)
|
||
(unless (s-matches-p "RESULTS" (match-string 0))
|
||
(replace-match (downcase (match-string 0)) t)
|
||
(setq count (1+ count))))
|
||
(message "Replaced %d keywords" count))))))
|
||
#+end_src
|
||
|
||
*** [[https://lists.gnu.org/archive/html/emacs-orgmode/2016-07/msg00394.html][Evaluate specific source blocks at load-time]]
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: sec:load-time-specific-source-block-evaluation
|
||
:END:
|
||
|
||
[[https://emacs.stackexchange.com/questions/12938/how-can-i-evaluate-elisp-in-an-orgmode-file-when-it-is-opened][How to do load time source block evaluation]]
|
||
|
||
#+caption[Evaluate specific source blocks at load-time]:
|
||
#+caption: Evaluate specific source blocks at load-time.
|
||
#+name: lst:load-time-specific-source-block-evaluation
|
||
#+begin_src emacs-lisp -n :results silent
|
||
(with-eval-after-load 'emacs
|
||
(defun org-eval-named-blocks-with-infix (infix)
|
||
"Evaluate all source blocks having INFIX in their name."
|
||
(when (eq major-mode 'org-mode)
|
||
(let ((blocks
|
||
(org-element-map
|
||
(org-element-parse-buffer 'greater-element nil) 'src-block
|
||
(lambda (block)
|
||
(when-let ((name (org-element-property :name block)))
|
||
(when (string-match-p infix name) block))))))
|
||
(dolist (block blocks)
|
||
(goto-char (org-element-property :begin block))
|
||
(org-babel-execute-src-block)))))
|
||
|
||
(defun org-eval-emacs-lisp-setup-blocks ()
|
||
"Evaluate all source blocks having \"emacs-lisp-setup\" in their name."
|
||
(interactive)
|
||
(org-eval-named-blocks-with-infix "emacs-lisp-setup"))
|
||
|
||
(defun org-eval-python-setup-blocks ()
|
||
"Evaluate all source blocks having \"python-setup\" in their name."
|
||
(interactive)
|
||
(org-eval-named-blocks-with-infix "python-setup"))
|
||
|
||
;; Emacs looks for "Local variables:" after the last "newline-formfeed".
|
||
(add-to-list 'safe-local-eval-forms '(org-eval-emacs-lisp-setup-blocks))
|
||
(add-to-list 'safe-local-eval-forms '(org-eval-python-setup-blocks)))
|
||
#+end_src
|
||
|
||
*** [[info:org#LaTeX header and sectioning][Easy LaTeX preamble editing]]
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: sec:easy-latex-preamble-editing
|
||
:END:
|
||
|
||
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 [[info:org#Top][Org (info)]] file uses the new way, but keeps the old way for
|
||
backwards compatibility.
|
||
|
||
The new way -- exploiting an idea of [[https://www.matem.unam.mx/~omar/][Omar Antolin Camarena]] -- is to code new
|
||
[[info:org#Editing Source Code][<LANGUAGE>-modes]] allowing to edit in LaTeX mode and to export to LaTeX code with
|
||
[[info:org#LaTeX specific export settings][correct LaTeX preamble export setting prefixes]]. Here, are links to three posts
|
||
exposing his idea:
|
||
1. [[https://www.reddit.com/r/orgmode/comments/7u2n0h/tip_for_defining_latex_macros_for_use_in_both/][Export LaTeX macros to LaTeX and HTML/MathJax preambles (reddit)]],
|
||
2. [[https://www.reddit.com/r/orgmode/comments/5bi6ku/tip_for_exporting_javascript_source_block_to/][Export JavaScript source blocks to script tags in HTML (reddit)]],
|
||
3. [[https://emacs.stackexchange.com/questions/28301/export-javascript-source-block-to-script-tag-in-html-when-exporting-org-file-to][Export JavaScript source blocks to script tags in HTML (SX)]].
|
||
Listing [[lst:org-babel-latex-header-blocks]] implements this way by means of two
|
||
new [[info:org#Editing Source Code][<LANGUAGE>-modes]]: =latex-header= and =latex-extra-header=.
|
||
|
||
#+caption[New =<LANGUAGE>-modes= to edit the LaTeX preamble easily]:
|
||
#+caption: Add =latex-header= and =latex-extra-header= language modes to edit
|
||
#+caption: LaTeX preamble =latex_header= and =latex_extra_header= export options
|
||
#+caption: easily.
|
||
#+name: lst:org-babel-latex-header-blocks
|
||
#+begin_src emacs-lisp -n :exports code :results silent
|
||
(with-eval-after-load 'emacs
|
||
(defun prefix-all-lines (prefix body)
|
||
(with-temp-buffer
|
||
(insert body)
|
||
(string-insert-rectangle (point-min) (point-max) prefix)
|
||
(buffer-string)))
|
||
|
||
(defun org-babel-execute:latex-extra-header (body _params)
|
||
"Execute a block of LaTeX extra header lines with org-babel.
|
||
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
|
||
(setopt 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))))
|
||
#+end_src
|
||
|
||
The old way is to use a special export attribute as in the function
|
||
=org-latex-header-blocks-filter= in [[https://git.sr.ht/~bzg/org-contrib/tree/master/item/lisp/ox-extra.el][ox-extra.el]]. Apparently, nobody is using
|
||
this broken function (broken, since it relies on support only in org-mode before
|
||
=2014-11-11=). Listing [[lst:org-latex-header-blocks-filter]] proposes a fix for
|
||
=org-latex-header-blocks-filter=.
|
||
|
||
#+caption[Convert marked LaTeX export blocks to LaTeX header lines]:
|
||
#+caption: Convert marked LaTeX export blocks to LaTeX header lines.
|
||
#+name: lst:org-latex-header-blocks-filter
|
||
#+begin_src emacs-lisp -n :results silent
|
||
(with-eval-after-load 'ox
|
||
(defun org-latex-header-blocks-filter (backend)
|
||
"Convert marked LaTeX export blocks to \"#+latex_header: \" lines.
|
||
The marker is a line \"#+header: :header yes\" preceding the block.
|
||
|
||
For instance, the LaTeX export block
|
||
|
||
,#+header: :header yes
|
||
,#+begin_export latex
|
||
% This line converts to a LaTeX header line.
|
||
,#+end_export
|
||
|
||
converts to
|
||
|
||
\"#+latex_header: % This line converts to a LaTeX header line.\"."
|
||
(when (org-export-derived-backend-p backend 'latex)
|
||
(let ((blocks
|
||
(org-element-map
|
||
(org-element-parse-buffer 'greater-element nil) 'export-block
|
||
(lambda (block)
|
||
(let ((type (org-element-property :type block))
|
||
(header (org-export-read-attribute :header block :header)))
|
||
(when (and (string= type "LATEX") (string= header "yes"))
|
||
block))))))
|
||
(mapc (lambda (block)
|
||
;; Set point to where to insert LaTeX header lines
|
||
;; after deleting the block.
|
||
(goto-char (org-element-property :post-affiliated block))
|
||
(let ((lines
|
||
(split-string (org-element-property :value block) "\n")))
|
||
(delete-region (org-element-property :begin block)
|
||
(org-element-property :end block))
|
||
(dolist (line lines)
|
||
(insert (concat "#+latex_header: "
|
||
(replace-regexp-in-string "\\` *" "" line)
|
||
"\n")))))
|
||
;; Reverse to go upwards to avoid wrecking the list of
|
||
;; block positions in the file that would occur in case
|
||
;; of going downwards.
|
||
(reverse blocks)))))
|
||
|
||
;; Push the filter on the hook.
|
||
(cl-pushnew #'org-latex-header-blocks-filter
|
||
org-export-before-parsing-hook))
|
||
#+end_src
|
||
|
||
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 [[info:org#Export Settings][#+SETUPFILE: <FILE>]], but only in [[info:org#Export Settings][#+INCLUDE: <FILE>]] files.
|
||
|
||
*** [[info:org#Macro Replacement][Org-mode macro utilities]]
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: sec:org-macro-utilities
|
||
:END:
|
||
|
||
Listing [[lst:by-backend-kbd-org-macro]] defines the Emacs Lisp utilities to
|
||
define the [[https://orgmode.org/][Org mode]] =kbd= macro in listing
|
||
[[lst:source-file-export-keyword-settings]].
|
||
|
||
#+attr_latex: :options breaklines
|
||
#+caption[Define Emacs Lisp utilities to define the =Org-mode= =kbd= macro]:
|
||
#+caption: Define Emacs Lisp utilities to define the =Org-mode= =kbd= macro.
|
||
#+name: lst:by-backend-kbd-org-macro
|
||
#+begin_src emacs-lisp -n :results silent
|
||
(with-eval-after-load 'emacs
|
||
(when (ensure-package-installation 'htmlize)
|
||
(autoload 'htmlize-protect-string "htmlize" nil t))
|
||
|
||
;; https://orgmode.org/worg/org-contrib/babel/languages/ob-doc-LaTeX.html
|
||
(defmacro by-backend (&rest body)
|
||
"Help for org-export backend dependent execution."
|
||
`(cl-case ',(bound-and-true-p org-export-current-backend) ,@body))
|
||
|
||
(defun by-backend-kbd-org-macro (keys)
|
||
"Help for an org-export backend dependent \"#+macro: kbd\"."
|
||
(by-backend
|
||
(html (format "@@html:<kbd>%s</kbd>@@" (htmlize-protect-string keys)))
|
||
(latex (format "@@latex:\\colorbox{PowderBlue}{\\texttt{%s}}@@" keys)))))
|
||
#+end_src
|
||
|
||
*** [[info:org#Export Settings][File inclusion (info)]] and [[info:org#Noweb Reference Syntax][Noweb (info)]] trickery
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: sec:file-inclusion-and-noweb
|
||
:END:
|
||
|
||
Note: [[https://list.orgmode.org/87sfisf31o.fsf@posteo.net/][How to include LaTeX packages in a LaTeX export block]].
|
||
|
||
Listing [[lst:source-file-export-keyword-settings]] shows the preamble lines of this
|
||
[[file:README.org]] file. It lists the export keyword settings, the definition of
|
||
the [[https://orgmode.org/][Org mode]] =kbd= macro and the source block that generates part of the LaTeX
|
||
preamble. Listing [[lst:latex-header-1]], [[lst:latex-header-2]], [[lst:latex-header-3]],
|
||
[[lst:latex-header-4]], and [[lst:latex-header-5]] show the LaTeX source blocks that
|
||
generate the LaTeX preamble. All listings in this section exists thanks to [[info:org#Export Settings][file
|
||
inclusion (info)]] and [[info:org#Noweb Reference Syntax][noweb (info)]] trickery.
|
||
|
||
#+caption[Source file export keyword settings]:
|
||
#+caption: The preamble lines of README.org containing the export keyword
|
||
#+caption: settings, the definition of the Org-mode =kbd= macro, and the
|
||
#+caption: source block that generates part of the LaTeX preamble.
|
||
#+name: lst:source-file-export-keyword-settings
|
||
#+include: "README.org" src org -n :lines "1-17"
|
||
|
||
#+name: latex-header-1
|
||
#+begin_src latex -n :exports none
|
||
% Begin of the LaTeX preamble:
|
||
% See: https://list.orgmode.org/87o807r7fr.fsf@posteo.net/
|
||
% From: "Juan Manuel Macías" <maciaschain@posteo.net>
|
||
% To: orgmode <emacs-orgmode@gnu.org>
|
||
% Subject: [tip] Insert arbitrary LaTeX code at the beginning of any float environment
|
||
% Date: Sun, 08 May 2022 22:22:16 +0000
|
||
% Message-ID: <87o807r7fr.fsf@posteo.net>
|
||
% LuaLaTeX-, PdfLaTeX-, or XeTeX-COMPILER COMPATIBILITY:
|
||
% Prevent collisions by using font packages before compiler specific packages.
|
||
\usepackage{ifthen,ifluatex,ifxetex}
|
||
\ifthenelse{\boolean{luatex}}{
|
||
\usepackage{fontspec} % lualatex
|
||
}{\ifthenelse{\boolean{xetex}}{
|
||
\usepackage{mathspec} % xetex
|
||
}{
|
||
\usepackage[T1]{fontenc} % pdflatex
|
||
\usepackage[utf8]{inputenc} % pdflatex
|
||
}
|
||
}
|
||
\usepackage{amsmath}
|
||
\usepackage{amssymb}
|
||
\usepackage{pifont} % check mark (\ding{52}) and cross mark (\ding{56})
|
||
\usepackage{textcomp} % \texttimes
|
||
\usepackage{wasysym} % \diameter
|
||
|
||
% Org-mode REQUIREMENTS:
|
||
\usepackage{graphicx}
|
||
\usepackage{longtable}
|
||
\usepackage{wrapfig}
|
||
\usepackage{rotating}
|
||
\usepackage[normalem]{ulem}
|
||
\usepackage{capt-of}
|
||
\usepackage{hyperref}
|
||
% End of the 1st LaTeX header block.
|
||
#+end_src
|
||
|
||
#+caption[LaTeX preamble: replacing the =Org-mode= default packages]:
|
||
#+caption: LaTeX preamble: replacing the =Org-mode= default packages.
|
||
#+name: lst:latex-header-1
|
||
#+begin_src latex -n :exports code :noweb yes
|
||
<<latex-header-1>>
|
||
#+end_src
|
||
|
||
#+name: latex-header-2
|
||
#+begin_src latex -n :exports none
|
||
% LANGUAGE:
|
||
\usepackage{babel}
|
||
\usepackage{fvextra}
|
||
\usepackage{csquotes}
|
||
|
||
% LISTS:
|
||
\usepackage{enumitem}
|
||
\setlist{noitemsep}
|
||
|
||
% LISTINGS:
|
||
% Section 2.6 of caption-eng.pdf (texdoc caption) explains that the sign
|
||
% of "skip" depends on the assumption "position=above" or "position=below".
|
||
% The assumption should match the real caption position in the LaTeX code.
|
||
\usepackage{caption}
|
||
\usepackage[newfloat]{minted}
|
||
\captionsetup[listing]{position=below,skip=0em}
|
||
\usemintedstyle{xcode}
|
||
|
||
% TABLES:
|
||
% https://tex.stackexchange.com/questions/341205/
|
||
% what-is-the-difference-between-tabular-tabular-and-tabularx-environments
|
||
% https://emacs.stackexchange.com/questions/26179/
|
||
% change-org-mode-table-style-just-for-latex-export
|
||
% https://tex.stackexchange.com/questions/468585/
|
||
% table-formatting-using-siunitx
|
||
\usepackage{booktabs}
|
||
\usepackage{colortbl}
|
||
\usepackage{tabularx} % DANGER: beware of Org table :width and :align options!
|
||
% End of the 2nd LaTeX header block.
|
||
#+end_src
|
||
|
||
#+caption[LaTeX preamble: language, lists and floats]:
|
||
#+caption: LaTeX preamble: language, lists and floats.
|
||
#+name: lst:latex-header-2
|
||
#+begin_src latex -n :exports code :noweb yes
|
||
<<latex-header-2>>
|
||
#+end_src
|
||
|
||
#+name: latex-header-3
|
||
#+begin_src latex -n :exports none
|
||
% PAGE LAYOUT:
|
||
\usepackage{fancyhdr}
|
||
\usepackage{lastpage}
|
||
\usepackage[
|
||
headheight=20mm,
|
||
top=40mm,
|
||
bottom=20mm,
|
||
left=0.1\paperwidth,
|
||
right=0.1\paperwidth,
|
||
heightrounded,
|
||
verbose,
|
||
]{geometry}
|
||
|
||
% TECHNICS:
|
||
\usepackage{siunitx}
|
||
\usepackage{tikz}
|
||
% End of the 3rd LaTeX header block.
|
||
#+end_src
|
||
|
||
#+caption[LaTeX preamble: page layout and technics]:
|
||
#+caption: LaTeX preamble: page layout.
|
||
#+name: lst:latex-header-3
|
||
#+begin_src latex -n :exports code :noweb yes
|
||
<<latex-header-3>>
|
||
#+end_src
|
||
|
||
#+name: latex-header-4
|
||
#+begin_src latex -n :exports none
|
||
% FLOAT BARRIERS:
|
||
% https://tex.stackexchange.com/questions/118662/use-placeins-for-subsections
|
||
% Make section an implicit float barrier:
|
||
\usepackage[section]{placeins}
|
||
% Make subsection an implicit float barrier:
|
||
\makeatletter
|
||
\AtBeginDocument{%
|
||
\expandafter\renewcommand\expandafter\subsection\expandafter{%
|
||
\expandafter\@fb@secFB\subsection
|
||
}%
|
||
}
|
||
\makeatother
|
||
% Make subsubsection an implicit float barrier:
|
||
\makeatletter
|
||
\AtBeginDocument{%
|
||
\expandafter\renewcommand\expandafter\subsubsection\expandafter{%
|
||
\expandafter\@fb@secFB\subsubsection
|
||
}%
|
||
}
|
||
% End of the 4th LaTeX header block.
|
||
#+end_src
|
||
|
||
#+caption[LaTeX preamble: float barriers]:
|
||
#+caption: LaTeX preamble: float barriers.
|
||
#+name: lst:latex-header-4
|
||
#+begin_src latex -n :exports code :noweb yes
|
||
<<latex-header-4>>
|
||
#+end_src
|
||
|
||
#+name: latex-header-5
|
||
#+begin_src latex -n :exports none
|
||
% FANCY HEADERS AND FOOTERS:
|
||
% Add fancy headers and footers to normal pages.
|
||
\pagestyle{fancy}
|
||
\fancyhf{}
|
||
\renewcommand{\footrulewidth}{0.4pt}
|
||
\fancyfoot[C]{\emph{
|
||
Emacs 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}}
|
||
}
|
||
% End of the 5th and last LaTeX header block.
|
||
#+end_src
|
||
|
||
#+caption[LaTeX preamble: fancy headers and footers]:
|
||
#+caption: LaTeX preamble: fancy headers and footers.
|
||
#+name: lst:latex-header-5
|
||
#+begin_src latex -n :exports code :noweb yes
|
||
<<latex-header-5>>
|
||
#+end_src
|
||
|
||
*** [[info:org#LaTeX specific export settings][Advanced LaTeX export settings]]
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: sec:advanced-latex-export-settings
|
||
:END:
|
||
|
||
Listing [[lst:ox-latex-emacs-lisp-setup]] initializes the buffer local variables
|
||
=org-latex-classes=, =org-latex-title-command=, =org-latex-toc-command=, and
|
||
=org-latex-subtitle-format=. Listing [[lst/title-page]] is a template to initialize
|
||
=org-latex-title-command=. Type {{{kbd(C-h v org-latex-classes)}}}, {{{kbd(C-h
|
||
v org-latex-subtitle-format)}}}, {{{kbd(C-h v org-latex-title-command)}}}, and
|
||
{{{kbd(C-h v org-latex-toc-command)}}} to read how those variables control
|
||
exporting from Org-mode to LaTeX.
|
||
|
||
#+caption[Define buffer local =ox-latex= variables]:
|
||
#+caption: Define buffer local =ox-latex= variables.
|
||
#+header: :var title-page=lst/title-page
|
||
#+name: lst:ox-latex-emacs-lisp-setup
|
||
#+begin_src emacs-lisp -n :results silent :tangle no
|
||
(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 ""))
|
||
#+end_src
|
||
|
||
#+caption[Show a title-page example for =org-latex-title-command=]:
|
||
#+caption: Show a title-page example for =org-latex-title-command=.
|
||
#+name: lst/title-page
|
||
#+begin_src latex -n :exports code :results silent :tangle no
|
||
\begin{titlepage}
|
||
%% https://tex.stackexchange.com/questions/506102/
|
||
%% adding-header-and-footer-to-custom-titlepage
|
||
\thispagestyle{titlepage}
|
||
\begin{center}
|
||
%% Title
|
||
\begin{Huge}
|
||
{\bf %t} \\
|
||
\vspace{1em}
|
||
\end{Huge}
|
||
%% Author
|
||
\begin{Large}
|
||
{\bf %a} \\
|
||
\vspace{1em}
|
||
\end{Large}
|
||
\end{center}
|
||
\end{titlepage}
|
||
#+end_src
|
||
|
||
*** [[https://orgmode.org/worg/dev/org-syntax-edited.html][Org Syntax]]
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: sec:org-syntax
|
||
:END:
|
||
|
||
Two tools to grok how [[https://orgmode.org/worg/dev/org-element-api.html][Org mode parsing]] works are the [[https://orgmode.org/worg/dev/org-syntax-edited.html][Org Syntax]] specification
|
||
and the [[http://xahlee.info/emacs/emacs/elisp_parse_org_mode.html][Org mode parser tutorial]]. The [[https://orgmode.org/worg/dev/org-element-api.html][Org element parsing API]] boils down to three
|
||
functions:
|
||
1. The function [[https://orgmode.org/worg/dev/org-element-api.html#global][~org-element-parse-buffer~]] implements a fully recursive buffer
|
||
parser that returns an abstract syntax tree.
|
||
2. The functions [[https://orgmode.org/worg/dev/org-element-api.html#local][~org-element-at-point~ and ~org-element-context~]] return
|
||
information on the document structure around point either at the element
|
||
level or at the object level in case of ~org-element-context~.
|
||
Listing [[lst:grok-org-element-tree]] improves the [[http://xahlee.info/emacs/emacs/elisp_parse_org_mode.html][Org mode parser tutorial]] by
|
||
defining interactive wrappers that pretty print the results of those
|
||
non-interactive =org-element= functions to an =Emacs-lisp= buffer.
|
||
|
||
#+caption[Grok how =org-element= parses your document]:
|
||
#+caption: Grok how =org-element= parses your document.
|
||
#+name: lst:grok-org-element-tree
|
||
#+begin_src emacs-lisp -n :results silent
|
||
(with-eval-after-load 'org-element
|
||
(when (require 'pp nil 'noerror)
|
||
(defconst grok-org-output
|
||
"*Grok Org Element Output*"
|
||
"Grok Org output buffer name.")
|
||
|
||
(defun grok-org-element-at-point ()
|
||
(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))))
|
||
#+end_src
|
||
|
||
** Grammar, spelling, and style tools
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: sec:writing-tools
|
||
:END:
|
||
|
||
*** [[info:emacs#Abbrevs][Abbrevs (info)]]
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: sec:writing-abbreviations
|
||
:END:
|
||
|
||
[[https://www.masteringemacs.org/][Mickey Peterson]] has posted [[https://www.masteringemacs.org/article/correcting-typos-misspellings-abbrev][Correcting Typos and Misspellings with Abbrev]] showing
|
||
how to use [[info:emacs#Keyboard Macros][Keyboard Macros (info)]] to exploit [[https://en.wikipedia.org/wiki/Wikipedia:Lists_of_common_misspellings/For_machines][Wikipedia's list of common
|
||
misspellings for machines]]. Listing [[lst:misspellings-abbrev]] defines his
|
||
keyboard macro under the name =misspellings-abrev=. I have used those
|
||
directions to define src_emacs-lisp{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:
|
||
1. Execute src_emacs-lisp{(edit-abbrevs)} to alter abbreviation definitions by
|
||
editing an =*Abbrevs*= buffer.
|
||
2. Add, edit, or remove definitions of the form ="source" 1 "target"= under the
|
||
global or a mode-specific table.
|
||
3. Execute src_emacs-lisp{(abbrev-edit-save-buffer)} to save all user
|
||
abbreviation definitions in the current buffer.
|
||
|
||
#+caption[Definition of the =misspellings-abbrev= keyboard macro]:
|
||
#+caption: Definition of the =misspellings-abbrev= keyboard macro.
|
||
#+name: lst:misspellings-abbrev
|
||
#+begin_src emacs-lisp -n :results silent
|
||
(with-eval-after-load 'emacs
|
||
(defun browse-common-misspellings ()
|
||
"Open the Wikipedia page of common misspellings for machines in EWW."
|
||
(interactive)
|
||
(eww (concat "https://en.wikipedia.org/wiki/Wikipedia"
|
||
":Lists_of_common_misspellings/For_machines")))
|
||
|
||
(fset 'misspellings-abbrev
|
||
(kmacro-lambda-form
|
||
[?\C-s ?- ?> return backspace backspace ?\C-k ?\C-x ?a ?i ?g ?\C-y return]
|
||
0 "%d"))
|
||
|
||
(setq-default abbrev-mode t))
|
||
#+end_src
|
||
|
||
Listing [[lst:word-games]] defines the =anagram-p= function that migth be used games.
|
||
|
||
#+caption[Word games]:
|
||
#+caption: Word games.
|
||
#+name: lst:word-games
|
||
#+begin_src emacs-lisp -n :results silent
|
||
;; https://funcall.blogspot.com/2022/07/lets-play-wordle.html
|
||
;; https://github.com/tabatkins/wordle-list
|
||
(defun anagram-p (evil vile)
|
||
"Check whether strings EVIL and VILE are anagrams of each other."
|
||
(if (string= evil vile)
|
||
nil
|
||
(string= (cl-sort evil #'<) (cl-sort vile #'<))))
|
||
|
||
;; https://funcall.blogspot.com/2020/01/palindromes-redux-and-sufficiently.html
|
||
;; https://irreal.org/blog/?p=8570
|
||
(defun palindrome-p (word)
|
||
"Check whether the string WORD is a palindrome."
|
||
(cl-do ((head 0 (1+ head))
|
||
(tail (1- (length word)) (1- tail))
|
||
(ok t))
|
||
((or (not ok) (>= head tail) ok)
|
||
(setq ok (= (aref word head) (aref word tail))))))
|
||
#+end_src
|
||
|
||
*** [[https://dict.org/bin/Dict][DICT.org]]
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: sec:writing-dict
|
||
:END:
|
||
|
||
Evaluating src_emacs-lisp{(dictionary)} connects to a local or remote =dictd=
|
||
server. The following links explain how to configure and use =dictd=:
|
||
1. [[https://jpmens.net/2020/03/08/looking-up-words-with-dict/][Looking up words with DICT]]
|
||
2. [[https://www.rfc-editor.org/rfc/rfc2229.html][RFC 2229: A Dictionary Server Protocol]]
|
||
3. [[https://www.masteringemacs.org/article/wordsmithing-in-emacs][Wordsmithing in Emacs]]
|
||
|
||
I am using the following Debian Bullseye dictionaries on Darwin and Gentoo:
|
||
1. [[http://ftp.fr.debian.org/debian/pool/main/d/dict-devil/dict-devil_1.0-13.1_all.deb][dict-devil_1.0-13.1_all.deb]]
|
||
2. [[http://ftp.fr.debian.org/debian/pool/main/d/dict-foldoc/dict-foldoc_20201018-1_all.deb][dict-foldoc_20201018-1_all.deb]]
|
||
3. [[http://ftp.fr.debian.org/debian/pool/main/d/dict-gcide/dict-gcide_0.48.5+nmu1_all.deb][dict-gcide_0.48.5+nmu1_all.deb]]
|
||
4. [[http://ftp.fr.debian.org/debian/pool/main/d/dict-jargon/dict-jargon_4.4.7-3.1_all.deb][dict-jargon_4.4.7-3.1_all.deb]]
|
||
5. [[http://ftp.fr.debian.org/debian/pool/main/v/vera/dict-vera_1.24-1_all.deb][dict-vera_1.24-1_all.deb]]
|
||
6. [[http://ftp.fr.debian.org/debian/pool/main/w/wordnet/dict-wn_3.0-36_all.deb][dict-wn_3.0-36_all.deb]]
|
||
|
||
#+caption[Resource file for =dict= on =Darwin=]:
|
||
#+caption: Resource file for =dict= on =Darwin=.
|
||
#+name: lst:darwin-dict-resource-file
|
||
#+header: :tangle (if (eq 'darwin system-type) "~/.dictrc" "no")
|
||
#+begin_src conf -n
|
||
# https://jpmens.net/2020/03/08/looking-up-words-with-dict/
|
||
server 127.0.0.1 {
|
||
port 2628
|
||
}
|
||
# Local Variables:
|
||
# mode: conf-unix
|
||
# End:
|
||
#+end_src
|
||
|
||
#+caption[Make a configuration file for =dictd= on =Darwin=]:
|
||
#+caption: Make a configuration file for =dictd= on =Darwin=.
|
||
#+name: lst:dictd-configuration-file
|
||
#+begin_src shell -n :eval (if (eq 'darwin system-type) "yes" "never") :results silent
|
||
cat > ~/.dictd.conf <<EOF
|
||
# https://www.masteringemacs.org/article/wordsmithing-in-emacs
|
||
database devil {
|
||
data "${HOME}/.local/share/dictd/devil.dict.dz"
|
||
index "${HOME}/.local/share/dictd/devil.index"
|
||
}
|
||
database foldoc {
|
||
data "${HOME}/.local/share/dictd/foldoc.dict.dz"
|
||
index "${HOME}/.local/share/dictd/foldoc.index"
|
||
}
|
||
database gcide {
|
||
data "${HOME}/.local/share/dictd/gcide.dict.dz"
|
||
index "${HOME}/.local/share/dictd/gcide.index"
|
||
}
|
||
database jargon {
|
||
data "${HOME}/.local/share/dictd/jargon.dict.dz"
|
||
index "${HOME}/.local/share/dictd/jargon.index"
|
||
}
|
||
database vera {
|
||
data "${HOME}/.local/share/dictd/vera.dict.dz"
|
||
index "${HOME}/.local/share/dictd/vera.index"
|
||
}
|
||
database wn {
|
||
data "${HOME}/.local/share/dictd/wn.dict.dz"
|
||
index "${HOME}/.local/share/dictd/wn.index"
|
||
}
|
||
EOF
|
||
#+end_src
|
||
|
||
#+caption[Run =dictd= in debug mode on =Darwin=]:
|
||
#+caption: Run =dictd= in debug mode on =Darwin=.
|
||
#+name: lst:darwin-dictd-launch
|
||
#+begin_src shell -n :eval never :tangle no
|
||
# https://jpmens.net/2020/03/08/looking-up-words-with-dict/
|
||
/usr/local/sbin/dictd \
|
||
--config .dictd.conf \
|
||
--verbose \
|
||
--logfile .dictd.log \
|
||
-d nodetach
|
||
#+end_src
|
||
|
||
*** [[https://github.com/tecosaur/lexic#readme][Emacs LEXICal information viewer]] :noexport:
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: sec:writing-lexic
|
||
:header-args:emacs-lisp: :tangle no
|
||
:END:
|
||
|
||
The [[https://github.com/tecosaur/lexic#readme][Emacs LEXICal information viewer]] is a front-end for offline dictionary,
|
||
etymology, or thesaurus back-ends (currently limited to [[https://en.wikipedia.org/wiki/Sdcv][sdcv]]). Listing
|
||
[[lst:configure-lexic]] configures [[https://github.com/tecosaur/lexic#readme][lexic]].
|
||
|
||
#+attr_latex: :options breaklines
|
||
#+caption[Configure =lexic=]:
|
||
#+caption: Configure =lexic=.
|
||
#+name: lst:configure-lexic
|
||
#+begin_src emacs-lisp -n :results silent
|
||
(when (ensure-package-installation 'lexic)
|
||
;; http://download.huzheng.org/fr/
|
||
;; https://polyglotte.tuxfamily.org/doku.php?id=donnees:dicos_bilingues
|
||
;; http://download.huzheng.org/dict.org/stardict-dictd-web1913-2.4.2.tar.bz2
|
||
;; http://download.huzheng.org/dict.org/stardict-dictd_www.dict.org_gcide-2.4.2.tar.bz2
|
||
(with-eval-after-load 'lexic
|
||
(if-let ((lpp (executable-find "sdcv")))
|
||
(setopt
|
||
lexic-program-path lpp
|
||
lexic-dictionary-alist '(("full" . t))
|
||
;; sdcv --data-dir .local/share -l
|
||
lexic-dictionary-list '("Webster's Revised Unabridged Dictionary (1913)"
|
||
"dictd_www.dict.org_gcide")
|
||
lexic-dictionary-path "~/.local/share")
|
||
(message "`lexic' fails to find the `sdcv' executable"))))
|
||
#+end_src
|
||
|
||
*** [[https://wordnet.princeton.edu/][Wordnet]] :noexport:
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: sec:writing-wordnet
|
||
:END:
|
||
|
||
The [[https://github.com/gromnitsky/wordnut#readme][wordnut]] package is a major mode interface to [[https://wordnet.princeton.edu/][Wordnet]], a lexical database for
|
||
the English language. Listing [[lst:check-wordnut]] checks whether the system meets
|
||
the [[https://github.com/gromnitsky/wordnut#readme][wordnut]] prerequisites.
|
||
|
||
#+caption[System check for =wordnut=]:
|
||
#+caption: System check for =wordnut=.
|
||
#+name: lst:check-wordnut
|
||
#+begin_src emacs-lisp -n :results silent :tangle no
|
||
(when (ensure-package-installation 'wordnut)
|
||
(with-eval-after-load 'wordnut
|
||
(if-let ((wn (executable-find "wn")))
|
||
(setopt wordnut-cmd wn)
|
||
(message "`wordnut' fails to find the `wn' executable"))))
|
||
#+end_src
|
||
|
||
*** [[https://github.com/bnbeckwith/writegood-mode#readme][Writegood mode]]
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: sec:writing-writegood-mode
|
||
:END:
|
||
|
||
[[https://github.com/bnbeckwith/writegood-mode#readme][Writegood mode]] is a minor mode to aid in finding common writing problems. The post
|
||
[[http://matt.might.net/articles/shell-scripts-for-passive-voice-weasel-words-duplicates/][Matt Might's "My Ph.D. advisor rewrote himself in bash" scripts]] inspired this mode.
|
||
Listing [[lst:configure-writegood-mode]] configures [[https://github.com/bnbeckwith/writegood-mode#readme][writegood mode]].
|
||
|
||
#+caption[Configure =writegood-mode=]:
|
||
#+caption: Configure =writegood-mode=.
|
||
#+name: lst:configure-writegood-mode
|
||
#+begin_src emacs-lisp -n :results silent
|
||
(when (and (ensure-package-installation 'writegood-mode)
|
||
(fboundp 'writegood-mode))
|
||
(global-set-key (kbd "C-c g") #'writegood-mode))
|
||
#+end_src
|
||
|
||
* Programming Tools
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: sec:programming-tools
|
||
:END:
|
||
|
||
** [[info:eglot#Top][Eglot (info)]]
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: sec:eglot
|
||
:END:
|
||
|
||
[[https://github.com/joaotavora/eglot#readme][Emacs polyGLOT (Eglot)]] is an Emacs language-server-protocol client that stays
|
||
out of your way. [[info:eglot#Top][Eglot (info)]] is a builtin since Emacs-29.0.60. The following
|
||
listings contribute to a programming language mode independent [[https://github.com/joaotavora/eglot][Eglot]]
|
||
configuration:
|
||
1. Listing [[lst:minimal-eglot-setup][minimal Eglot setup]] ensures installation of [[https://github.com/joaotavora/eglot][Eglot]], shows how to get
|
||
debug information from the [[https://github.com/python-lsp/python-lsp-server][Python LSP server]], and adds key bindings to =eglot-mode-keymap=.
|
||
2. Listing [[lst:help-setup-org-src-mode-for-eglot]] and
|
||
[[lst:setup-python-org-src-mode-for-eglot]] try to prepare any =org-src-mode=
|
||
buffers for use with [[https://github.com/joaotavora/eglot][Eglot]]. They are a refactored implementation of the post
|
||
[[https://www.reddit.com/r/emacs/comments/w4f4u3/using_rustic_eglot_and_orgbabel_for_literate/][Using rustic, eglot, and org-babel with LSP support in Emacs]] for my use with
|
||
Python.
|
||
3. Listing [[lst: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 use* =format-all-buffer= *on such buffers*, since =format-all-buffer= does not
|
||
handle the narrowing.
|
||
4. Listing [[lst:eglot-maybe-ensure]] starts [[https://github.com/joaotavora/eglot][Eglot]] in case of proper programming
|
||
modes and proper directory local variables (meaning in presence of a proper
|
||
file [[info:emacs#Directory Variables][.dir-locals.el]] in the root directory of any project using proper
|
||
programming modes).
|
||
|
||
#+caption[Ensure =eglot= installation with minimal setup]:
|
||
#+caption: Ensure =eglot= installation with minimal setup.
|
||
#+name: lst:minimal-eglot-setup
|
||
#+begin_src emacs-lisp -n :results silent
|
||
(with-eval-after-load 'emacs
|
||
;; Replace `nil' with `t' for debugging.
|
||
(when nil
|
||
(when (require 'eglot nil t)
|
||
(setq eglot-server-programs
|
||
`(((python-ts-mod python-mode)
|
||
. ,(eglot-alternatives
|
||
'(("pylsp" "-vv")
|
||
("ruff-lsp")))))))))
|
||
|
||
(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))
|
||
#+end_src
|
||
|
||
#+caption[Help to setup any =org-src-mode= buffers for =eglot=]:
|
||
#+caption: Help to setup any =org-src-mode= buffers for =eglot=.
|
||
#+name: lst:help-setup-org-src-mode-for-eglot
|
||
#+begin_src emacs-lisp -n :results silent
|
||
(with-eval-after-load 'emacs
|
||
(defcustom eglot-maybe-ensure-modes '(python-mode)
|
||
"Modes where maybe `eglot-ensure' should be or has been called.
|
||
This may be in the case of proper directory local variables or in
|
||
the case of proper `org-src-mode' buffers.")
|
||
|
||
;; https://www.reddit.com/r/emacs/comments/w4f4u3
|
||
;; /using_rustic_eglot_and_orgbabel_for_literate/
|
||
(defun eglot-org-babel-edit-prep (info)
|
||
"Try to setup an `org-mode-src' buffer to make `eglot-ensure' succeed.
|
||
INFO has a form similar to the return value of
|
||
`org-babel-get-src-block-info'. Try to load the tangled file
|
||
into the `org-src-mode' buffer as well as to narrow the region to
|
||
the Org-mode source block code before calling `eglot-ensure'."
|
||
(unless (bound-and-true-p org-src-mode)
|
||
(user-error "Buffer %s is no `org-src-mode' buffer" (buffer-name)))
|
||
(let ((mark (point))
|
||
(body (nth 1 info))
|
||
(filename (cdr (assq :tangle (nth 2 info)))))
|
||
(when (string= filename "no")
|
||
(user-error "Org source block has no tangled file"))
|
||
(setq filename (expand-file-name filename))
|
||
(unless (file-readable-p filename)
|
||
(user-error "Tangled file %s is not readable" filename))
|
||
(with-temp-buffer
|
||
(insert-file-contents filename 'visit nil nil 'replace)
|
||
(unless (search-forward body nil 'noerror)
|
||
(user-error "Org source block does not occur in tangled file %s"
|
||
filename))
|
||
(when (search-forward body nil 'noerror)
|
||
(user-error "Org source block occurs twice or more in tangled file %s"
|
||
filename)))
|
||
(goto-char (point-min))
|
||
(insert-file-contents filename 'visit nil nil 'replace)
|
||
(let ((max-point (search-forward body))
|
||
(min-point (search-backward body)))
|
||
(narrow-to-region min-point max-point))
|
||
(goto-char mark)
|
||
(eglot-ensure))))
|
||
#+end_src
|
||
|
||
#+caption[Setup Python =org-src-mode= buffers for =eglot=]:
|
||
#+caption: Setup Python =org-src-mode= buffers for =eglot=.
|
||
#+name: lst:setup-python-org-src-mode-for-eglot
|
||
#+begin_src emacs-lisp -n :results silent
|
||
(with-eval-after-load 'emacs
|
||
;; https://www.reddit.com/r/emacs/comments/w4f4u3
|
||
;; /using_rustic_eglot_and_orgbabel_for_literate/
|
||
(defun undo-eglot-org-babel-edit-prep()
|
||
"Undo the `eglot' setup by deleting the text hidden by narrowing.
|
||
This is to advice `org-edit-src-exit' and `org-edit-src-save'."
|
||
(when (and (bound-and-true-p org-src-mode)
|
||
(buffer-file-name)
|
||
(apply #'derived-mode-p eglot-maybe-ensure-modes))
|
||
(save-excursion
|
||
(goto-char (point-min))
|
||
(save-restriction
|
||
(widen)
|
||
(delete-region (point-min) (point)))
|
||
(goto-char (point-max))
|
||
(save-restriction
|
||
(widen)
|
||
(delete-region (point) (point-max))))))
|
||
|
||
(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))
|
||
#+end_src
|
||
|
||
#+caption[Experimental =narrow-format-all:python=]:
|
||
#+caption: Experimental =narrow-format-all:python=.
|
||
#+name: lst:narrow-format-all-python
|
||
#+begin_src emacs-lisp -n :results silent
|
||
(when (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)))))))))
|
||
#+end_src
|
||
|
||
#+caption[Start =eglot= in case of a proper =dir-local-variables-alist=]:
|
||
#+caption: Start =eglot= in case of a proper =dir-local-variables-alist=.
|
||
#+name: lst:eglot-maybe-ensure
|
||
#+begin_src emacs-lisp -n :results silent
|
||
(with-eval-after-load 'emacs
|
||
(defun eglot-maybe-ensure ()
|
||
(when (and (apply #'derived-mode-p eglot-maybe-ensure-modes)
|
||
(assoc 'eglot-workspace-configuration dir-local-variables-alist))
|
||
(eglot-ensure)))
|
||
|
||
;; The two hooks `after-change-major-mode-hook' and
|
||
;; `hack-local-variables-hook' are OK, but language mode hooks like
|
||
;; `python-mode-hook' are not.
|
||
(add-hook 'after-change-major-mode-hook #'eglot-maybe-ensure))
|
||
#+end_src
|
||
|
||
** [[https://github.com/lassik/emacs-format-all-the-code#readme][Format-all]]
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: sec:format-all
|
||
:END:
|
||
|
||
Listing [[lst:configure-format-all]]:
|
||
1. Configures [[https://github.com/lassik/emacs-format-all-the-code#readme][format-all]] which is a package that provides an universal interface
|
||
to code formatters of more than 60 computer languages.
|
||
2. Adds =format-all-org-babel-post-tangle= to =org-babel-post-tangle-hook= to
|
||
format tangled Python code.
|
||
|
||
#+caption[Configure =format-all=]:
|
||
#+caption: Configure =format-all=.
|
||
#+name: lst:configure-format-all
|
||
#+begin_src emacs-lisp -n :results silent
|
||
;; https://github.com/lassik/emacs-format-all-the-code#readme
|
||
;; https://ianyepan.github.io/posts/format-all/
|
||
;; https://jamesaimonetti.com/posts/formatting-tangled-output-in-org-mode/
|
||
(when (ensure-package-installation 'format-all)
|
||
;; (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)))))))
|
||
#+end_src
|
||
|
||
** [[info:flymake#Top][Flymake (info)]]
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: sec:flymake
|
||
:END:
|
||
|
||
Flymake is an universal on-the-fly syntax checker for Emacs. It is a requirement
|
||
of [[https://github.com/joaotavora/eglot][eglot]], but you can use it without [[https://github.com/joaotavora/eglot][eglot]] for instance in [[https://git.savannah.gnu.org/cgit/emacs.git/tree/lisp/progmodes/python.el][python-mode]] buffers
|
||
that do not visit a file.
|
||
|
||
* Programming Modes
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: sec:programming-languages
|
||
:END:
|
||
|
||
** [[https://dept-info.labri.fr/~strandh/Teaching/PFS/Common/Strandh-Tutorial/Dir-symbolic.html][Common Lisp programming]]
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: sec:common-lisp-programming
|
||
:END:
|
||
|
||
Listing [[lst:configure-sly]] configures the [[info:sly#Top][Sly (info)]] Common Lisp IDE for Emacs
|
||
for use with [[http://www.sbcl.org/][Steel Bank Common Lisp (sbcl)]]:
|
||
1. It configures =sly-default-lisp= and =sly-lisp-implementations= as in the ~sly~ documentation string instead of in the [[info:sly#Multiple Lisps][multiple lisps (info)]] manual.
|
||
2. It ensures the [[info:sly#Auto-SLY][automatic connection to the lisp server (info)]] when opening a
|
||
Common Lisp file.
|
||
3. It configures searching documentation in the [[http://www.lispworks.com/documentation/HyperSpec/Front/][Common Lisp HyperSpec]] according
|
||
to the [[info:sly#Basic customization][basic customization (info)]] manual.
|
||
Finally, listing [[lst:configure-sly]] uses a technique to [[info:sly#Loading Slynk faster][load Slynk faster (info)]]
|
||
by means of a custom core file in src_emacs-lisp{no-littering-var-directory}.
|
||
Listing [[lst:sbcl-core-for-sly]] tangles to a script to dump such a [[http://www.sbcl.org/][SBCL]] core.
|
||
|
||
#+caption[Configure =sly=]:
|
||
#+caption: Configure =sly=.
|
||
#+name: lst:configure-sly
|
||
#+begin_src emacs-lisp -n :results silent
|
||
(when (ensure-package-installation 'sly)
|
||
(with-eval-after-load 'sly
|
||
;; Set `sly-default-lisp' instead of `inferior-lisp-program',
|
||
;; because `sly' uses `inferior-lisp-program' only as a backwards
|
||
;; compatibility fallback option.
|
||
;; The warning "Value ‘sbcl’ does not match type function" is due to
|
||
;; the buggy `defcustom' type of `sbcl-default-lisp' in `sly.el'.
|
||
(setopt sly-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)))
|
||
#+end_src
|
||
|
||
#+caption[Script to dump a SBCL core for the Sly Common Lisp IDE]:
|
||
#+caption: Script to dump a SBCL core for the Sly Common Lisp IDE.
|
||
#+header: :tangle-mode (identity #o755)
|
||
#+name: lst:sbcl-core-for-sly
|
||
#+begin_src shell -n :noeval :tangle ~/bin/sbcl.core-for-sly
|
||
#!/bin/sh
|
||
|
||
sbcl <<EOF
|
||
(mapc 'require '(sb-bsd-sockets sb-posix sb-introspect sb-cltl2 asdf))
|
||
(save-lisp-and-die "sbcl.core-for-sly")
|
||
EOF
|
||
|
||
# Local Variables:
|
||
# mode: shell-script
|
||
# sh-indentation: 2
|
||
# sh-basic-offset: 2
|
||
# End:
|
||
#+end_src
|
||
|
||
*** [[https://courses.cs.northwestern.edu/325/][CS 325 AI Programming]]
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: sec:cs-325-ai-programming
|
||
:END:
|
||
|
||
The [[https://courses.cs.northwestern.edu/325/][CS 325 AI Programming]] course allows to learn Common Lisp by self-study and
|
||
the page [[https://courses.cs.northwestern.edu/325/admin/lisp-setup.php][CS 325: Setting up Lisp]] gives instructions how to:
|
||
1. [[https://courses.cs.northwestern.edu/325/admin/lisp-setup.php#lisp][Download and install common lisp]].
|
||
2. [[https://courses.cs.northwestern.edu/325/admin/lisp-setup.php#quicklisp][Install Quicklisp]].
|
||
3. [[https://courses.cs.northwestern.edu/325/admin/lisp-setup.php#install-325][Install the CS325 library]].
|
||
|
||
*** [[https://www.quicklisp.org/][Quicklisp]]
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: sec:quicklisp
|
||
:END:
|
||
|
||
[[https://www.quicklisp.org/][Quicklisp]] is a library manager for Common Lisp. Listing
|
||
[[lst:download+verify-quicklisp]] downloads the [[https://www.quicklisp.org/][Quicklisp]] file and verifies its
|
||
signature to prevent tampering. Listing [[lst:bootstrap-quicklisp]] tangles to a
|
||
shell script allowing to bootstrap [[https://www.quicklisp.org/][Quicklisp]] with [[http://www.sbcl.org/][SBCL]]. Listing
|
||
[[lst:quicklisp-sbclrc-file]] tangles to the a [[http://www.sbcl.org/][SBCL]] resource file with [[https://www.quicklisp.org/][Quicklisp]]
|
||
support.
|
||
|
||
#+caption[Download and verify =quicklisp=]:
|
||
#+caption: Download and verify =quicklisp=.
|
||
#+name: lst:download+verify-quicklisp
|
||
#+begin_src shell -n :dir ~ :results none :tangle no
|
||
curl -sS -O https://beta.quicklisp.org/quicklisp.lisp
|
||
curl -sS -O https://beta.quicklisp.org/quicklisp.lisp.asc
|
||
curl -sS -O https://beta.quicklisp.org/release-key.txt
|
||
gpg --import release-key.txt
|
||
gpg --verify quicklisp.lisp.asc quicklisp.lisp
|
||
#+end_src
|
||
|
||
#+caption[Bootstrap =quicklisp=]:
|
||
#+caption: Bootstrap =quicklisp=.
|
||
#+header: :tangle-mode (identity #o755)
|
||
#+name: lst:bootstrap-quicklisp
|
||
#+begin_src shell -n :noeval :tangle ~/bin/quicklisp-sbcl-bootstrap
|
||
#!/bin/sh
|
||
|
||
sbcl --load ~/quicklisp.lisp <<EOF
|
||
(quicklisp-quickstart:install)
|
||
(quit)
|
||
EOF
|
||
|
||
# Local Variables:
|
||
# mode: shell-script
|
||
# sh-indentation: 2
|
||
# sh-basic-offset: 2
|
||
# End:
|
||
#+end_src
|
||
|
||
#+caption[A =quicklisp= sbclrc file]:
|
||
#+caption: A =quicklisp= sbclrc file.
|
||
#+name: lst:quicklisp-sbclrc-file
|
||
#+begin_src lisp -n :eval never-export :tangle ~/.sbclrc
|
||
;;; 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)))
|
||
#+end_src
|
||
|
||
** [[info:eintr#Top][Emacs Lisp Programming (info)]]
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: sec:emacs-lisp-programming
|
||
:END:
|
||
|
||
Here is a list of links describing how to program and debug [[info:elisp#Top][Emacs Lisp (info)]] code:
|
||
1. [[https://www.masteringemacs.org/article/evaluating-elisp-emacs][Evaluating Elisp in Emacs]]
|
||
2. [[https://endlessparentheses.com/debugging-emacs-lisp-part-1-earn-your-independence.html][Debugging Elisp Part 1: Earn your independence]]
|
||
3. [[https://endlessparentheses.com/debugging-elisp-part-2-advanced-topics.html][Debugging Elisp Part 2: Advanced topics]]
|
||
4. [[http://xahlee.info/talk_show/xah_talk_show_2022-01-20.html][Xah talk show: Elisp coding: xah-add-space-after-comma]]
|
||
5. [[http://xahlee.info/talk_show/xah_talk_show_2022-01-22.html][Xah talk show: Elisp coding: narrow-to-region, sort-lines, hilight-unicode]]
|
||
|
||
Ref. [cite:@Monnier.ACM-PL.4.1] exposes the evolution of Emacs Lisp and explains
|
||
many Emacs Lisp idioms.
|
||
|
||
Listing [[lst:setup-ielm][setup ielm]] configures the [[https://wikemacs.org/wiki/IELM][Interactive Emacs Lisp Mode]] for better
|
||
interoperability with [[https://smartparens.readthedocs.io/en/latest/][Smartparens]]: get help on the key bindings by means of
|
||
src_emacs-lisp{(describe-function 'inferior-emacs-lisp-mode)}.
|
||
|
||
#+caption[Setup =ielm=]:
|
||
#+caption: Setup =ielm=.
|
||
#+name: lst:setup-ielm
|
||
#+begin_src emacs-lisp -n :results silent
|
||
(with-eval-after-load 'ielm
|
||
(setopt ielm-dynamic-return nil))
|
||
#+end_src
|
||
|
||
** [[https://fennel-lang.org/][Fennel Programming]]
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: sec:fennel-programming
|
||
:END:
|
||
|
||
Here is a list of links describing how to install and setup [[https://fennel-lang.org/][Fennel]] and how to
|
||
install and use [[https://gitlab.com/andreyorst/ob-fennel][ob-fennel]] with the [[https://love2d.org/][LÖVE 2D game engine]]:
|
||
1. [[https://fennel-lang.org/][The Fennel Programming Language]]
|
||
2. [[https://fennel-lang.org/setup][Setting up Fennel]]
|
||
3. [[https://gitlab.com/andreyorst/ob-fennel][Org Babel support for the Fennel language: ob-fennel]]
|
||
4. [[https://andreyorst.gitlab.io/posts/2022-09-26-reproducible-research-with-org-mode-fennel-and-love/][Reproducible Research with Org Mode, Fennel, and LÖVE]]
|
||
|
||
#+caption[Install =fennel-mode= and =ob-fennel= when =fennel= is an executable]:
|
||
#+caption: Install =fennel-mode= and =ob-fennel= when =fennel= is an executable.
|
||
#+name: lst:install-ob-fennel-mode
|
||
#+begin_src emacs-lisp -n :results silent
|
||
(when (and (executable-find "fennel")
|
||
(ensure-package-installation 'fennel-mode))
|
||
(unless (package-installed-p 'ob-fennel)
|
||
(package-vc-install "https://gitlab.com/andreyorst/ob-fennel"))
|
||
(when (package-installed-p 'ob-fennel)
|
||
(ensure-package-installation 'ob-fennel)
|
||
(set-org-babel-language-activity 'fennel (fboundp 'fennel-mode))))
|
||
#+end_src
|
||
|
||
#+caption[Fennel example]:
|
||
#+caption: Fennel example.
|
||
#+header: :var x=10 y=20
|
||
#+name: lst:fennel-example
|
||
#+begin_src fennel -n :eval never-export :results silent :session fennel
|
||
(+ x y)
|
||
#+end_src
|
||
|
||
#+caption[Install =Fennel= with luarocks on Darwin]:
|
||
#+caption: Install =Fennel= with luarocks on Darwin.
|
||
#+name: lst:install-fennel-on-darwin
|
||
#+begin_src shell -n :eval never :results silent :tangle no
|
||
luarocks \
|
||
install --local readline \
|
||
HISTORY_INCDIR=/usr/local/opt/readline/include \
|
||
HISTORY_DIR=/usr/local/opt/readline/lib
|
||
#+end_src
|
||
|
||
** [[https://www.lua.org/][Lua Programming]]
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: sec:lua-programming
|
||
:END:
|
||
|
||
#+caption[Install =lua-mode= in case of =Lua=]:
|
||
#+caption: Install =lua-mode= in case of =Lua=.
|
||
#+name: lst:install-lua-mode
|
||
#+begin_src emacs-lisp -n :results silent
|
||
(when (and (executable-find "lua")
|
||
(ensure-package-installation 'company-lua 'lua-mode)))
|
||
#+end_src
|
||
|
||
#+caption[Lua example]:
|
||
#+caption: Lua example.
|
||
#+name: lst:lua-example
|
||
#+begin_src lua -n :results silent :var x=10 y=20
|
||
return (x+y)
|
||
#+end_src
|
||
|
||
** [[info:maxima#Top][Maxima Programming (info)]]
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: sec:maxima-programming
|
||
:END:
|
||
|
||
Listing [[lst:configure-maxima]] configures [[https://gitlab.com/sasanidas/maxima][Maxima Mode]]. The following list contains
|
||
more information:
|
||
1. [[https://gitlab.com/sasanidas/maxima][Maxima Mode]] is an Emacs interface to the computer algebra system [[https://en.wikipedia.org/wiki/Maxima_(software)][Maxima]].
|
||
2. [[https://orgmode.org/worg/org-contrib/babel/languages/ob-doc-maxima.html][Maxima Source Code Blocks in Org Mode]] shows how to use [[https://en.wikipedia.org/wiki/Maxima_(software)][Maxima]] in [[https://orgmode.org/][Org Mode]].
|
||
3. [[https://home.csulb.edu/~woollett/mbe.html][Maxima By Example]] is a tutorial for [[https://en.wikipedia.org/wiki/Maxima_(software)][Maxima]].
|
||
Listing [[lst:maxima-example]] is a minimal example of how to use how to use [[https://en.wikipedia.org/wiki/Maxima_(software)][Maxima]]
|
||
in [[https://orgmode.org/][Org Mode]].
|
||
|
||
#+caption[Configure =maxima=]:
|
||
#+caption: Configure =maxima=.
|
||
#+name: lst:configure-maxima
|
||
#+begin_src emacs-lisp -n :results silent
|
||
(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)))
|
||
#+end_src
|
||
|
||
#+caption[Maxima example]:
|
||
#+caption: Maxima example.
|
||
#+name: lst:maxima-example
|
||
#+begin_src maxima -n :exports code :results silent
|
||
print(1+1);
|
||
#+end_src
|
||
|
||
** [[https://www.seas.upenn.edu/~chaoliu/2017/09/01/python-programming-in-emacs/][Python programming]]
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: sec:python-programming
|
||
:END:
|
||
|
||
The [[https://www.emacswiki.org/emacs/PythonProgrammingInEmacs][Python Programming in Emacs]] wiki page lists options to enhance Emacs's
|
||
built-in ~python-mode~. Here, the focus is on two Emacs packages and four Python
|
||
packages:
|
||
1. [[#sec:eglot][Eglot - Emacs polyGLOT: a builtin LSP client since Emacs-29.0.60]]. The
|
||
maintainer also contributes to Emacs itself and has a deep understanding of
|
||
[[https://sheer.tj/the_way_of_emacs.html][the Way of Emacs]]. He refuses to add new features without seeing how they fit
|
||
into [[https://sheer.tj/the_way_of_emacs.html][the Way of Emacs]] as this discussion on [[https://github.com/joaotavora/eglot/issues/523][org-mode source code blocks]]
|
||
shows.
|
||
2. [[https://jedi.readthedocs.io/en/latest/][Jedi]] is a static analysis tool for Python that is typically used in plugins
|
||
for editors or integrated development environments. Jedi has a focus on
|
||
autocompletion and object definition lookup functionality.
|
||
3. [[https://github.com/charliermarsh/ruff#readme][Ruff]] is an extremely fast Python linter and a replacement for [[https://flake8.pycqa.org/en/latest/][Flake8]] with a
|
||
variety of its plugins. [[https://notes.crmarsh.com/][Charlie Marsh]] explains why he started [[https://pypi.org/project/ruff/][Ruff]] in the
|
||
post [[https://notes.crmarsh.com/python-tooling-could-be-much-much-faster][Python tooling could be much faster]].
|
||
4. [[https://github.com/python-lsp/python-lsp-server#readme][Python LSP Server]] is in my opinion the most complete [[https://en.wikipedia.org/wiki/Language_Server_Protocol][Language Server Protocol]]
|
||
implementation for Python and it handles [[https://github.com/charliermarsh/ruff#readme][Ruff]] thanks to the [[https://github.com/python-lsp/python-lsp-ruff][python-lsp-ruff]]
|
||
plugin.
|
||
5. [[https://github.com/astoff/code-cells.el#readme][Code-cells]] allows to edit [[https://ipython.org/notebook.html][IPython or Jupyter notebooks]] in Emacs with help
|
||
from either [[https://github.com/mwouts/jupytext][Jupytext]] or [[https://pandoc.org/][Pandoc]].
|
||
|
||
Here are links covering how to integrate Emacs, Python and a Python LSP
|
||
server, before plunging into the configuration steps:
|
||
1. [[https://taingram.org/blog/emacs-lsp-ide.html][Building Your Own Emacs IDE with LSP]]
|
||
2. [[https://rgoswami.me/posts/emacs-lang-servers/][Doom Emacs and Language Servers]]
|
||
3. [[https://ddavis.io/posts/eglot-python-ide/][Eglot based Emacs Python IDE]]
|
||
4. [[https://www.mattduck.com/lsp-python-getting-started.html][Getting started with lsp-mode for Python]]
|
||
5. [[https://ddavis.io/posts/python-emacs-3/][Python & Emacs, Take 3]]
|
||
6. [[https://ddavis.io/posts/emacs-python-lsp/][Python with Emacs: py(v)env and lsp-mode]]
|
||
|
||
*** [[https://git.savannah.gnu.org/cgit/emacs.git/tree/lisp/progmodes/python.el][Python-mode]]
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: sec:python-mode
|
||
:END:
|
||
|
||
Listing [[lst:setup-python-mode][setup Python mode]] and [[lst:choose-common-python-interpreter][choose common Python interpreter]] select a common
|
||
Python interpreter in a virtual environment for use in =python-mode= and
|
||
=ob-python= by means of src_emacs-lisp{(choose-common-python-interpreter)}. The
|
||
[[https://github.com/pythonic-emacs/pythonic#readme][pythonic]], [[https://github.com/jorgenschaefer/pyvenv#readme][pyvenv]], and [[https://github.com/jorgenschaefer/pyvenv#readme][pyvenv]] packages allow to handle Python virtual
|
||
environments within Emacs. The [[https://github.com/pyenv/pyenv][pyenv]] package provides support to work with
|
||
[[https://github.com/pyenv/pyenv#readme][pyenv]] (eventually with [[https://github.com/pyenv/pyenv-virtualenv#readme][pyenv-virtualenv]]) to select between different python
|
||
versions (eventually each with different environments). In the end, all those
|
||
packages do is to set =python-shell-virtualenv-root= (in case of [[https://github.com/pyenv/pyenv#readme][pyenv]] and
|
||
[[https://github.com/pythonic-emacs/pythonic#readme][pythonic]]) or tweak the environment variables and restart the relevant Python
|
||
child processes (in case of [[https://github.com/jorgenschaefer/pyvenv#readme][pyvenv]]). Therefore, I replace those packages with
|
||
listing [[lst:access-pyenv][access pyenv]] and [[lst:select-python-virtual-environment][select a Python interpreter in a virtual environment]] to
|
||
set =python-shell-virtualenv-root= from within Emacs.
|
||
|
||
Listing [[lst:setup-python-mode][setup Python mode]] and [[lst:choose-common-python-linter][choose common Python linter]] select a common Python
|
||
linter for the =python-check-command= and the =python-flymake-command= by means
|
||
of src_emacs-lisp{(choose-common-python-linter)} from:
|
||
1. [[https://github.com/PyCQA/pyflakes][Pyflakes - simple tool which checks Python source files for errors]].
|
||
2. [[https://flake8.pycqa.org/en/latest/][Flake8 - your tool for style guide enforcement]].
|
||
3. [[https://github.com/charliermarsh/ruff][Ruff - an extremely fast Python linter written in Rust]].
|
||
|
||
Listing [[lst:pyproject-toml-kickoff][kickoff pyproject.toml proposal]] facilitates dropping a [[https://pip.pypa.io/en/stable/reference/build-system/pyproject-toml][pyproject.toml]]
|
||
file into Python projects which anyhow should switch to using a [[https://pip.pypa.io/en/stable/reference/build-system/pyproject-toml][pyproject.toml]]
|
||
file as explained in the post [[https://bbc.github.io/cloudfit-public-docs/packaging/this_way_up.html][This Way Up: A Bottom-Up Look At Python Packaging]].
|
||
The [[https://packaging.python.org/en/latest/tutorials/packaging-projects/][packaging Python projects tutorial]] and the [[https://github.com/pypa/sampleproject][Python sample project]] walk you
|
||
through the process of writing a [[https://pip.pypa.io/en/stable/reference/build-system/pyproject-toml][pyproject.toml]] file. [[https://github.com/charliermarsh/ruff#does-ruff-support-numpy--or-google-style-docstrings][Ruff docstring setup]]
|
||
explains how to setup documentation string checking in the [[https://pip.pypa.io/en/stable/reference/build-system/pyproject-toml][pyproject.toml]] file.
|
||
|
||
Listing [[lst:setup-cfg-kickoff][kickoff setup.cfg proposal]] implements the [[https://black.readthedocs.io/en/stable/guides/using_black_with_other_tools.html][using black with other tools]]
|
||
rules which make [[https://flake8.pycqa.org/en/latest/][flake8]] or [[https://pycodestyle.pycqa.org/en/latest/][pycodestyle]] agree with [[https://black.readthedocs.io/en/stable/index.html][black's uncompromising style]].
|
||
|
||
Finally, listing [[lst:flake8-nocolor][flake8-nocolor]] and [[lst:ruff-nocolor][ruff-nocolor]] pipe the =stdout= output of the
|
||
[[https://pypi.org/project/flake8/][flake8]] and [[https://pypi.org/project/ruff/][ruff]] executables through =cat= to remove escape sequences.
|
||
|
||
#+caption[Choose a common Python interpreter]:
|
||
#+caption: Choose a common Python interpreter for =ob-python= and =python-mode=.
|
||
#+name: lst:choose-common-python-interpreter
|
||
#+begin_src emacs-lisp -n :results silent
|
||
(defun choose-common-python-interpreter (&optional interpreter)
|
||
"Let `ob-python' and `python-mode' use the same Python INTERPRETER."
|
||
(interactive)
|
||
(unless (featurep 'ob-python)
|
||
(set-org-babel-language-activity 'python (fboundp 'python-mode)))
|
||
(let* ((prompt (format "Choose Python (%s): "
|
||
(bound-and-true-p python-shell-interpreter)))
|
||
(choices '(ipython python))
|
||
(choice (if (member interpreter choices)
|
||
(symbol-name interpreter)
|
||
(completing-read prompt choices nil t))))
|
||
(when (boundp 'org-babel-python-command)
|
||
(pcase choice
|
||
("python"
|
||
(setopt org-babel-python-command
|
||
(concat (or (executable-find "python3")
|
||
(executable-find "python"))
|
||
" -E")))
|
||
("ipython"
|
||
(setopt org-babel-python-command
|
||
(concat (or (executable-find "ipython3")
|
||
(executable-find "ipython"))
|
||
" --simple-prompt --HistoryAccessor.enabled=False"))))
|
||
(message "Now `org-babel-python-command' equals %S"
|
||
org-babel-python-command))
|
||
(when (boundp 'python-shell-interpreter)
|
||
(pcase choice
|
||
("python"
|
||
(setopt python-shell-interpreter (or (executable-find "python3")
|
||
(executable-find "python"))
|
||
python-shell-interpreter-args "-E -i"))
|
||
("ipython"
|
||
(setopt python-shell-interpreter (or (executable-find "ipython3")
|
||
(executable-find "ipython"))
|
||
python-shell-interpreter-args
|
||
"--simple-prompt --HistoryAccessor.enabled=False")))
|
||
(message "Now `python-shell-interpreter' equals %S"
|
||
python-shell-interpreter))))
|
||
#+end_src
|
||
|
||
#+caption[Choose a common Python linter]:
|
||
#+caption: Choose a common Python linter for =python-check-command= and
|
||
#+caption: =python-flymake-command=.
|
||
#+name: lst:choose-common-python-linter
|
||
#+begin_src emacs-lisp -n :results silent
|
||
(defun choose-common-python-linter (&optional linter)
|
||
"Let `python-check-command' and `python-flymake-command' use the same LINTER."
|
||
(interactive)
|
||
(let* ((prompt (format "Choose Python checker (%s): "
|
||
(bound-and-true-p python-check-command)))
|
||
(choices '(flake8-nocolor pyflakes ruff-nocolor))
|
||
(choice (if (member linter choices)
|
||
(symbol-name linter)
|
||
(completing-read prompt choices nil t))))
|
||
(when (and (boundp 'python-check-command) (boundp 'python-flymake-command))
|
||
(pcase choice
|
||
("flake8-nocolor"
|
||
(setopt python-check-command (executable-find choice)
|
||
python-flymake-command (list (executable-find choice) "-")))
|
||
("pyflakes"
|
||
(setopt python-check-command (executable-find choice)
|
||
python-flymake-command `(,(executable-find choice))))
|
||
("ruff-nocolor"
|
||
(setopt python-check-command (executable-find choice)
|
||
python-flymake-command (list (executable-find choice)
|
||
"--stdin-filename" "stdin" "-"))))
|
||
(when (bound-and-true-p python-check-custom-command)
|
||
(setq python-check-custom-command nil))
|
||
(message "Python checker commands are %S and %S"
|
||
python-check-command python-flymake-command))))
|
||
#+end_src
|
||
|
||
#+caption[Setup Python mode]:
|
||
#+caption: Setup Python mode.
|
||
#+name: lst:setup-python-mode
|
||
#+begin_src emacs-lisp -n :results silent
|
||
(with-eval-after-load 'python
|
||
(setopt python-indent-guess-indent-offset nil
|
||
python-shell-completion-native-disabled-interpreters '("ipython3"
|
||
"pypy"))
|
||
(choose-common-python-interpreter 'python)
|
||
(choose-common-python-linter 'ruff-nocolor))
|
||
#+end_src
|
||
|
||
#+caption[Access =pyenv=]:
|
||
#+caption: Access =pyenv=.
|
||
#+name: lst:access-pyenv
|
||
#+begin_src emacs-lisp -n :results silent
|
||
(when (executable-find "pyenv")
|
||
(defun pyenv-full-path (version)
|
||
"Return the full path for VERSION."
|
||
(unless (string= version "system")
|
||
(concat (pyenv-root) (file-name-as-directory "versions") version)))
|
||
|
||
(defun pyenv-root ()
|
||
"Return \"pyenv root\" as a directory."
|
||
(cl-destructuring-bind (exit-code output)
|
||
(shell-command-with-exit-code "pyenv" "root")
|
||
(if (= 0 exit-code)
|
||
(file-name-as-directory (string-trim output))
|
||
(error "%s" (string-trim output)))))
|
||
|
||
(defun pyenv-version-name ()
|
||
"Return \"pyenv version-name\"."
|
||
(cl-destructuring-bind (exit-code output)
|
||
(shell-command-with-exit-code "pyenv" "version-name")
|
||
(if (= 0 exit-code)
|
||
(string-trim output)
|
||
(error "%s" (string-trim output)))))
|
||
|
||
(defun pyenv-versions ()
|
||
"Return \"pyenv versions --bare --skip-aliases\" as a list.
|
||
Complete the result with \"system\"."
|
||
(cl-destructuring-bind (exit-code output)
|
||
(shell-command-with-exit-code
|
||
"pyenv" "versions" "--bare" "--skip-aliases")
|
||
(if (= 0 exit-code)
|
||
(cons "system" (split-string output))
|
||
(error "%s" (string-trim output)))))
|
||
|
||
(defun pyenv-virtualenvs ()
|
||
"Return \"pyenv virtualenvs --bare --skip-aliases\" as a list."
|
||
(cl-destructuring-bind (exit-code output)
|
||
(shell-command-with-exit-code
|
||
"pyenv" "virtualenvs" "--bare" "--skip-aliases")
|
||
(if (= 0 exit-code)
|
||
(split-string output)
|
||
(error "%s" (string-trim output))))))
|
||
#+end_src
|
||
|
||
#+caption[Select the Python virtual environment]:
|
||
#+caption: Select the Python virtual environment.
|
||
#+name: lst:select-python-virtual-environment
|
||
#+begin_src emacs-lisp -n :results silent
|
||
(with-eval-after-load 'python
|
||
(when (cl-every #'fboundp '(pyenv-full-path
|
||
pyenv-version-name
|
||
pyenv-versions
|
||
pyenv-virtualenvs))
|
||
(setq python-shell-virtualenv-root
|
||
(pyenv-full-path (or (pyenv-version-name)
|
||
(car (pyenv-virtualenvs))
|
||
(car (pyenv-versions)))))
|
||
(message "Now `python-shell-virtualenv-root' equals %S"
|
||
python-shell-virtualenv-root)
|
||
|
||
(defun set-python-shell-virtualenv-root-to-pyenv-version ()
|
||
"Set `python-shell-virtual-env-root' to a pyenv version."
|
||
(interactive)
|
||
(let* ((version-name (pyenv-version-name))
|
||
(prompt (format "pyenv version (%s): " version-name))
|
||
(choices (pyenv-versions))
|
||
(version (completing-read prompt choices nil 'require-match)))
|
||
(unless (string= version-name version)
|
||
(setq python-shell-virtualenv-root (pyenv-full-path version))
|
||
(setenv "PYENV_VERSION" version))
|
||
(message "Now `python-shell-virtualenv-root' equals %S"
|
||
python-shell-virtualenv-root)))
|
||
|
||
(defun set-python-shell-virtualenv-root-to-pyenv-virtualenv ()
|
||
"Set `python-shell-virtual-env-root' to a pyenv virtualenv."
|
||
(interactive)
|
||
(let* ((version-name (pyenv-version-name))
|
||
(prompt (format "pyenv virtualenv (%s): " version-name))
|
||
(choices (pyenv-virtualenvs))
|
||
(version (completing-read prompt choices nil 'require-match)))
|
||
(unless (string= version-name version)
|
||
(setq python-shell-virtualenv-root (pyenv-full-path version))
|
||
(setenv "PYENV_VERSION" version))
|
||
(message "Now `python-shell-virtualenv-root' equals %S"
|
||
python-shell-virtualenv-root)))))
|
||
#+end_src
|
||
|
||
#+caption[Kickoff =pyproject.toml= proposal]:
|
||
#+caption: Kickoff =pyproject.toml= proposal.
|
||
#+name: lst:pyproject-toml-kickoff
|
||
#+begin_src toml -n :tangle pyproject.toml
|
||
# Use for instance "hatch new" to create or initialize a project.
|
||
# See: https://pypi.org/project/hatch
|
||
|
||
[tool.black]
|
||
line-length = 88
|
||
|
||
[tool.ruff]
|
||
line-length = 88
|
||
select = [
|
||
"ARG", # flake8-unused-arguments
|
||
"B", # flake8-bugbear
|
||
"C", # mccabe
|
||
"C4", # flake8-comprehensions
|
||
"E", # pycodestyle
|
||
"D", # pydocstyle
|
||
"F", # pyflakes
|
||
"UP", # pyupgrade
|
||
"W", # pycodestyle
|
||
]
|
||
ignore = [
|
||
"B905", # `zip()` without an explicit `strict=` parameter
|
||
]
|
||
|
||
[tool.ruff.mccabe]
|
||
max-complexity = 15
|
||
|
||
[tool.ruff.pydocstyle]
|
||
convention = "numpy"
|
||
|
||
# Local Variables:
|
||
# mode: conf-toml-mode
|
||
# End:
|
||
#+end_src
|
||
|
||
#+caption[Kickoff =setup.cfg= proposal]:
|
||
#+caption: Kickoff =setup.cfg= proposal.
|
||
#+name: lst:setup-cfg-kickoff
|
||
#+begin_src toml -n :tangle setup.cfg
|
||
[flake8]
|
||
docstring-convention = numpy
|
||
extend-select = B,F,W
|
||
extend-ignore = W503
|
||
max-complexity = 15
|
||
max-line-length = 88
|
||
|
||
[pycodestyle]
|
||
ignore = W503
|
||
max-line-length = 88
|
||
|
||
# Local Variables:
|
||
# mode: conf-toml
|
||
# End:
|
||
#+end_src
|
||
|
||
#+caption[Wrap =flake8= to remove color from text output]:
|
||
#+caption: Wrap =flake8= to remove color from text output.
|
||
#+header: :tangle-mode (identity #o755)
|
||
#+name: lst:flake8-nocolor
|
||
#+begin_src shell -n :noeval :tangle ~/bin/flake8-nocolor
|
||
#!/bin/sh
|
||
|
||
flake8 "$@" | cat
|
||
|
||
# Local Variables:
|
||
# mode: shell-script
|
||
# sh-indentation: 2
|
||
# sh-basic-offset: 2
|
||
# End:
|
||
#+end_src
|
||
|
||
#+caption[Wrap =ruff= to remove color from text output]:
|
||
#+caption: Wrap =ruff= to remove color from text output.
|
||
#+header: :tangle-mode (identity #o755)
|
||
#+name: lst:ruff-nocolor
|
||
#+begin_src shell -n :noeval :tangle ~/bin/ruff-nocolor
|
||
#!/bin/sh
|
||
|
||
ruff "$@" | cat
|
||
|
||
# Local Variables:
|
||
# mode: shell-script
|
||
# sh-indentation: 2
|
||
# sh-basic-offset: 2
|
||
# End:
|
||
#+end_src
|
||
|
||
*** [[https://github.com/joaotavora/eglot][Eglot]] for [[https://git.savannah.gnu.org/cgit/emacs.git/tree/lisp/progmodes/python.el][python-mode]]
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: sec:eglot-python
|
||
:END:
|
||
|
||
Listing [[lst:configure-eglot+pylsp-ruff]] configures [[https://github.com/joaotavora/eglot][eglot]] for [[https://www.python.org][Python]] using the
|
||
[[https://github.com/python-lsp/python-lsp-server][python-lsp-server]] with the [[https://github.com/python-lsp/python-lsp-ruff#readme][pylsp-ruff]] plugin for the easiest way to let
|
||
[[https://github.com/python-lsp/python-lsp-server][python-lsp-server]] use [[https://pypi.org/project/ruff/][Ruff]]. It is better to uninstall the Python packages
|
||
[[https://github.com/hhatto/autopep8#readme][autopep8]], [[https://github.com/PyCQA/flake8][flake8]], [[https://github.com/PyCQA/pydocstyle#readme][pydocstyle]], [[https://github.com/PyCQA/pylint#readme][pylint]], and [[https://github.com/python-rope/rope#readme][rope]].
|
||
|
||
Listing [[lst:eglot-directory-variables-for-python]] shows a proper [[info:emacs#Directory Variables][.dir-locals.el]]
|
||
file in the root directory of any [[https://www.python.org][Python]] project to start [[https://github.com/joaotavora/eglot][eglot]] automatically
|
||
according to the configuration in listing [[lst:eglot-maybe-ensure]].
|
||
|
||
Type {{{kbd(M-x eglot-show-workspace-configuration)}}} to dump a =JSON=
|
||
representation of src_emacs-lisp{eglot-workspace-configuration} for debugging.
|
||
The comment in listing [[lst:minimal-eglot-setup][minimal Eglot setup]] also shows how to decrease
|
||
or to increase the verbosity of [[https://github.com/python-lsp/python-lsp-server][Python LSP server]] log output for debugging.
|
||
|
||
#+caption[Configure =eglot= with =python-lsp-ruff=]:
|
||
#+caption: Configure =eglot= with =python-lsp-ruff=.
|
||
#+name: lst:configure-eglot+pylsp-ruff
|
||
#+begin_src emacs-lisp -n :results silent
|
||
(with-eval-after-load 'eglot
|
||
(setq-default
|
||
eglot-workspace-configuration
|
||
;; Enable the `:pylsp_ruff' plugin and ensure to disable the
|
||
;; `:flake8', `:mccabe', and `:pycodestye' plugins.
|
||
'(:pylsp (:plugins
|
||
(:pylsp_ruff
|
||
(:enabled t)
|
||
:flake8 ;; It is better to uninstall flake8
|
||
(:enabled :json-false)
|
||
:mccabe ;; It is better to uninstall mccabe
|
||
(:enabled :json-false)
|
||
:pycodestyle ;; It is better to uninstall pycodestyle
|
||
(:enabled :json-false)
|
||
:jedi
|
||
(:auto_import_modules ["numpy"])
|
||
:jedi_completion
|
||
(:cache_for ["astropy"]))))))
|
||
#+end_src
|
||
|
||
#+caption[A =.dir-locals.el= proposal to launch =eglot= automatically]:
|
||
#+caption: A =.dir-locals.el= file proposal for Python projects or Org-mode
|
||
#+caption: projects tangling Python files to launch =eglot= automatically.
|
||
#+name: lst:eglot-directory-variables-for-python
|
||
#+begin_src emacs-lisp -n :results silent :tangle dir-locals.el
|
||
;; A .dir-locals.el file proposal in the root of any
|
||
;; Python project or Org-mode project tangling Python files
|
||
;; to launch eglot automatically.
|
||
((nil ;; nil, since Emacs-29.1 filters out irrelevant variable names.
|
||
. ((eglot-workspace-configuration
|
||
;; Enable the `:pylsp_ruff' plugin and ensure to disable the
|
||
;; `:flake8', `:mccabe', and `:pycodestye' plugins.
|
||
. (:pylsp (:plugins
|
||
(:pylsp_ruff
|
||
(:enabled t)
|
||
:flake8 ;; It is better to uninstall flake8
|
||
(:enabled :json-false)
|
||
:mccabe ;; It is better to uninstall mccabe
|
||
(:enabled :json-false)
|
||
:pycodestyle ;; It is better to uninstall pycodestyle
|
||
(:enabled :json-false)
|
||
:jedi
|
||
(:auto_import_modules ["numpy"])
|
||
:jedi_completion
|
||
(:cache_for ["astropy"]))))))))
|
||
#+end_src
|
||
|
||
#+attr_latex: :booktabs yes :float table
|
||
#+caption[Eglot related key bindings]:
|
||
#+caption: Eglot related key-bindings.
|
||
#+name: tab:eglot-related-key-bindings
|
||
|-------------------------+----------------+------------------|
|
||
| command | key map | keys |
|
||
|-------------------------+----------------+------------------|
|
||
| xref-find-definition | global-map | {{{kbd(M-.)}}} |
|
||
| xref-pop | global-map | {{{kbd(M-\,)}}} |
|
||
| flymake-goto-next-error | eglot-mode-map | {{{kbd(C-c n)}}} |
|
||
| flymake-goto-prev-error | eglot-mode-map | {{{kbd(C-c p)}}} |
|
||
| eglot-rename | eglot-mode-map | {{{kbd(C-c r)}}} |
|
||
| eldoc-doc-buffer | eglot-mode-map | {{{kbd(C-h .)}}} |
|
||
|-------------------------+----------------+------------------|
|
||
|
||
Listing [[lst:make-pylsp-server-patch]] is useful to propagate eventual patches of
|
||
a local fork of [[https://github.com/python-lsp/python-lsp-server][python-lsp-server]].
|
||
|
||
#+caption[Make =pylsp-auto-import-modules.patch= listing]:
|
||
#+caption: Make =pylsp-auto-import-modules.patch= listing.
|
||
#+name: lst:make-pylsp-server-patch
|
||
#+begin_src shell -n :exports code :results none
|
||
git -C $HOME/VCS/python-lsp-server diff >pylsp.patch
|
||
#+end_src
|
||
|
||
*** [[https://jedi.readthedocs.io/en/latest/][Jedi]]
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: sec:jedi
|
||
:END:
|
||
|
||
Listing [[lst:example-py]] is a [[https://www.python.org][Python]] example to test whether [[https://jedi.readthedocs.io/en/latest/][jedi]] in combination
|
||
with [[https://github.com/joaotavora/eglot][eglot]] works when coding for instance [[https://numpy.org/][numpy]] universal functions.
|
||
|
||
#+caption[Tangle the =example.py= file]:
|
||
#+caption: Tangle the =example.py= file.
|
||
#+name: lst:example-py
|
||
#+begin_src python -n :tangle example.py
|
||
import numpy
|
||
import astropy.units as apu
|
||
|
||
a = numpy.arange(0, 11)
|
||
a = numpy.linspace(0, 10, num=11)
|
||
a = numpy.arccos(a)
|
||
q = apu.Quantity(a, apu.meter)
|
||
print(q)
|
||
#+end_src
|
||
|
||
*** [[https://github.com/astoff/code-cells.el#readme][Code-cells]]
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: sec:code-cells
|
||
:END:
|
||
|
||
[[https://github.com/astoff/code-cells.el#readme][Code-cells]] allows to edit [[https://ipython.org/notebook.html][IPython or Jupyter notebooks]] in Emacs with help from
|
||
either [[https://github.com/mwouts/jupytext][Jupytext]] or [[https://pandoc.org/][Pandoc]] to translate between the [[https://nbformat.readthedocs.io/en/latest/][ipynb]] format and different
|
||
plain text formats. Sofar, I have used [[https://github.com/astoff/code-cells.el#readme][code-cells]] to open [[https://nbformat.readthedocs.io/en/latest/][ipynb]] notebooks with
|
||
{{{kbd(C-x C-f)}}} in Emacs for viewing. Listing [[lst:configure-code-cells]]
|
||
configures =code-cells=.
|
||
|
||
#+caption[Configure =code-cells=]:
|
||
#+caption: Configure =code-cells=.
|
||
#+name: lst:configure-code-cells
|
||
#+begin_src emacs-lisp -n :results silent
|
||
(when (ensure-package-installation 'code-cells)
|
||
(with-eval-after-load 'code-cells
|
||
(let ((map code-cells-mode-map))
|
||
(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))))
|
||
#+end_src
|
||
|
||
*** TODO Look into: editing facilities
|
||
1. [[https://github.com/douglasdavis/numpydoc.el/blob/main/numpydoc.el][Emacs extension to insert numpy style docstrings in function definitions]]
|
||
|
||
* [[https://github.com/emacs-tw/awesome-emacs#library][Libraries]]
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: sec:libraries
|
||
:END:
|
||
|
||
** [[info:dash.info#Top][Library dash.el (info)]]
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: sec:dash-library
|
||
:END:
|
||
|
||
The library [[info:dash.info#Top][dash.el (info)]] positions itself as a modern alternative for the list
|
||
processing API of [[info:cl#Top][cl]]. It is a requirement of several important packages in this
|
||
Emacs setup (for instance [[https://github.com/andras-simonyi/citeproc-el#readme][citeproc]], [[https://github.com/magit/magit#readme][magit]], and [[https://github.com/Fuco1/smartparens#readme][smartparens]]). Listing
|
||
[[lst:configure-dash]] enables fontification of =dash= macros and makes
|
||
=info-lookup-symbol= take into account the =dash= macros.
|
||
|
||
#+caption[Configure =dash= fontification and info lookup]:
|
||
#+caption: Configure =dash= fontification and info lookup.
|
||
#+name: lst:configure-dash
|
||
#+begin_src emacs-lisp -n :results silent
|
||
(when (fboundp #'global-dash-fontify-mode)
|
||
(global-dash-fontify-mode))
|
||
|
||
(when (fboundp #'dash-register-info-lookup)
|
||
(with-eval-after-load 'info-look
|
||
(dash-register-info-lookup)))
|
||
#+end_src
|
||
|
||
** [[https://github.com/rejeep/f.el][Library f.el]]
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: sec:f-library
|
||
:END:
|
||
|
||
The library [[https://github.com/rejeep/f.el][f.el]] positions itself as a modern API for working with files and
|
||
directories in Emacs. It is a requirement of the [[https://github.com/andras-simonyi/citeproc-el#readme][citeproc]] package in this Emacs
|
||
setup and requires no configuration.
|
||
|
||
** [[https://github.com/magnars/s.el][Library s.el]]
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: sec:s-library
|
||
:END:
|
||
The library [[https://github.com/magnars/s.el][s.el]] positions itself as the long lost Emacs string manipulation
|
||
library. It is a requirement of the [[https://github.com/andras-simonyi/citeproc-el#readme][citeproc]] and [[https://github.com/bdarcus/citar#readme][citar]] packages in this Emacs
|
||
setup and requires no configuration.
|
||
|
||
* [[info:emacs#Minor Modes][Minor Modes (info)]]
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: sec:minor-modes
|
||
:END:
|
||
|
||
** [[https://github.com/victorhge/iedit#readme][Synchronal multiple-region editing]]
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: sec:synchronal-multiple-region-editing
|
||
:END:
|
||
|
||
#+caption[Enable =iedit=]:
|
||
#+caption: Enable =iedit=.
|
||
#+name: lst:enable-iedit
|
||
#+begin_src emacs-lisp -n :results silent
|
||
(when (ensure-package-installation 'iedit)
|
||
(require 'iedit nil 'noerror))
|
||
#+end_src
|
||
|
||
** [[https://github.com/lewang/ws-butler#readme][Unobtrusive whitespace trimming]]
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: sec:unobtrusive-whitespace-trimming
|
||
:END:
|
||
|
||
#+caption[Configure =ws-butler=]:
|
||
#+caption: Configure =ws-butler=.
|
||
#+name: lst:configure-ws-butler
|
||
#+begin_src emacs-lisp -n :results silent
|
||
(when (and (ensure-package-installation 'ws-butler)
|
||
(require 'ws-butler nil 'noerror))
|
||
(setopt ws-butler-keep-whitespace-before-point nil)
|
||
(add-hook 'prog-mode-hook #'ws-butler-mode)
|
||
(add-hook 'text-mode-hook #'ws-butler-mode))
|
||
#+end_src
|
||
|
||
** [[https://countvajhula.com/2021/09/25/the-animated-guide-to-symex/][Structural editing]]
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: sec:structural-editing
|
||
:END:
|
||
|
||
Structural editing keeps character pairs (for instance parentheses, curly and
|
||
square brackets as well as single and double quotes) balanced to leave code (for
|
||
instance Lisp and Python) and text (for instance LaTeX and Org) structure
|
||
intact. I use [[https://github.com/Fuco1/smartparens][smartparens]] which offers a normal mode (=smartparens-mode=) and a
|
||
strict mode (=smartparens-strict-mode=). Although both modes insert character
|
||
pairs, the normal mode allows to delete one of the paired characters easily
|
||
while the strict mode does not. Therefore, the strict mode is more for code
|
||
editing since it never breaks programming language rules and the normal mode is
|
||
more for text editing where structure is a matter of convention instead of
|
||
programming language rules.
|
||
|
||
For instance, the strict mode in Python allows to delete entire lists, tuples,
|
||
or the arguments after the cursor (what Emacs calls =point=) in a function call
|
||
without breaking the character pair balance. In order to repair a broken
|
||
character pair balance, insert a single character by prefixing it with "C-q"
|
||
bound to =quoted-insert=.
|
||
|
||
The [[https://smartparens.readthedocs.io/en/latest/index.html][smartparens documentation]] targets experienced Emacs users. The
|
||
following links show how to put the documentation to practical use:
|
||
1. [[https://gist.github.com/oantolin][Omar Antolin's gist "my-smartparens-config.el"]] is the first place to look for
|
||
how to tweak [[https://github.com/Fuco1/smartparens][smartparens]]. However, the gist may be partially obsolete, since
|
||
it is not part of his current [[https://github.com/oantolin/emacs-config][Emacs configuration]].
|
||
2. [[https://lists.gnu.org/archive/html/help-gnu-emacs/2014-07/msg00135.html][How to enable smartparens in the minibuffer after eval-expression]] explains
|
||
how the machinery after the first and after later usages of =eval-expression=
|
||
differ and discusses options how to handle those differences.
|
||
Listing [[lst:configure-smartparens]] aims to configure [[https://github.com/Fuco1/smartparens][smartparens]] for Elisp,
|
||
LaTeX, Org, and Python.
|
||
|
||
Despite the provocative post [[https://andreyorst.gitlab.io/posts/2022-02-20-what-if-structural-editing-was-a-mistake/]["What if structural editing was a mistake?"]],
|
||
[[https://github.com/Fuco1/smartparens][smartparens]] is one of my favorite packages. In particular, [[https://github.com/Fuco1/smartparens][smartparens]] handles
|
||
matching pairs in Org-mode files with export and source blocks for multiple
|
||
formats and languages correctly. Therefore, [[https://smartparens.readthedocs.io/en/latest/index.html?highlight=show-smartparens-mode#getting-started][show-smartparens-mode]] highlights
|
||
matching pairs immediately in front or after point in such files correctly,
|
||
contrary to for instance [[https://github.com/Fanael/rainbow-delimiters#readme][rainbow-delimiters]].
|
||
|
||
#+caption[Configure =smartparens=]:
|
||
#+caption: Configure =smartparens=.
|
||
#+name: lst:configure-smartparens
|
||
#+begin_src emacs-lisp -n :results silent
|
||
(when (and (ensure-package-installation 'smartparens)
|
||
;; Require `smartparens-config' instead of `smartparens' to
|
||
;; disable pairing of the quote character for lisp modes.
|
||
(require 'smartparens-config nil 'noerror))
|
||
(setopt sp-base-key-bindings 'sp
|
||
sp-override-key-bindings '(("C-(" . sp-backward-barf-sexp)
|
||
("C-)" . sp-forward-slurp-sexp)))
|
||
|
||
(dolist (hook '(conf-toml-mode-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))
|
||
#+end_src
|
||
|
||
** [[https://github.com/davidshepherd7/electric-operator#readme][Electric operators]]
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: sec:electric-operators
|
||
:END:
|
||
|
||
Listing [[lst:configure-electric-operator]] configures =electric-operator-mode=
|
||
to add spaces around operators for compatibility with for instance the [[https://black.readthedocs.io/en/stable/][Black
|
||
code formatter for Python]].
|
||
|
||
#+caption[Configure =electric-operator=]:
|
||
#+caption: Configure =electric-operator=.
|
||
#+name: lst:configure-electric-operator
|
||
#+begin_src emacs-lisp -n :results silent
|
||
(when (and (ensure-package-installation 'electric-operator)
|
||
(fboundp 'electric-operator-mode))
|
||
(add-hook 'c-mode-common-hook #'electric-operator-mode)
|
||
(add-hook 'python-mode-hook #'electric-operator-mode))
|
||
#+end_src
|
||
|
||
** [[https://joaotavora.github.io/yasnippet/][Smart snippets]]
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: sec:smart-snippets
|
||
:END:
|
||
|
||
#+caption[Enable =yas-global-mode=]:
|
||
#+caption: Enable =yas-global-mode=.
|
||
#+name: lst:enable-yas-global-mode
|
||
#+begin_src emacs-lisp -n :results silent
|
||
(when (ensure-package-installation 'yasnippet)
|
||
;; Set `yas-alias-to-yas/prefix-p' before loading `yasnippet'.
|
||
(setopt yas-alias-to-yas/prefix-p nil)
|
||
(when (require 'yasnippet nil 'noerror)
|
||
(yas-global-mode +1)))
|
||
#+end_src
|
||
|
||
** [[info:autotype#Tempo][Tempo (info)]]
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: sec:tempo
|
||
:END:
|
||
|
||
The official [[info:autotype#Tempo][tempo (info)]] documentation is very short and the following links
|
||
are obsolete or do not reveal the full power of [[info:autotype#Tempo][tempo (info)]] after its
|
||
adaptation to =lexical-binding=:
|
||
1. [[https://www.emacswiki.org/emacs/TempoMode][Tempo (Emacs Wiki)]].
|
||
2. [[https://www.lysator.liu.se/~davidk/elisp/][David Kågedal’s elisp]].
|
||
3. [[https://github.com/yilkalargaw/emacs-native-snippets][Emacs's Native Snippets]], [[https://www.reddit.com/r/emacs/comments/wdbk34/emacss_native_templating_and_snippet_fuctionality/][Emacs's native templating and snippet functionality]].
|
||
4. [[https://www.n16f.net/blog/templating-in-emacs-with-tempo/][Templating in Emacs with Tempo]].
|
||
Listing [[lst:configure-tempo-ui]] defines a function that inserts major modes
|
||
specific tempo templates by means of [[https://github.com/minad/marginalia][marginalia]], a function to define local
|
||
keybindings and the tools to add =tempo-tags= to abbreviation tables.
|
||
|
||
Listing [[lst:configure-tempo-latex-completing-read]] shows how to combine =tempo=,
|
||
=latex= and =completing-read= for the builtin =tempo= package. Listing
|
||
[[lst:setup-python-tempo]] configures =tempo= for =python-mode=.
|
||
|
||
#+caption[Configure =tempo= user interface]:
|
||
#+caption: Configure =tempo= user-interface.
|
||
#+name: lst:configure-tempo-ui
|
||
#+begin_src emacs-lisp -n :results silent
|
||
(with-eval-after-load 'tempo
|
||
(setopt tempo-interactive t)
|
||
|
||
(with-eval-after-load 'marginalia
|
||
(add-to-list 'marginalia-prompt-categories
|
||
'("\\<tempo template\\>" . command)))
|
||
|
||
;; https://www.n16f.net/blog/templating-in-emacs-with-tempo/
|
||
(defun insert-tempo-template ()
|
||
"Insert a major mode specific tempo template."
|
||
(interactive)
|
||
(let* ((choices (mapcar #'cdr (tempo-build-collection)))
|
||
(function-name (completing-read "Tempo template: " choices)))
|
||
(funcall (intern function-name))))
|
||
|
||
(defun setup-local-tempo-key-bindings ()
|
||
"Initialize local `tempo' key bindings."
|
||
(keymap-local-set "M-n" #'tempo-forward-mark)
|
||
(keymap-local-set "M-p" #'tempo-backward-mark)
|
||
(keymap-local-set "M-+" #'insert-tempo-template))
|
||
|
||
(defun abbrev-tempo-expand-if-complete ()
|
||
"Hook function for `define-abbrev' with `no-self-insert' property `t'."
|
||
(tempo-expand-if-complete))
|
||
|
||
(put 'abbrev-tempo-expand-if-complete 'no-self-insert t)
|
||
|
||
(defun add-tempo-tags-to-abbrev-table (tempo-tags abbrev-table)
|
||
"Add the tags in TEMPO-TAGS to ABBREV-TABLE.
|
||
Allows `tempo' expansion by typing <SPC> after a complete `tempo' tag."
|
||
(dolist (tag tempo-tags)
|
||
(unless (abbrev-symbol (car tag) abbrev-table)
|
||
(define-abbrev abbrev-table
|
||
(car tag) 1 'abbrev-tempo-expand-if-complete)))))
|
||
|
||
(with-eval-after-load 'org
|
||
(add-hook 'org-mode-hook 'setup-local-tempo-key-bindings))
|
||
#+end_src
|
||
|
||
#+caption[Configure =tempo= for =latex= with =completing-read=]:
|
||
#+caption: Configure =tempo= for =latex= with =completing-read=.
|
||
#+name: lst:configure-tempo-latex-completing-read
|
||
#+begin_src emacs-lisp -n :results silent
|
||
(with-eval-after-load 'latex
|
||
(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))
|
||
#+end_src
|
||
|
||
#+caption[Configure =tempo= for =python-mode=]:
|
||
#+caption: Configure =tempo= for =python-mode=.
|
||
#+name: lst:setup-python-tempo
|
||
#+begin_src emacs-lisp -n :results silent
|
||
(with-eval-after-load 'python
|
||
(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))
|
||
#+end_src
|
||
|
||
* [[info:emacs#Display][Display (info)]]
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: sec:display
|
||
:END:
|
||
|
||
** [[info:emacs#Narrowing][Narrowing]]
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: sec:narrowing
|
||
:END:
|
||
|
||
Narrowing means focusing in on some portion of the buffer and widening means
|
||
focussing out on the whole buffer. This allows to concentrate temporarily on
|
||
for instance a particular function or paragraph by removing clutter. The "Do
|
||
What I Mean" [[https://endlessparentheses.com/emacs-narrow-or-widen-dwim.html][narrow-or-widen-dwim]] function allows to toggle between narrowed and
|
||
widened buffer states. Here, the function =narrow-or-widen-dwim= operates also
|
||
on any Org table, Org source block, Org block, or Org subtree.
|
||
|
||
#+caption[Configure =narrow-or-widen-dwim=]:
|
||
#+caption: Configure =narrow-or-widen-dwim=.
|
||
#+name: lst:configure-narrow-or-widen-dwim
|
||
#+begin_src emacs-lisp -n :results silent
|
||
(with-eval-after-load 'emacs
|
||
(declare-function org-at-table-p "org" (&optional table-type))
|
||
|
||
(defun org-narrow-to-table ()
|
||
"Narrow buffer to table at point."
|
||
(interactive)
|
||
(if (org-at-table-p)
|
||
(narrow-to-region (org-table-begin) (org-table-end))
|
||
(user-error "Not in a table")))
|
||
|
||
(defun narrow-or-widen-dwim (p)
|
||
"Widen 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))
|
||
#+end_src
|
||
|
||
** [[https://jblevins.org/log/rainbow-mode][Visualize color codes and names]]
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: sec:rainbow-mode
|
||
:END:
|
||
|
||
Listing [[lst:enable-rainbow-mode]] enables =rainbow-mode= to colorize color codes
|
||
and names in buffers for debugging.
|
||
|
||
#+caption[Enable =rainbow-mode=]:
|
||
#+caption: Enable =rainbow-mode=.
|
||
#+name: lst:enable-rainbow-mode
|
||
#+begin_src emacs-lisp -n :results silent
|
||
(when (and (ensure-package-installation 'rainbow-mode)
|
||
(fboundp 'rainbow-mode))
|
||
(setopt rainbow-x-colors-major-mode-list '(c++-mode
|
||
c-mode
|
||
emacs-lisp-mode
|
||
inferior-emacs-lisp-mode
|
||
java-mode
|
||
lisp-interaction-mode
|
||
org-mode
|
||
python-mode))
|
||
(rainbow-mode +1))
|
||
#+end_src
|
||
|
||
** [[https://karthinks.com/software/batteries-included-with-emacs/][Flash the line around point for visual feedback]]
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: sec:flash-line-around-point
|
||
:END:
|
||
|
||
Listing [[lst:flash-line-around-point]] implements flashing of the line around point
|
||
for visual feedback after a selection of commands that make it hard to track
|
||
point movements visually.
|
||
|
||
#+caption[Implement =flash-line-around-point=]:
|
||
#+caption: Implement =flash-line-around-point=.
|
||
#+name: lst:flash-line-around-point
|
||
#+begin_src emacs-lisp -n :results silent
|
||
;; Ensure loading `pulse' with the dynamic variables `pulse-delay' and
|
||
;; `pulse-iterations' before masking them lexically instead of after
|
||
;; to prevent the warning triggered by lazy loading.
|
||
(when (require 'pulse nil 'noerror)
|
||
;; https://karthinks.com/software/batteries-included-with-emacs/
|
||
;; https://github.com/karthink/.emacs.d/blob/master/init.el#L2077
|
||
(defun flash-line-around-point (&rest _)
|
||
"Flash the line around point."
|
||
(let ((pulse-iterations 16)
|
||
(pulse-delay 0.1))
|
||
(pulse-momentary-highlight-one-line (point))))
|
||
|
||
(dolist (command '(scroll-up-command
|
||
scroll-down-command
|
||
recenter-top-bottom
|
||
other-window))
|
||
(advice-add command :after #'flash-line-around-point)))
|
||
#+end_src
|
||
|
||
* Applications
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: sec:applications
|
||
:END:
|
||
|
||
** [[info:emacs#Hyperlinking][Hyperlinking and Web Navigation Features (info)]]
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: sec:hyperlinking-web-navigating
|
||
:END:
|
||
|
||
*** [[info:emacs#Browse-URL][Browse URL (info)]]
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: sec:browse-url
|
||
:END:
|
||
|
||
Listing [[lst:configure-browse-url]] configures =browse-url=.
|
||
|
||
#+attr_latex: :options breaklines
|
||
#+caption[Configure =browse-url=]:
|
||
#+caption: Configure =browse-url=.
|
||
#+name: lst:configure-browse-url
|
||
#+begin_src emacs-lisp -n :results silent
|
||
(with-eval-after-load 'browse-url
|
||
(defun browse-url-mpv (url &optional _)
|
||
(start-process "mpv" nil "mpv" url))
|
||
|
||
(defconst browse-url-mpv-regexp
|
||
(rx bos "http" (opt "s") "://"
|
||
(or (seq "youtu.be/")
|
||
(seq "www.youtube.com/" (or "embed/" "watch?"))
|
||
(seq (+? nonl) (or ".mp4" ".webm") eos)))
|
||
"Match hyperlinks to open with mpv.")
|
||
|
||
(setopt
|
||
browse-url-generic-program (or (when (eq system-type 'darwin) "open")
|
||
(executable-find "firefox"))
|
||
browse-url-handlers `((,browse-url-mpv-regexp . browse-url-mpv)
|
||
("." . eww-browse-url))))
|
||
#+end_src
|
||
|
||
*** [[https://en.wikipedia.org/wiki/Eww_(web_browser)][Emacs Web Wowser]]
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: sec:emacs-web-wowser
|
||
:END:
|
||
|
||
#+caption[Configure =eww= URLs]:
|
||
#+caption: Configure =eww= URLs.
|
||
#+name: lst:configure-eww-urls
|
||
#+begin_src emacs-lisp -n :results silent
|
||
(when (fboundp 'eww-browse-url)
|
||
(defun eww-subreddit ()
|
||
"Read a subreddit in Emacs."
|
||
(interactive)
|
||
(eww-browse-url (format "www.reddit.com/r/%s/.mobile"
|
||
(completing-read "subreddit: "
|
||
'("emacs"
|
||
"i3wm"
|
||
"orgmode")
|
||
nil t)))))
|
||
#+end_src
|
||
|
||
#+caption[Rename =eww= buffers and display =pdf= links properly]:
|
||
#+caption: Rename =eww= buffers and display =pdf= links properly.
|
||
#+name: lst:rename-eww-buffer-display-pdf-links
|
||
#+begin_src emacs-lisp -n :results silent
|
||
(with-eval-after-load 'eww
|
||
(defun eww-display-pdf-as-binary (fn &rest args)
|
||
(let ((buffer-file-coding-system 'binary))
|
||
(apply fn args)))
|
||
|
||
(defun eww-rename-buffer ()
|
||
"Rename the buffer using title or url."
|
||
(let* ((title (plist-get eww-data :title))
|
||
(name (or (and (eq "" title) (plist-get eww-data :url)) title)))
|
||
(rename-buffer (format "*%s # eww*" name) t)))
|
||
|
||
(add-hook 'eww-after-render-hook #'eww-rename-buffer)
|
||
(advice-add 'eww-display-pdf :around #'eww-display-pdf-as-binary))
|
||
#+end_src
|
||
|
||
*** [[https://www.gnu.org/software/hyperbole/][GNU Hyperbole]] :noexport:
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: sec:ensure-hyperbole-installation
|
||
:header-args:emacs-lisp: :tangle no :eval never-export
|
||
:END:
|
||
|
||
Listing [[lst:ensure-hyperbole-installation][ensure Hyperbole installation]] works around two [[https://www.gnu.org/software/hyperbole/][GNU Hyperbole]] quirks:
|
||
1. The [[https://elpa.gnu.org/elpa/hyperbole.html][GNU ELPA Hyperbole]] and [[https://elpa.gnu.org/devel/hyperbole.html][GNU ELPA Hyperbole developer]] packages ship their
|
||
own =hyperbole-autoloads= and =kotl-autoloads= files which are incompatible
|
||
with Emacs Lisp packages from "package archives". Consequently, Emacs can
|
||
neither autoload nor load the =hyperbole-autoloads= and =kotl-autoloads=
|
||
files. Therefore, the predicate loading the =kotl-autoloads= file in the
|
||
first =unless= form of listing [[lst:ensure-hyperbole-installation][ensure Hyperbole installation]] will fail and
|
||
the body of this form will overwrite the =hyperbole-autoloads= file to fix =load-path= *and* load the =hyperbole-autoloads= and =kotl-autoloads= files.
|
||
A next time, Emacs will be able to autoload the new =hyperbole-autoloads=
|
||
file but not the =kotl-autoloads= file. However, Emacs will now be able to
|
||
load the =kotl-autoloads= file which the predicate in the first =unless= form
|
||
of listing [[lst:ensure-hyperbole-installation][ensure Hyperbole installation]] does.
|
||
2. [[https://www.gnu.org/software/hyperbole/][GNU Hyperbole]] and [[info:vertico#Top][Vertico (info)]] do not play well together, since the =vertico-mode= preselection of the first candidate out of the full candidate
|
||
completion list interferes with =hyperbole-mode= expectations where it must
|
||
handle a full candidate list. Therefore, the function call
|
||
src_emacs-lisp{(either-hyperbole-or-vertico-mode)} handles this conflict by
|
||
toggling between =hyperbole-mode= and =vertico-mode=.
|
||
|
||
#+caption[Ensure Hyperbole installation]:
|
||
#+caption: Ensure Hyperbole installation and address two Hyperbole quirks.
|
||
#+name: lst:ensure-hyperbole-installation
|
||
#+begin_src emacs-lisp -n :results silent
|
||
(when (ensure-package-installation 'hyperbole)
|
||
(unless (load "kotl-autoloads.el" 'noerror nil nil 'must-suffix)
|
||
;; See `package-generate-autoloads' for `loaddefs-generate' usage.
|
||
(unless (bound-and-true-p package-archive-contents)
|
||
(package-read-all-archive-contents))
|
||
(let* ((pkg-desc (cadr (assq 'hyperbole package-archive-contents)))
|
||
(pkg-dir (expand-file-name
|
||
(package-desc-full-name pkg-desc) package-user-dir))
|
||
(hpbl-alf (expand-file-name "hyperbole-autoloads.el" pkg-dir))
|
||
(kotl-alf (expand-file-name
|
||
(file-name-concat "kotl" "kotl-autoloads.el") pkg-dir))
|
||
(extra-data
|
||
(concat
|
||
(prin1-to-string
|
||
'(add-to-list
|
||
'load-path
|
||
(or (and load-file-name (file-name-directory load-file-name))
|
||
(car load-path))))
|
||
"\n"
|
||
(prin1-to-string
|
||
'(add-to-list
|
||
'load-path
|
||
(expand-file-name
|
||
"kotl"
|
||
(or (and load-file-name (file-name-directory load-file-name))
|
||
(car load-path)))))))
|
||
(autoload-timestamps nil)
|
||
(version-control 'never))
|
||
(loaddefs-generate pkg-dir hpbl-alf nil extra-data nil 'generate-full)
|
||
(dolist (alf (list hpbl-alf kotl-alf))
|
||
(let ((buf (find-buffer-visiting hpbl-alf)))
|
||
(when buf (kill-buffer buf)))
|
||
(load alf nil nil nil 'must-suffix))))
|
||
|
||
(defun either-hyperbole-or-vertico-mode ()
|
||
"Toggle between either `hyperbole-mode' or `vertico-mode'."
|
||
(interactive)
|
||
(when (and (boundp hyperbole-mode) (boundp vertico-mode))
|
||
(if hyperbole-mode
|
||
(progn
|
||
(hyperbole-mode -1)
|
||
(vertico-mode +1))
|
||
(hyperbole-mode +1)
|
||
(vertico-mode -1)))))
|
||
#+end_src
|
||
|
||
*** [[https://en.wikipedia.org/wiki/Media_type#Mailcap][Mailcap]]
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: sec:mailcap
|
||
:END:
|
||
|
||
Extension packages as [[info:emacs#EWW][EWW (info)]], [[info:emacs#Gnus][Gnus (info)]], and [[info:org#Top][Org (info)]] rely on a [[https://en.wikipedia.org/wiki/Media_type#Mailcap][mailcap]]
|
||
file to know what application should open a specific media file type. Listing
|
||
[[lst:tangle-mailcap-file]] specifies =emacsclient= as the application to open PDF
|
||
files.
|
||
|
||
#+caption[Tangle =mailcap= file]:
|
||
#+caption: Tangle =mailcap= file.
|
||
#+name: lst:tangle-mailcap-file
|
||
#+begin_src org -n :tangle ~/.mailcap
|
||
# https://emacs.stackexchange.com/a/24502 answers the question:
|
||
# How to use pdf-tools (pdf-view-mode) in emacs?
|
||
application/pdf; emacsclient %s
|
||
#+end_src
|
||
|
||
*** [[https://www.emacswiki.org/emacs/WebJump][Webjump]]
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: sec:webjump
|
||
:END:
|
||
|
||
Listing [[lst:set-webjump-options]] binds {{{kbd(C-c j)}}} to =webjump= and
|
||
sets the =webjump-sites= option.
|
||
|
||
#+attr_latex: :options breaklines
|
||
#+caption[Set =webjump= options and bind the =webjump= command]:
|
||
#+caption: Set =webjump= options and bind the =webjump= command.
|
||
#+name: lst:set-webjump-options
|
||
#+begin_src emacs-lisp -n :results silent
|
||
(when (fboundp 'webjump)
|
||
(global-set-key (kbd "C-c j") 'webjump)
|
||
(with-eval-after-load 'webjump
|
||
(setopt
|
||
webjump-sites
|
||
'(("CS 325 AI Programming" . "courses.cs.northwestern.edu/325")
|
||
("Emacs News" . "sachachua.com/blog/category/emacs-news")
|
||
("Mastering Emacs" . "www.masteringemacs.org")
|
||
("Planet Emacs Life" . "planet.emacslife.com")
|
||
("Real Python" . "realpython.com")
|
||
("Worg - Org Mode Community" . "orgmode.org/worg")
|
||
("Git: Emacs" . "git.savannah.gnu.org/cgit/emacs.git")
|
||
("Git: Emacs Core Python Mode" .
|
||
"git.savannah.gnu.org/cgit/emacs.git/log/lisp/progmodes/python.el")
|
||
("Git: Emacs MultiMedia System" .
|
||
"https://git.savannah.gnu.org/cgit/emms.git")
|
||
("Git: GNU ELPA" . "git.savannah.gnu.org/cgit/emacs/elpa.git")
|
||
("Git: NonGNU ELPA" . "git.savannah.gnu.org/cgit/emacs/nongnu.git")
|
||
("Git: Org Mode" . "git.savannah.gnu.org/cgit/emacs/org-mode.git")
|
||
("List: Org Mode" . "list.orgmode.org")
|
||
("List: Emacs Developer Archives" .
|
||
"lists.gnu.org/archive/html/emacs-devel/")
|
||
("List: Help GNU Emacs Archives" .
|
||
"lists.gnu.org/archive/html/help-gnu-emacs/")
|
||
("Asian Pacific Journal Japan Focus" . "apjjf.org")
|
||
("Counterpunch" . "www.counterpunch.org")
|
||
("Dictionary FR" . [simple-query "www.cnrtl.fr"
|
||
"www.cnrtl.fr/definition/" ""])
|
||
("Dictionary NL" . [simple-query "www.woorden.org"
|
||
"www.woorden.org/woord/" ""])
|
||
("Le Figaro" . "www.lefigaro.fr")
|
||
("Le Monde" . "www.lemonde.fr")
|
||
("Libération" . "www.liberation.fr")
|
||
("Monde Diplomatique" . "www.monde-diplomatique.fr")
|
||
("NRC". "www.nrc.nl")
|
||
("The Guardian" . "www.theguardian.com/international")
|
||
("Trouw" . "www.trouw.nl")
|
||
("Volkskrant" . "www.volkskrant.nl")))))
|
||
#+end_src
|
||
|
||
** [[info:gnus#Top][Reading News and Mail (info)]]
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: sec:reading-news-mail
|
||
:END:
|
||
|
||
1. [[https://www.maketecheasier.com/emacs-usenet-reader-with-gnus/][How to use Emacs as a USENET reader with Gnus]]
|
||
2. [[https://github.com/redguardtoo/mastering-emacs-in-one-year-guide/blob/master/gnus-guide-en.org][A practical guide to Gnus]]
|
||
3. [[https://jao.io/blog/2021-05-17-reading-and-searching-gmane-with-gnus-fast.html][Fast reading and searching of Gmane.io with Gnus]]
|
||
4. [[https://www.brautaset.org/posts/leafnode-nntp-os-x.html][Setting up Leafnode on macOS]]
|
||
5. [[https://blog.bitside.pl/posts/gnus/][Gnus]]
|
||
6. [[http://www.bobnewell.net/publish/35years/gmailhacks.html][Stupid GMail hacks for Gnus]]
|
||
7. [[http://www.bobnewell.net/publish/35years/gnuhacks.html][More stupid Gnus hacks]]
|
||
8. [[https://config.phundrak.com/emacs.html][See Mu4e section of Phundrak's Emacs configuration]]
|
||
|
||
1. [[https://www.passwordstore.org/][Pass: the standard unix password manager]]
|
||
1. [[https://www.howtogeek.com/devops/how-to-use-pass-a-command-line-password-manager-for-linux-systems/][How to use Pass, a command-line password manager for Unix systems]]
|
||
2. [[https://vitalyparnas.com/guides/pass/][Clever uses of pass, the Unix password manager]]
|
||
|
||
#+attr_latex: :booktabs yes :float table
|
||
#+caption[Gnus key bindings]:
|
||
#+caption: Gnus key bindings.
|
||
#+name: tab:gnus-key-bindings
|
||
| command | map | keys |
|
||
|--------------------------------+---------------------+----------------|
|
||
| gnus-group-list-active | gnus-group-list-map | {{{kbd(A-A)}}} |
|
||
| gnus-group-list-all-groups | gnus-group-mode-map | {{{kbd(L)}}} |
|
||
| gnus-group-toggle-subscription | gnus-group-mode-map | {{{kbd(U)}}} |
|
||
|--------------------------------+---------------------+----------------|
|
||
|
||
#+caption[Configure =gnus=]:
|
||
#+caption: Configure =gnus=.
|
||
#+name: lst:configure-gnus
|
||
#+begin_src emacs-lisp -n :results silent
|
||
(with-eval-after-load 'gnus
|
||
(setopt gnus-select-method '(nntp "news.gmane.io")))
|
||
|
||
(with-eval-after-load 'gnus-start
|
||
(setopt gnus-check-bogus-newsgroups nil
|
||
gnus-check-new-newsgroups 'ask-server
|
||
gnus-read-newsrc-file t
|
||
gnus-read-active-file 'some
|
||
gnus-save-killed-list t
|
||
gnus-save-newsrc-file t
|
||
gnus-use-dribble-file t))
|
||
|
||
(with-eval-after-load 'gnus-sum
|
||
(setopt gnus-thread-hide-subtree t))
|
||
#+end_src
|
||
|
||
** [[info:emacs#Sending Mail][Sending Mail (info)]]
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: sec:sending-mail
|
||
:END:
|
||
|
||
1. [[https://macowners.club/posts/email-emacs-mu4e-macos/][Email setup in Emacs with Mu4e on macOS]]
|
||
2. [[https://www.bounga.org/tips/2020/05/03/multiple-smtp-accounts-in-gnus-without-external-tools/][Setting up multiple IMAP and SMTP accounts in Gnus]]
|
||
3. [[https://jherrlin.github.io/posts/emacs-on-macos-monterey/][Emacs on Macos Monterey]]
|
||
4. [[https://github.com/00riddle00/dotfiles/blob/master/.msmtprc][Msmtp resource file for posteo.net]]
|
||
5. [[https://www.devdungeon.com/content/gpg-tutorial][GPG tutorial]]
|
||
6. [[https://en.wikipedia.org/wiki/Key_server_(cryptographic)#Keyserver_examples][Cryptographic key server examples]]
|
||
7. [[https://rakhim.org/fastmail-setup-with-emacs-mu4e-and-mbsync-on-macos/][Fastmail setup with Emacs, mu4e and mbsync on macOS]]
|
||
|
||
#+caption[Configure =message=]:
|
||
#+caption: Configure =message=.
|
||
#+name: lst:configure-message
|
||
#+begin_src emacs-lisp -n :results silent
|
||
(setopt user-full-name "Gerard Vermeulen"
|
||
user-mail-address "gerard.vermeulen@posteo.net")
|
||
(with-eval-after-load 'message
|
||
(setopt message-sendmail-envelope-from 'header))
|
||
#+end_src
|
||
|
||
#+caption[Configure =sendmail=]:
|
||
#+caption: Configure =sendmail=.
|
||
#+name: lst:configure-sendmail
|
||
#+begin_src emacs-lisp -n :results silent
|
||
(with-eval-after-load 'sendmail
|
||
(setopt mail-specify-envelope-from t
|
||
mail-envelope-from 'header
|
||
send-mail-function #'sendmail-send-it
|
||
sendmail-program (executable-find "msmtp")))
|
||
#+end_src
|
||
|
||
#+attr_latex: :options breaklines
|
||
#+caption[Resource file for =msmtp= on =Darwin=]:
|
||
#+caption: Resource file for =msmtp= on =Darwin=.
|
||
#+name: lst:darwin-msmpt-resource-file
|
||
#+header: :tangle (if (eq 'darwin system-type) "~/.msmtprc" "no")
|
||
#+begin_src conf -n
|
||
# Set default values for all accounts.
|
||
defaults
|
||
auth on
|
||
tls on
|
||
logfile ~/.msmtp.log
|
||
|
||
# CNRS on Darwin
|
||
# security add-generic-password -s cnrs -a gerard.vermeulen@neel.cnrs.fr -w
|
||
account cnrs
|
||
host smtps.grenoble.cnrs.fr
|
||
port 465
|
||
tls_starttls off
|
||
from gerard.vermeulen@neel.cnrs.fr
|
||
user gerard.vermeulen@neel.cnrs.fr
|
||
passwordeval security find-generic-password -s cnrs -w
|
||
|
||
# https://notmuchmail.org/emacstips/#index11h2
|
||
# https://wiki.debian.org/msmtp
|
||
# https://tushartyagi.com/blog/configure-mu4e-and-msmtp/
|
||
# https://www.emacswiki.org/emacs/GnusMSMTP
|
||
# https://www.ying-ish.com/essay/emacs-notmuch-mbsync-msmtp-email/
|
||
|
||
# Local Variables:
|
||
# mode: conf-unix
|
||
# End:
|
||
#+end_src
|
||
|
||
#+attr_latex: :options breaklines
|
||
#+caption[Resource file for =msmtp= on =Linux=]:
|
||
#+caption: Resource file for =msmtp= on =Linux=.
|
||
#+name: lst:linux-msmpt-resource-file
|
||
#+header: :tangle (if (eq 'gnu/linux system-type) "~/.msmtprc" "no")
|
||
#+begin_src conf -n
|
||
# Set default values for all accounts.
|
||
defaults
|
||
auth on
|
||
tls on
|
||
tls_trust_file /etc/ssl/certs/ca-certificates.crt
|
||
logfile ~/.msmtp.log
|
||
|
||
# CNRS on Linux
|
||
# secret-tool store --label=msmtp host smtps.grenoble.cnrs.fr service smtp user gerard.vermeulen@neel.cnrs.fr
|
||
account cnrs
|
||
host smtps.grenoble.cnrs.fr
|
||
port 465
|
||
tls_starttls off
|
||
from gerard.vermeulen@neel.cnrs.fr
|
||
user gerard.vermeulen@neel.cnrs.fr
|
||
|
||
# https://notmuchmail.org/emacstips/#index11h2
|
||
# https://wiki.debian.org/msmtp
|
||
# https://tushartyagi.com/blog/configure-mu4e-and-msmtp/
|
||
# https://www.emacswiki.org/emacs/GnusMSMTP
|
||
# https://www.ying-ish.com/essay/emacs-notmuch-mbsync-msmtp-email/
|
||
|
||
# Local Variables:
|
||
# mode: conf-unix
|
||
# End:
|
||
#+end_src
|
||
|
||
** [[https://github.com/skeeto/elfeed#readme][Elfeed: Emacs web feed reader]]
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: sec:emacs-web-feed-reader
|
||
:END:
|
||
|
||
Listing [[lst:set-elfeed-options]] sets =elfeed= options, binds the =elfeed=
|
||
command, and makes a minimal attempt to enable =emms=.
|
||
|
||
#+attr_latex: :options breaklines
|
||
#+caption[Set =elfeed= options and bind =elfeed= command]:
|
||
#+caption: Set =elfeed= options and bind =elfeed= command.
|
||
#+name: lst:set-elfeed-options
|
||
#+begin_src emacs-lisp -n :results silent
|
||
(when (and (ensure-package-installation 'elfeed)
|
||
(fboundp 'elfeed))
|
||
(global-set-key (kbd "C-x w") #'elfeed)
|
||
|
||
(with-eval-after-load 'elfeed
|
||
(setopt
|
||
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://tdodge.consulting/blog/rss.xml" t-dodge)
|
||
("https://talkpython.fm/episodes/rss" talk-python)
|
||
("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))))
|
||
#+end_src
|
||
|
||
** [[info:emms#Top][Emacs Multimedia System (info)]]
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: sec:emacs-multimedia-system
|
||
:END:
|
||
|
||
The link [[https://sqrtminusone.xyz/posts/2021-09-07-emms/][my emms and elfeed setup]] is a nice introduction to configuring and
|
||
using =emms= with =elfeed=. Listing [[lst:set-emms-options]] configures =emms=.
|
||
|
||
#+caption[Set =emms= options]:
|
||
#+caption: Set =emms= options.
|
||
#+name: lst:set-emms-options
|
||
#+begin_src emacs-lisp -n :results silent
|
||
(when (ensure-package-installation 'emms)
|
||
(with-eval-after-load 'emms
|
||
(setopt emms-player-list '(emms-player-mpd emms-player-mpv))
|
||
(emms-all))
|
||
|
||
(with-eval-after-load 'emms-mode-line
|
||
(setopt emms-mode-line-format ""))
|
||
|
||
(with-eval-after-load 'emms-player-mpd
|
||
(setopt emms-player-mpd-music-directory (expand-file-name "~/Music")
|
||
emms-player-mpd-server-name "localhost"
|
||
emms-player-mpd-server-port "6600"
|
||
emms-player-mpd-verbose t))
|
||
|
||
(with-eval-after-load 'emms-player-mpv
|
||
(setopt emms-player-mpv-ipc-method 'ipc-server
|
||
emms-player-mpv-update-metadata t))
|
||
|
||
(with-eval-after-load 'emms-playing-time
|
||
(setopt emms-playing-time-display-format " %s "))
|
||
|
||
(with-eval-after-load 'emms-playlist-mode
|
||
(setopt emms-playlist-mode-center-when-go t))
|
||
|
||
(with-eval-after-load 'emms-streams
|
||
(setopt emms-streams-file
|
||
(no-littering-expand-etc-file-name "emms/streams.emms"))))
|
||
#+end_src
|
||
|
||
*** Hiding spurious buffers
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: sec:hiding-spurious-buffers
|
||
:END:
|
||
|
||
When =emms-player-mpd= opens a connection to =mpd= and =mpd= is not running,
|
||
=mpd= responds with a message of the form "OK MPD 0.23.5". Neither
|
||
=emms-player-mpd= nor its support library =tq= anticipate the "OK MPD 0.23.5"
|
||
message, but send such messages to a new intrusive buffer called =*spurious*=.
|
||
Listing [[lst:hiding-spurious-buffers]] hides those =*spurious*= buffers, but you
|
||
can always switch to them. See:
|
||
1. [[info:elisp#The Zen of Buffer Display][The Zen of Buffer Display (info)]]
|
||
2. [[https://e17i.github.io/articles-emacs-display-1/][Configuring the Emacs display system]]
|
||
3. [[https://www.masteringemacs.org/article/demystifying-emacs-window-manager][Demystifying Emacs's Window Manager]]
|
||
for technical information.
|
||
|
||
#+caption[Hiding =*spurious*= buffers]:
|
||
#+caption: Hiding =*spurious*= buffers.
|
||
#+name: lst:hiding-spurious-buffers
|
||
#+begin_src emacs-lisp -n :results silent
|
||
(with-eval-after-load 'emms-player-mpd
|
||
;; https://www.masteringemacs.org/article/demystifying-emacs-window-manager
|
||
;; Hide `*spurious*' buffers, but you can always switch to them:
|
||
(add-to-list 'display-buffer-alist
|
||
'("\\*spurious\\*.*" display-buffer-no-window
|
||
(allow-no-window . t))))
|
||
#+end_src
|
||
|
||
*** [[https://www.musicpd.org/][Music Player Daemon]] configuration
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: sec:mpd-configuration-file
|
||
:END:
|
||
|
||
Listing [[lst:darwin-mpd-conf]] proposes a [[https://www.musicpd.org/][Music Player Daemon]] configuration file on
|
||
Darwin that keeps each new connection for a day in order to reduce the rate of
|
||
=*spurious*= generation.
|
||
|
||
#+caption[A Music Player Daemon configuration file on Darwin]:
|
||
#+caption: A Music Player Daemon configuration file on Darwin.
|
||
#+name: lst:darwin-mpd-conf
|
||
#+begin_src conf -n :tangle (if (eq 'darwin system-type) "~/.mpd/mpd.conf" "no")
|
||
music_directory "~/Music"
|
||
playlist_directory "~/.mpd/playlists"
|
||
db_file "~/.mpd/mpd.db"
|
||
log_file "~/.mpd/mpd.log"
|
||
pid_file "~/.mpd/mpd.pid"
|
||
state_file "~/.mpd/mpdstate"
|
||
follow_outside_symlinks "yes"
|
||
follow_inside_symlinks "yes"
|
||
|
||
bind_to_address "localhost"
|
||
port "6600"
|
||
connection_timeout "86400"
|
||
|
||
audio_output {
|
||
type "osx"
|
||
name "CoreAudio"
|
||
mixer_type "software"
|
||
}
|
||
#+end_src
|
||
|
||
* [[info:emacs#Init File][Init File (info)]] footer
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: sec:user-init-file-footer
|
||
:END:
|
||
|
||
#+caption[Tangle the =user-init-file= footer]:
|
||
#+caption: Tangle the =user-init-file= footer.
|
||
#+name: lst:tangle-user-init-file-footer
|
||
#+begin_src emacs-lisp -n :results silent
|
||
(provide 'init)
|
||
|
||
;; Emacs looks for "Local variables:" after the last "newline-formfeed".
|
||
|
||
;; Local Variables:
|
||
;; indent-tabs-mode: nil
|
||
;; End:
|
||
;;; init.el ends here
|
||
#+end_src
|
||
|
||
* GNU Free Documentation License
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: sec:gnu-fdl
|
||
:END:
|
||
|
||
#+include: fdl.org
|
||
|
||
* Local variables linking to [[#sec:latexmk-save-compile-display-loop][Latexmk save-compile-display-loop]]
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: sec:local-variables
|
||
:END:
|
||
|
||
Only the [[info:org#Top][Org]] source file shows the local variables footer.
|
||
|
||
#+print_bibliography:
|
||
|
||
# Emacs looks for "Local variables:" after the last "newline-formfeed".
|
||
|
||
# Local Variables:
|
||
# compile-command: "latexmk -interaction=nonstopmode -lualatex -pvc -shell-escape README.tex"
|
||
# eval: (org-eval-emacs-lisp-setup-blocks)
|
||
# fill-column: 80
|
||
# End:
|