#+title: Emacs setup for use with LaTeX, Org, and Python #+author: Gerard Vermeulen #+latex_class: article #+latex_class_options: [11pt,a4paper,english,svgnames,tables] #+setupfile: "setup-include.org" #+include: "setup-include.org" * Quick start :PROPERTIES: :CUSTOM_ID: sec:quick-start :END: Backup your =user-emacs-directory= (defaults often to =~/.emacs.d= on =Linux=, =Unix=, or =Darwin=) directory and execute the commands in listing [[lst:prepare-user-emacs-directory]]. After invoking Emacs interactively (in interactive mode, neither in batch mode, nor in server mode), Emacs will ask you to install a selected set of packages. Quit Emacs and invoke Emacs again. #+caption[Prepare the user-emacs-directory]: #+caption: Clone and initialize the user-emacs-directory. #+name: lst:prepare-user-emacs-directory #+begin_src shell :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 * 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]] requires [[info:org#Top][Org-9.5 (info)]], which is already part of Emacs-28.1. [[https://github.com/bdarcus/citar][Citar]] exploits the enhancements of Emacs' builtin selection mechanism provided by the extension packages [[https://github.com/minad/vertico][vertico]], [[https://github.com/oantolin/orderless][orderless]], [[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]]. 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 Antolín 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]]. I have stolen his idea of using ~custom-set-variables~. 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. I have stolen her idea of using [[https://github.com/quelpa/quelpa][quelpa]] to install packages from any source. 5. [[https://github.com/purcell/emacs.d][Steve Purcell's configuration]] is well organized, a showcase of readable code, as well helpful commit and issue histories. See for instance the discussion on [[https://github.com/purcell/emacs.d/issues/778][the correctness of order of company candidates in Emacs lisp mode]]. * [[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 :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 customizes Emacs variables. It consists of three parts in listing [[lst:1st-custom-set-variables-call]], [[lst:2nd-custom-set-variables-call]], and [[lst:3rd-custom-set-variables-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 reader macros ~'~ (quote), ~`~ (backquote), ~,~ (substitute) and ~@,~ (splice) in the ~custom-set-variables~ function calls in listing [[lst:1st-custom-set-variables-call]], [[lst:2nd-custom-set-variables-call]], and [[lst:3rd-custom-set-variables-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~ as [[info:emacs#Saving Customizations][saving customizations (info)]] recommends because of the ~custom-set-variables~ function calls. #+caption[Customize the first set of Emacs variables]: #+caption: Customize the first set of Emacs variables. #+name: lst:1st-custom-set-variables-call #+begin_src emacs-lisp ;;; init.el --- user init file -*- lexical-binding: t -*- ;;; Commentary: ;;; Code: (require 'cl-lib) (custom-set-variables '(after-save-hook #'executable-make-buffer-file-executable-if-script-p) '(column-number-mode t) '(cursor-type 'box) `(custom-file ,(locate-user-emacs-file "custom.el")) '(epg-pinentry-mode 'loopback) '(global-hl-line-mode t) '(global-hl-line-sticky-flag t) '(indent-tabs-mode nil) '(inhibit-startup-buffer-menu t) '(inhibit-startup-screen t) '(initial-buffer-choice t) '(initial-scratch-message "") `(insert-directory-program ,(or (executable-find "gls") (executable-find "ls"))) '(kill-ring-max 300) '(package-archives '(("gnu" . "https://elpa.gnu.org/packages/") ("nongnu" . "https://elpa.nongnu.org/nongnu/") ("melpa" . "https://melpa.org/packages/"))) ;; Pin those packages to GNU ELPA to get the info documentation. '(package-pinned-packages '((consult . "gnu") (marginalia . "gnu") (vertico . "gnu")))) #+end_src #+caption[Customize the second set of Emacs variables]: #+caption: Customize the second set of Emacs variables. #+name: lst:2nd-custom-set-variables-call #+begin_src emacs-lisp (custom-set-variables `(package-selected-packages `(,@(when (version< emacs-version "28.0") '(org)) ; plain text thought organizer anaconda-mode ; strangles python-mode async ; asynchroneous processing auctex ; Aalborg University Center TeX blacken ; Black Python-code formatter client citar ; bibliography handling citeproc ; bibliography handling company ; complete anything company-anaconda ; complete anything in anaconda-mode consult ; consult completing-read eglot ; Emacs polyGLOT LSP client electric-operator ; automatic spacing around operators elfeed ; web feed reader embark ; act on any buffer selection emms ; Emacs Multi-Media System htmlize ; convert buffer contents to HTML iedit ; simultaneous multi-entity editing laas ; LaTeX Auto-Activating Snippets leuven-theme ; beautiful color theme magit ; Git Text-based User Interface marginalia ; minibuffer margin notes markdown-mode ; markdown text mode no-littering ; keep `user-emacs-directory' clean nov ; EPUB reader orderless ; Emacs completion style pdf-tools ; interactive docview replacement pdf-view-restore ; add view history to pdf-tools pyenv-mode ; Python environment selector quelpa ; install Emacs packages from source rainbow-mode ; set background color to color string smartparens ; smart editing of character pairs toml-mode ; Tom's Obvious Minimal Language mode undo-tree ; more advanced yet simpler undo system vertico ; VERTical Interactive Completion wgrep ; open a writable grep buffer which-key ; on the fly key-binding help wordnut ; WordNet lexical database writegood-mode ; bullshit and weasel-word detector ws-butler ; remove trailing whitespace xr ; undo rx to grok regular expressions yasnippet))) ; code or text template expansion #+end_src #+caption[Customize the third set of Emacs variables]: #+caption: Customize the third set of Emacs variables. #+name: lst:3rd-custom-set-variables-call #+begin_src emacs-lisp (custom-set-variables '(recentf-mode t) '(save-place-mode t) '(scroll-bar-mode nil) '(tab-always-indent 'complete) '(tab-width 8) '(tool-bar-mode nil) '(url-cookie-trusted-urls nil) '(url-cookie-untrusted-urls '(".*")) '(use-dialog-box nil) '(use-short-answer t) '(view-read-only t)) (when (eq system-type 'darwin) (custom-set-variables '(ns-alternate-modifier nil) '(ns-command-modifier 'meta) '(ns-right-command-modifier 'super))) (when (eq window-system 'ns) (add-to-list 'initial-frame-alist '(height . 51)) (add-to-list 'initial-frame-alist '(width . 180))) #+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)]]. In addition, the [[https://github.com/quelpa/quelpa][quelpa]] tool allows to fetch code from any source and build a package on your computer before installation. 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 ensures installation of [[https://github.com/quelpa/quelpa][quelpa]] before ensuring installation of [[https://www.reddit.com/r/emacs/comments/l6rcqh/undotree_repositorys_new_home_gitlab/][undo-tree]]. 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. 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 (unless (require 'no-littering nil 'noerror) (package-refresh-contents) (package-install 'no-littering) (require 'no-littering)) (unless (package-installed-p 'quelpa) (package-install 'quelpa)) (unless (package-installed-p 'undo-tree) ;; Neither GNU ELPA, nor MELPA have the latest version. (quelpa '(undo-tree :repo "tsc25/undo-tree" :fetcher gitlab))) (unless noninteractive (package-install-selected-packages)) #+end_src * [[info:dir#Top][Info documentation]] :PROPERTIES: :CUSTOM_ID: sec:info-documentation :END: Listing [[lst:configure-info]] adds a path in my home directory to the places where =info= looks for files. #+caption[Configure =info=]: #+caption: Configure =info=. #+name: lst:configure-info #+begin_src emacs-lisp (with-eval-after-load 'info (add-to-list 'Info-directory-list (expand-file-name "~/.local/share/info"))) #+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 (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 succesful (re)compilation on condition that the settings of the ~compile-command~ local variable in section are compatible. The local variable ~compile-command~ in the [[#sec:local-variables][local variables]] section (only visible in =org= files, but not in =html= and =pdf= files) shows how to use the =latexmkrc= file. #+caption[Tangle the Latexmk resource file]: #+caption: Tangle the Latexmk resource file. #+name: lst:latexmkrc #+begin_src perl :tangle latexmkrc :comments none # pdf previewer and update pdf previewer $pdf_previewer = "emacsclient -e '(find-file-other-window %S)'"; $pdf_update_method = 4; # 4 runs a command to force the update $pdf_update_command = "emacsclient -e '(with-current-buffer (find-buffer-visiting %S) (pdf-view-revert-buffer nil t))'"; # see for instance glossary.latexmkrc add_cus_dep( 'acn', 'acr', 0, 'makeglossaries' ); add_cus_dep( 'glo', 'gls', 0, 'makeglossaries' ); $clean_ext .= " acr acn alg bbl glo gls glg ist lol run.xml"; sub makeglossaries { my ($name, $path) = fileparse( $$Psource ); return system "makeglossaries -d '$path' '$name'"; } # Emacs looks for "Local variables:" after the last "?\n?\f". # Local Variables: # mode: perl # End: #+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 none #+header: :tangle-mode (identity #o755) #+name: lst:qutebrowser-userscript #+begin_src python :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 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]] * 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:orderless#Top][Orderless (info)]] for an advanced completion style, 2. [[info:embark#Top][Embark (info)]] for minibuffer actions with context menus, 3. [[info:marginalia#Top][Marginalia (info)]] for rich annotations in the minibuffer, and 4. [[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 and [[#sec:minibuffer-history-completion][minibuffer-history-completion]] provides completion on previous input in the minibuffer. ** [[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 (unless noninteractive (custom-set-variables '(history-delete-duplicates t) '(history-length 500) '(savehist-additional-variables '(eww-history kill-ring regexp-search-string search-ring search-string))) (savehist-mode +1) (when (fboundp 'vertico-mode) (vertico-mode +1))) #+end_src ** [[info:orderless#Top][Orderless (info)]] :PROPERTIES: :CUSTOM_ID: sec:orderless-configuration :END: Listing [[lst:configure-orderless]] enables =orderless=. #+caption[Configure =orderless=]: #+caption: Configure =orderless=. #+name: lst:configure-orderless #+begin_src emacs-lisp (unless noninteractive (when (fboundp 'orderless-filter) (custom-set-variables ;; https://github.com/purcell/emacs.d/issues/778 '(completion-styles '(basic completion-partial orderless)) '(completion-category-defaults nil) '(completion-category-overrides '((file (styles partial-completion))))) (add-hook 'minibuffer-setup-hook (defun my-on-minibuffer-setup-hook() (setq-default completion-styles '(substring orderless)))))) #+end_src ** [[info:embark#Top][Embark (info)]] :PROPERTIES: :CUSTOM_ID: sec:embark-configuration :END: Listing [[lst:configure-embark]] configures =embark=. #+caption[Configure =embark=]: #+caption: Configure =embark=. #+name: lst:configure-embark #+begin_src emacs-lisp (unless noninteractive (when (cl-every #'fboundp '(embark-act embark-bindings embark-dwim)) (global-set-key (kbd "C-,") #'embark-act) (global-set-key (kbd "C-:") #'embark-dwim) (global-set-key (kbd "C-h B") #'embark-bindings))) #+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 (unless noninteractive (when (fboundp 'marginalia-mode) (marginalia-mode +1))) #+end_src ** [[info:consult#Top][Consult (info)]] :PROPERTIES: :CUSTOM_ID: sec:consult-configuration :END: Listing [[lst:configure-consult]] configures =consult=. #+attr_latex: :booktabs yes :float table #+caption[Configuration specific key binding]: #+caption: Configuration specific key-bindings. #+name: tab:configuration-specific-key-bindings |-------------------------------+---------------------+------------------------| | command | keys | key map | |-------------------------------+---------------------+------------------------| | =consult-apropos= | {{{kbd( a)}}} | =global-map= | | =consult-bookmark= | {{{kbd(C-x r b)}}} | =ctl-x-keymap= | | =consult-buffer-other-frame= | {{{kbd(C-x 5 b)}}} | =ctl-x-keymap= | | =consult-buffer-other-window= | {{{kbd(C-x 4 b)}}} | =ctl-x-keymap= | | =consult-buffer= | {{{kbd(C-x b)}}} | =ctl-x-keymap= | | =consult-compile-error= | {{{kbd(M-g e)}}} | =goto-map= | | =consult-complex-command= | {{{kbd(C-x M-:)}}} | =ctl-x-keymap= | | =consult-find= | {{{kbd(M-s f)}}} | =search-map= | | =consult-focus-lines= | {{{kbd(M-s u)}}} | =search-map= | | =consult-git-grep= | {{{kbd(M-s g)}}} | =search-map= | | =consult-global-mark= | {{{kbd(M-g k)}}} | =goto-map= | | =consult-goto-line= | {{{kbd(M-g M-g)}}} | =goto-map= | | =consult-goto-line= | {{{kbd(M-g g)}}} | =goto-map= | | =consult-history= | {{{kbd(C-c h)}}} | =global-map= | | =consult-imenu-project= | {{{kbd(M-g i)}}} | =goto-map= | | =consult-keep-lines= | {{{kbd(M-s k)}}} | =search-map= | | =consult-line= | {{{kbd(M-s l)}}} | =search-map= | | =consult-mark= | {{{kbd(M-g m)}}} | =goto-map= | | =consult-mode-command= | {{{kbd(C-c m)}}} | =global-map= | | =consult-multi-occur= | {{{kbd(M-s m)}}} | =search-map= | | =consult-outline= | {{{kbd(M-g o)}}} | =goto-map= | | =consult-register= | {{{kbd(C-x r x)}}} | =ctl-x-keymap= | | =consult-yank-pop= | {{{kbd(M-y)}}} | =global-map= | |-------------------------------+---------------------+------------------------| | =elfeed= | {{{kbd(C-x w)}}} | =global-map= | | =embark-act= | {{{kbd(C-\,)}}} | =global-map= | | =embark-bindings= | {{{kbd(C-h B)}}} | =global-map= | | =embark-dwim= | {{{kbd(C-:)}}} | =global-map= | | =iedit-mode= | {{{kbd(C-;)}}} | =global-map= | | =minibuffer-complete-history= | {{{kbd(C-)}}} | =minibuffer-local-map= | | =narrow-or-widen-dwim= | {{{kbd(C-x C-n)}}} | =ctl-x-keymap= | | =org-agenda= | {{{kbd(C-c a)}}} | =global-map= | | =org-capture= | {{{kbd(C-c c)}}} | =global-map= | | =org-cite= | {{{kbd(C-c b)}}} | =org-mode-map= | | =org-insert-link-global= | {{{kbd(C-c C-l)}}} | =global-map= | | =org-narrow-to-table= | {{{kbd(C-x n t)}}} | =ctl-x-keymap= | | =org-store-link= | {{{kbd(C-c l)}}} | =global-map= | |-------------------------------+---------------------+------------------------| #+caption[Configure =consult=]: #+caption: Configure =consult=. #+name: lst:configure-consult #+begin_src emacs-lisp (unless noninteractive (when (fboundp 'consult-apropos) (custom-set-variables '(consult-project-root-function #'vc-root-dir)) ;; C-c bindings (mode-specific-map) (global-set-key (kbd "C-c h") #'consult-history) (global-set-key (kbd "C-c m") #'consult-mode-command) ;; C-x bindings (ctl-x-map) (define-key ctl-x-map (kbd "M-:") #'consult-complex-command) (define-key ctl-x-map (kbd "b") #'consult-buffer) (define-key ctl-x-map (kbd "4 b") #'consult-buffer-other-window) (define-key ctl-x-map (kbd "5 b") #'consult-buffer-other-frame) (define-key ctl-x-map (kbd "r x") #'consult-register) (define-key ctl-x-map (kbd "r 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-project) (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 (global-set-key (kbd "M-y") #'consult-yank-pop) (global-set-key (kbd " a") #'consult-apropos) ;; Tweak functions (advice-add 'completing-read-multiple :override #'consult-completing-read-multiple) (fset 'multi-occur #'consult-multi-occur))) #+end_src ** [[https://company-mode.github.io/][Company: a modular complete anything framework for Emacs]] :PROPERTIES: :CUSTOM_ID: sec:company-configuration :END: Listing [[lst:configure-company]] configures =company=. #+caption[Configure =company=]: #+caption: Configure =company=. #+name: lst:configure-company #+begin_src emacs-lisp (unless noninteractive (when (fboundp 'company-mode) (custom-set-variables ;; https://github.com/purcell/emacs.d/issues/778 '(company-transformers '(company-sort-by-occurrence))) (dolist (hook '(LaTeX-mode-hook org-mode-hook emacs-lisp-mode-hook lisp-interaction-mode-hook python-mode-hook ielm-mode-hook)) (add-hook hook #'company-mode)))) #+end_src ** [[https://lists.gnu.org/archive/html/emacs-devel/2021-12/msg00802.html][Minibuffer history completion]] :PROPERTIES: :CUSTOM_ID: sec:minibuffer-history-completion :END: See [[https://lists.gnu.org/archive/html/emacs-devel/2021-12/msg00802.html][Juri Linkov (Emacs Developer mailing list)]] for how to allow completion on previous input in the minibuffer. Listing [[lst:enable-minibuffer-history-completion]] enables minibuffer history completion. #+caption[Enable =minibuffer-history-completion=]: #+caption: Enable =minibuffer-history-completion=. #+name: lst:enable-minibuffer-history-completion #+begin_src emacs-lisp (defun minibuffer-setup-history-completions () (unless (or minibuffer-completion-table minibuffer-completion-predicate) (setq-local minibuffer-completion-table (symbol-value minibuffer-history-variable)))) (add-hook 'minibuffer-setup-hook 'minibuffer-setup-history-completions) ;; Stolen from Emacs-28.1 for Emacs-27.2: (unless (fboundp 'minibuffer--completion-prompt-end) (defun minibuffer--completion-prompt-end () (let ((end (minibuffer-prompt-end))) (if (< (point) end) (user-error "Can't complete in prompt") end)))) ;; Adapted from ‘minibuffer-complete’: (defun minibuffer-complete-history () "Allow minibuffer completion on previous input." (interactive) (completion-in-region (minibuffer--completion-prompt-end) (point-max) (symbol-value minibuffer-history-variable) nil)) (define-key minibuffer-local-map (kbd "C-") 'minibuffer-complete-history) #+end_src ** [[https://github.com/justbur/emacs-which-key#readme][Prefix key-binding help]] :PROPERTIES: :CUSTOM_ID: sec:prefix-key-binding-help :END: Configure ~which-key-mode~ so that typing =C-h= after a prefix key displays all keys available after the prefix key. Listing [[lst:enable-which-key-mode]] enables =which-key-mode=. #+caption[Enable =which-key-mode=]: #+caption: Enable =which-key-mode=. #+name: lst:enable-which-key-mode #+begin_src emacs-lisp (when (fboundp 'which-key-mode) (custom-set-variables '(which-key-idle-delay 10000) '(which-key-idle-secondary-delay 0.05) '(which-key-show-early-on-C-h t)) (which-key-mode +1)) #+end_src * Reading :PROPERTIES: :CUSTOM_ID: sec:reading :END: ** Reading EPUB files :PROPERTIES: :CUSTOM_ID: sec:reading-epub-files :END: Listing [[lst:enable-nov-mode]] enables =nov-mode=. #+caption[Enable =nov-mode=]: #+caption: Enable =nov-mode=. #+name: lst:enable-nov-mode #+begin_src emacs-lisp (when (fboundp 'nov-mode) (add-to-list 'auto-mode-alist `(,(rx ".epub" eos) . nov-mode))) #+end_src ** Reading PDF files :PROPERTIES: :CUSTOM_ID: sec:reading-pdf-files :END: The [[https://github.com/vedang/pdf-tools][pdf-tools]] package exploits the [[https://github.com/freedesktop/poppler][poppler]] library to render and to let you annotate [[https://en.wikipedia.org/wiki/PDF][PDF]] files. It also exploits the [[https://wiki.contextgarden.net/SyncTeX][SyncTeX]] library to link anchors in [[https://en.wikipedia.org/wiki/PDF][PDF]] files produced with LaTeX to the original LaTeX sources. In order to use [[https://github.com/vedang/pdf-tools][pdf-tools]], you have to type =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[Enable =pdf-tools=]: #+caption: Enable =pdf-tools=. #+name: lst:enable-pdf-tools #+begin_src emacs-lisp ;; 'pdf-loader-install' is the lazy equivalent of 'pdf-tools-install': ;; see the README file. (when (fboundp 'pdf-loader-install) (pdf-loader-install)) (with-eval-after-load 'pdf-view (when (fboundp 'pdf-view-restore-mode) (add-hook 'pdf-view-mode-hook #'pdf-view-restore-mode))) #+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]]. For instance, the ~TeX-master~ safe local variable in the =tex.el= elisp library file has no autoload cookie. Without prior loading of =tex.el=, Emacs will complain that ~TeX-master~ is no safe local variable in case it reads a LaTeX file that sets ~TeX-master~. Listing [[lst:require-auctex]] initializes [[https://en.wikipedia.org/wiki/AUCTeX][AUCTeX]] properly. #+caption[Require =AUCTeX=]: #+caption: Require =AUCTeX=. #+name: lst:require-auctex #+begin_src emacs-lisp ;; Use `require' to make `TeX-master' a safe local variable. (when (require 'tex nil 'noerror) (custom-set-variables '(TeX-auto-save t) '(TeX-install-font-lock #'font-latex-setup) '(TeX-parse-self t))) #+end_src Although, the LaTeX =biblatex= is in use, listing [[lst:configure-bibtex]] configures the Emacs =bibtex= library for the LaTeX =BiBTeX= package to maintain backwards compatibility. #+caption[Configure =bibtex=]: #+caption: Configure =bibtex=. #+name: lst:configure-bibtex #+begin_src emacs-lisp (with-eval-after-load 'bibtex (custom-set-variables '(bibtex-dialect 'BibTeX))) #+end_src Listing [[lst:configure-font-latex]] disables font scaling of section titles. #+caption[Configure =font-latex=]: #+caption: Configure =font-latex=. #+name: lst:configure-font-latex #+begin_src emacs-lisp (with-eval-after-load 'font-latex (custom-set-variables '(font-latex-fontify-sectioning 1.0))) #+end_src Listing [[lst:configure-latex]] configures =latex= for a full featured =LaTeX-section-command=. #+caption[Configure =latex=]: #+caption: Configure =latex=. #+name: lst:configure-latex #+begin_src emacs-lisp (with-eval-after-load 'latex (custom-set-variables '(LaTeX-section-hook '(LaTeX-section-heading LaTeX-section-title LaTeX-section-toc LaTeX-section-section LaTeX-section-label)))) #+end_src Out of the box, [[https://en.wikipedia.org/wiki/AUCTeX][AUCTeX]] does not indent text between square brackets. The code in listing [[lst:configure-tex]] corrects this by advising to override ~TeX-brace-count-line~ with ~my-TeX-brace-count-line~. #+caption[Configure =TeX=]: #+caption: Configure =TeX=. #+name: lst:configure-tex #+begin_src emacs-lisp (with-eval-after-load 'tex ;; https://emacs.stackexchange.com/questions/17396/ ;; indentation-in-square-brackets (defun my-TeX-brace-count-line () "Count number of open/closed braces." (save-excursion (let ((count 0) (limit (line-end-position)) char) (while (progn (skip-chars-forward "^{}[]\\\\" limit) (when (and (< (point) limit) (not (TeX-in-comment))) (setq char (char-after)) (forward-char) (cond ((eq char ?\{) (setq count (+ count TeX-brace-indent-level))) ((eq char ?\}) (setq count (- count TeX-brace-indent-level))) ((eq char ?\[) (setq count (+ count TeX-brace-indent-level))) ((eq char ?\]) (setq count (- count TeX-brace-indent-level))) ((eq char ?\\) (when (< (point) limit) (forward-char) t)))))) count))) (advice-add 'TeX-brace-count-line :override #'my-TeX-brace-count-line)) #+end_src *** TODO Improve the AUCTeX configuration slowly [[https://github.com/thisirs/dotemacs/blob/master/lisp/init-auctex.el][AUCTeX setup of an experienced user]] ** Writing Org files :PROPERTIES: :CUSTOM_ID: sec:writing-org-files :END: *** [[info:org#Activation][Org activation (info)]] :PROPERTIES: :CUSTOM_ID: sec:activate-org :END: #+caption[Activate =Org=]: #+caption: Activate =Org=. #+name: lst:activate-org #+begin_src emacs-lisp ;; Inspect: ;; function with "C-h f" ;; symbols with "C-h o" ;; variables with "C-h v" (global-set-key (kbd "C-c a") #'org-agenda) (global-set-key (kbd "C-c c") #'org-capture) (global-set-key (kbd "C-c l") #'org-store-link) (global-set-key (kbd "C-c C-l") #'org-insert-link-global) #+end_src *** Org customization :PROPERTIES: :CUSTOM_ID: sec:customize-org :END: The code in listing [[lst:customize-org-babel]], [[lst:customize-org]], and [[lst:customize-org-export]] does basic customization of [[https://orgmode.org/][Org Mode]] variables. #+caption[Customize =org-babel=]: #+caption: Customize =org-babel=. #+name: lst:customize-org-babel #+begin_src emacs-lisp (with-eval-after-load 'ob-core (custom-set-variables '(org-confirm-babel-evaluate nil))) (with-eval-after-load 'ob-python (custom-set-variables '(org-babel-python-command "python -E"))) (with-eval-after-load 'ob-latex (custom-set-variables '(org-babel-latex-preamble (lambda (_) "\\documentclass[preview]{standalone}\n")) '(org-babel-latex-begin-env (lambda (_) "\\begin{document}\n")) '(org-babel-latex-end-env (lambda (_) "\\end{document}\n")))) #+end_src #+caption[Customize =Org=]: #+caption: Customize =Org=. #+name: lst:customize-org #+begin_src emacs-lisp (with-eval-after-load 'org (custom-set-variables '(org-babel-load-languages '((C . t) (calc . t) (dot . t) (emacs-lisp . t) (eshell . t) (fortran . t) (gnuplot . t) (latex . t) (lisp . t) (maxima . t) (org . t) (perl . t) (python . t) (scheme . t) (shell . t))) '(org-export-backends '(ascii beamer html icalendar latex odt texinfo)) '(org-file-apps '((auto-mode . emacs) (directory . emacs) ("\\.mm\\'" . default) ("\\.x?html?\\'" . default) ("\\.pdf\\'" . emacs))) '(org-modules '(ol-bibtex ol-doi ol-eww ol-info org-id org-protocol org-tempo)) '(org-structure-template-alist '(("a" . "export ascii") ("c" . "center") ("C" . "comment") ("e" . "example") ("E" . "export") ("h" . "export html") ("l" . "export latex") ("q" . "quote") ("s" . "src") ("p" . "src python :session :async") ("v" . "verse"))))) #+end_src #+caption[Customize =org-export=]: #+caption: Customize =org-export=. #+name: lst:customize-org-export #+begin_src emacs-lisp (with-eval-after-load 'ox-latex (custom-set-variables '(org-latex-compiler "lualatex") '(org-latex-hyperref-template "\\hypersetup{ pdfauthor={%a}, pdftitle={%t}, pdfkeywords={%k}, pdfsubject={%d}, pdfcreator={%c}, pdflang={%L}, citecolor=blue, colorlinks=true, filecolor=blue, hyperfootnotes=false, linkcolor=blue, unicode=true, urlcolor=blue, }\n") '(org-latex-listings 'minted) '(org-latex-minted-langs '((cc "c++") (cperl "perl") (diff "diff") (shell-script "bash") (caml "ocaml") (org "latex"))) '(org-latex-minted-options '(("bgcolor" "LightGoldenrodYellow"))) `(org-latex-logfiles-extensions ',(cl-union '("lof" "lot") org-latex-logfiles-extensions :test #'equal)) '(org-latex-prefer-user-labels t) '(org-latex-subtitle-separate t))) #+end_src *** [[https://github.com/bdarcus/citar][Citar: citing bibliography]] with [[https://orgmode.org/][Org Mode]] :PROPERTIES: :CUSTOM_ID: sec:citing-bibliography :END: [[https://github.com/bdarcus/citar][Citar]] is a completing-read front-end to browse and act on BibTeX, BibLaTeX, as well as CSL JSON bibliographic data with LaTeX, markdown, and org-cite editing support. In combination with vertico, orderless, 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. Listing [[lst:configure-oc-cite+citar]] configures =org-cite=, =citar=, and =org=. #+caption[Configure =org-cite= with =citar=]: #+caption: Configure =oc-cite= with =citar=. #+name: lst:configure-oc-cite+citar #+begin_src emacs-lisp (with-eval-after-load 'oc (require 'oc-biblatex) (require 'oc-csl) (custom-set-variables '(org-cite-export-processors '((latex biblatex) (t csl))) '(org-cite-global-bibliography '("~/VCS/research/refs.bib"))) (when (require 'citar nil 'noerror) (custom-set-variables '(org-cite-activate-processor 'citar) '(org-cite-follow-processor 'citar) '(org-cite-insert-processor 'citar)))) (with-eval-after-load 'org (when (require 'citar nil 'noerror) (custom-set-variables '(citar-bibliography '("~/VCS/research/refs.bib")) '(citar-file-extensions '("djvu" "pdf")) '(citar-library-paths '("~/VCS/research/papers/")))) (define-key org-mode-map (kbd "C-c b") 'org-cite-insert)) #+end_src *** 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]] *** [[info:org#Adding Hyperlink Types][Making Org hyperlink types (info)]] :PROPERTIES: :CUSTOM_ID: sec:making-org-hyperlink-types :END: The code in listing [[lst:define-org-link-types]] defines =org-link= types for backwards compatibility with [[https://github.com/jkitchin/org-ref][org-ref]]. #+caption[Define =org-link= types]: #+caption: Define =org-link= types. #+name: lst:define-org-link-types #+begin_src emacs-lisp (with-eval-after-load 'ol (org-link-set-parameters "ac*" :export (lambda (path _desc backend _info) (pcase backend (`latex (format "\\gls*{%s}" path)) (_ path)))) (org-link-set-parameters "cite" :export (lambda (path _desc backend _info) (pcase backend (`latex (format "\\cite{%s}" path)) (_ path)))) (org-link-set-parameters "eqref" :export (lambda (path _desc backend _info) (pcase backend (`latex (format "\\eqref{%s}" path)) (_ path)))) (org-link-set-parameters "hyperlink" :export (lambda (path desc backend _info) (pcase backend (`latex (format "\\hyperlink{%s}{%s}" path desc)) (_ path)))) (org-link-set-parameters "label" :export (lambda (path _desc backend _info) (pcase backend (`latex (format "\\label{%s}" path)) (_ path)))) (org-link-set-parameters "ref" :export (lambda (path _desc backend _info) (pcase backend (`latex (format "\\ref{%s}" path)) (_ path))))) #+end_src Listing [[lst:emacs-lisp-setup-patch-ol-info]] patches the function =org-info-export= and the constant ~org-info-other-documents~, to export the [[info:org#External Links][info: links (info)]] in this document to =html= and LaTeX correctly. #+caption[Patch =ol-info=]: #+caption: Patch =ol-info=. #+name: lst:emacs-lisp-setup-patch-ol-info #+begin_src emacs-lisp (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 "%s" (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)))) (make-variable-buffer-local 'org-info-emacs-documents) (setq org-info-emacs-documents (delete "org" org-info-emacs-documents)) (make-variable-buffer-local 'org-info-other-documents) (setq org-info-other-documents (cl-union '(("consult" . "https://github.com/minad/consult") ("embark" . "https://github.com/oantolin/embark") ("marginalia" . "https://github.com/minad/marginalia") ("org" . "https://orgmode.org/org.html") ("orderless" . "https://github.com/oantolin/orderless") ("vertico" . "https://github.com/minad/vertico")) org-info-other-documents :test #'equal))) #+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 (with-eval-after-load 'org (defun org-syntax-convert-keyword-case-to-lower () "Convert all #+KEYWORDS to #+keywords." (interactive) (save-excursion (goto-char (point-min)) (let ((count 0) (case-fold-search nil)) (while (re-search-forward "^[ \t]*#\\+[A-Z_]+" nil t) (unless (s-matches-p "RESULTS" (match-string 0)) (replace-match (downcase (match-string 0)) t) (setq count (1+ count)))) (message "Replaced %d keywords" count))))) #+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 (defun my-org-eval-blocks-named (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) (let ((name (org-element-property :name block))) (when (and name (string-match-p infix name)) block)))))) (dolist (block blocks) (goto-char (org-element-property :begin block)) (org-babel-execute-src-block))))) ;; Emacs looks for "Local variables:" after the last "?\n?\f". (add-to-list 'safe-local-eval-forms '(apply 'my-org-eval-blocks-named '("emacs-lisp-setup"))) (add-to-list 'safe-local-eval-forms '(apply 'my-org-eval-blocks-named '("python-setup"))) #+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][-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:emacs-lisp-setup-latex-header]] implements this way by means of two new [[info:org#Editing Source Code][-modes]]: =latex-header= and =latex-extra-header=. #+caption[New =-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:emacs-lisp-setup-latex-header #+begin_src emacs-lisp (with-eval-after-load 'org-src (defun prefix-all-lines (prefix body) (with-temp-buffer (insert body) (string-insert-rectangle (point-min) (point-max) prefix) (buffer-string))) (add-to-list 'org-src-lang-modes '("latex-header" . latex)) (defvar org-babel-default-header-args:latex-header '((:exports . "results") (:results . "raw"))) (defun org-babel-execute:latex-header (body _params) "Execute a block of LaTeX preamble 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)) (add-to-list 'org-src-lang-modes '("latex-extra-header" . latex)) (defvar org-babel-default-header-args:latex-extra-header '((:exports . "results") (:results . "raw"))) (defun org-babel-execute:latex-extra-header (body _params) "Execute a block of LaTeX preamble 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))) #+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 (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)))))) #+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: ]], but only in [[info:org#Export Settings][#+INCLUDE: ]] files. *** [[info:org#Export Settings][#+SETUPFILE: and #+INCLUDE: usage]] :PROPERTIES: :CUSTOM_ID: sec:setupfile+include-usage :END: Evaluation of the source block in listing [[lst:make-source-block-with-export-keyword-settings]] produces the source block that exports to listing [[lst:source-file-export-keyword-settings]] which shows the first six lines of this [[file:README.org]] file. The last two lines show that [[file:setup-include.org][setup-include.org]] is argument for [[info:org#Export Settings][#+SETUPFILE:]] *and* [[info:org#Export Settings][#+INCLUDE:]]. #+caption[Make setup and include file export keyword settings source block]: #+caption: The shell script to make the source block containing the export #+caption: keyword settings. #+name: lst:make-source-block-with-export-keyword-settings #+begin_src shell :exports both :results drawer echo "#+caption[Source file export keyword settings]:" echo "#+caption: The first six lines of README.org containing the export" echo "#+caption: keyword settings." echo "#+name: lst:source-file-export-keyword-settings" echo "#+begin_src org :tangle no" head -n 6 README.org echo -n "#+end_src" #+end_src #+RESULTS: lst:make-source-block-with-export-keyword-settings :results: #+caption[Source file export keyword settings]: #+caption: The first six lines of README.org containing the export #+caption: keyword settings. #+name: lst:source-file-export-keyword-settings #+begin_src org :tangle no #+title: Emacs setup for use with LaTeX, Org, and Python #+author: Gerard Vermeulen #+latex_class: article #+latex_class_options: [11pt,a4paper,english,svgnames,tables] #+setupfile: "setup-include.org" #+include: "setup-include.org" #+end_src :end: Listing [[lst:setup-include-export-keyword-settings]], [[lst:use-latex-header-1]], [[lst:use-latex-header-2]], [[lst:use-latex-header-3]], and [[lst:use-latex-header-4]] tangle into the [[file:setup-include.org][setup-include.org]] file. #+caption[Setup and include file export keyword settings]: #+caption: Setup and include file export keyword settings. #+name: lst:setup-include-export-keyword-settings #+begin_src org ,#+babel: :cache no ,#+macro: kbd (eval (by-backend (html (format "@@html:%s@@" (htmlize-protect-string $1))) (latex (format "@@latex:\\colorbox{PowderBlue}{\\texttt{%s}}@@" $1)))) ,#+property: header-args:emacs-lisp :exports code :results silent :tangle init.el ,#+property: header-args:org :tangle setup-include.org ,#+startup: content #+end_src #+caption[LaTeX preamble: language, lists and floats]: #+caption: LaTeX preamble: language, lists and floats. #+name: lst:use-latex-header-1 #+begin_src org ,#+begin_src latex-header % LANGUAGE: \usepackage{babel} \usepackage{fvextra} \usepackage{csquotes} % LISTS: \usepackage{enumitem} \setlist{noitemsep} % LISTINGS: % Section 2.6 of caption-eng.pdf (texdoc caption) explains that the sign % of "skip" depends on the assumption "position=above" or "position=below". % The assumption should match the real caption position in the LaTeX code. \usepackage{caption} \usepackage[newfloat]{minted} \captionsetup[listing]{position=below,skip=0em} \usemintedstyle{xcode} % TABLES: % https://tex.stackexchange.com/questions/341205/ % what-is-the-difference-between-tabular-tabular-and-tabularx-environments % https://emacs.stackexchange.com/questions/26179/ % change-org-mode-table-style-just-for-latex-export % https://tex.stackexchange.com/questions/468585/ % table-formatting-using-siunitx \usepackage{booktabs} \usepackage{colortbl} \usepackage{tabularx} % DANGER: beware of Org table :width and :align options! ,#+end_src #+end_src #+caption[LaTeX preamble: page layout]: #+caption: LaTeX preamble: page layout. #+name: lst:use-latex-header-2 #+begin_src org ,#+begin_src latex-header % PAGE LAYOUT: \usepackage{fancyhdr} \usepackage{lastpage} \usepackage[ headheight=20mm, top=40mm, bottom=20mm, left=60pt, right=60pt, heightrounded, verbose, ]{geometry} % TECHNICS: \usepackage{siunitx} \usepackage{tikz} ,#+end_src #+end_src #+caption[LaTeX preamble: float barriers]: #+caption: LaTeX preamble: float barriers. #+name: lst:use-latex-header-3 #+begin_src org ,#+begin_src latex-header % FLOAT BARRIERS: % https://tex.stackexchange.com/questions/118662/use-placeins-for-subsections % Make section an implicit float barrier: \usepackage[section]{placeins} % Make subsection an implicit float barrier: \makeatletter \AtBeginDocument{% \expandafter\renewcommand\expandafter\subsection\expandafter{% \expandafter\@fb@secFB\subsection }% } \makeatother % Make subsubsection an implicit float barrier: \makeatletter \AtBeginDocument{% \expandafter\renewcommand\expandafter\subsubsection\expandafter{% \expandafter\@fb@secFB\subsubsection }% } \makeatother ,#+end_src #+end_src #+caption[LaTeX preamble: fancy headers and footers]: #+caption: LaTeX preamble: fancy headers and footers. #+name: lst:use-latex-header-4 #+begin_src org ,#+begin_src latex-header % FANCY HEADERS AND FOOTERS: % Add fancy headers and footers to normal pages. \pagestyle{fancy} \fancyhf{} \renewcommand{\footrulewidth}{0.4pt} \fancyfoot[C]{\emph{ Emacs setup for use with \LaTeX{}, Org, and Python -- Gerard Vermeulen}} \renewcommand{\headrulewidth}{0.4pt} \fancyhead[L]{\includegraphics[height=1.8cm]{Org-mode-unicorn.png}} \fancyhead[C]{ Page: \thepage/\pageref{LastPage} \\ \text{ } \\ \text{ } \\ DRAFT } \fancyhead[R]{\includegraphics[height=1.8cm]{Emacs-logo.png}} % Add fancy header and footer to custom titlepage. % https://tex.stackexchange.com/questions/506102/ % adding-header-and-footer-to-custom-titlepage \fancypagestyle{titlepage}{% \fancyhf{} \renewcommand{\footrulewidth}{0.4pt} \fancyfoot[C]{\emph{ Emacs setup for use with \LaTeX{}, Org, and Python -- Gerard Vermeulen}} \renewcommand{\headrulewidth}{0.4pt} \fancyhead[L]{\includegraphics[height=1.8cm]{Org-mode-unicorn.png}} \fancyhead[C]{ \pageref{LastPage} pages \\ \text{ } \\ \text{ } \\ DRAFT } \fancyhead[R]{\includegraphics[height=1.8cm]{Emacs-logo.png}} } % #+latex_header: END. ,#+end_src #+end_src *** [[info:org#LaTeX specific export settings][Advanced LaTeX export settings]] :PROPERTIES: :CUSTOM_ID: sec:advanced-latex-export-settings :END: [[https://emacs.stackexchange.com/questions/47347/customizing-org-latex-title-command-to-edit-title-page][How to customize =org-latex-title-command= only in this buffer]] #+caption[Define =my-ox-latex-export-buffer-local-variables=]: #+caption: Define =my-ox-latex-export-buffer-local-variables=. #+name: lst:emacs-lisp-setup-defun #+begin_src emacs-lisp :results silent (defun my-ox-latex-export-buffer-local-variables (title-page) (with-eval-after-load 'ox (make-variable-buffer-local 'org-export-before-parsing-hook) (cl-pushnew #'org-latex-header-blocks-filter org-export-before-parsing-hook)) (when (require 'ox-latex nil 'noerror) (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 (mapconcat 'identity '("" "\\tableofcontents\\label{toc}" "\\listoffigures" "\\listoflistings" "\\listoftables" "\\newpage" "") "\n")) (make-variable-buffer-local 'org-latex-subtitle-format) (setq org-latex-subtitle-format ""))) #+end_src Listing [[lst:emacs-lisp-setup-call]] initializes the buffer local variables =org-export-before-parsing-hook=, =org-latex-classes=, =org-latex-title-command=, =org-latex-toc-command=, and =org-latex-subtitle-format=. #+caption[Call =my-ox-latex-export-local-variables=]: #+caption: Use the setup macro for =ox-latex=. #+name: lst:emacs-lisp-setup-call #+header: :var title-page=lst/title-page #+begin_src emacs-lisp :tangle no (my-ox-latex-export-buffer-local-variables title-page) #+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 :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/org-contrib/babel/languages/ob-doc-LaTeX.html#orgcc214c1][Worg: backend dependent execution]] :PROPERTIES: :CUSTOM_ID: sec:backend-dependent-execution-update :END: #+caption[Worg update: backend dependent header arguments]: #+caption: Worg update: backend dependent header arguments. #+name: lst:backend-dependent-execution-update #+begin_src org :tangle worg-backend-dependent-execution-update.org ,#+latex_header: \usepackage{tikz} ,#+header: :file (by-backend (html "tree.svg") (latex nil)) ,#+header: :results (by-backend (html "raw file") (latex "latex replace")) ,#+header: :headers '("\\usepackage{tikz}\n") ,#+begin_src latex \usetikzlibrary{trees} \begin{tikzpicture} \node [circle, draw, fill=red!20] at (0,0) {1} child { node [circle, draw, fill=blue!30] {2} child { node [circle, draw, fill=green!30] {3} } child { node [circle, draw, fill=yellow!30] {4} }}; \end{tikzpicture} ,#+end_src # Setup ,#+begin_src emacs-lisp :exports none (with-eval-after-load 'ox (setq org-babel-latex-htlatex "htlatex") (defmacro by-backend (&rest body) `(cl-case org-export-current-backend ,@body))) ,#+end_src #+end_src #+caption[Specify export backend dependent header arguments]: #+caption: Specify export backend dependent header arguments. #+name: lst:header-arguments-by-backend #+begin_src emacs-lisp (with-eval-after-load 'ox (defmacro by-backend (&rest body) `(cl-case org-export-current-backend ,@body))) #+end_src This section updates the outdated [[https://orgmode.org/worg/org-contrib/babel/languages/ob-doc-LaTeX.html#orgcc214c1][Worg: backend dependent execution]] example to Emacs-27.2 and Org-9.5.2. It shows how to export [[https://en.wikipedia.org/wiki/PGF/TikZ][PGF/TikZ]] images to: 1. [[https://en.wikipedia.org/wiki/PDF][PDF]] by embedding in [[https://www.latex-project.org/][LaTeX]]. 2. [[https://en.wikipedia.org/wiki/HTML][HTML]] by passing from [[https://www.latex-project.org/][LaTeX]] by [[https://en.wikipedia.org/wiki/PDF][PDF]] and to [[https://en.wikipedia.org/wiki/Scalable_Vector_Graphics][SVG]]. Listing [[lst:backend-dependent-execution-update]] tangles to [[file:worg-backend-dependent-execution-update.org][worg-backend-dependent-execution-update.org]] as either a standalone example or an example for inclusion. Listing [[lst:header-arguments-by-backend]] tangles to the =user-init-file= to make the convenience =by-backend= macro always available. Finally, inclusion of [[file:worg-backend-dependent-execution-update.org][worg-backend-dependent-execution-update.org]] produces here the figure with the nodes 1, 2, 3, and 4:\\ #+include: worg-backend-dependent-execution-update.org How to include such figures as floats remains an open question. * Programming :PROPERTIES: :CUSTOM_ID: sec:programming :END: ** Emacs-lisp programming :PROPERTIES: :CUSTOM_ID: sec:emacs-lisp-programming :END: ** [[https://www.seas.upenn.edu/~chaoliu/2017/09/01/python-programming-in-emacs/][Python programming]] :PROPERTIES: :CUSTOM_ID: sec:python-coding :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 packages: 1. [[https://github.com/joaotavora/eglot][Eglot - Emacs polyGLOT: an Emacs LSP client that stays out of your way]]. The maintainer also contributes to Emacs itself and has a deep understanding of [[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://github.com/pythonic-emacs/anaconda-mode][Anaconda - code navigation, documentation lookup, and completion for Python]]. In my opinion, [[https://github.com/joaotavora/eglot][eglot]] has more potential than [[https://github.com/pythonic-emacs/anaconda-mode][anaconda]], but [[https://github.com/pythonic-emacs/anaconda-mode][anaconda]] is compatible with [[info:org#Editing Source Code][source code block editing]] while [[https://github.com/joaotavora/eglot][eglot]] is not. Listing [[lst:configure-python]] configures [[https://www.python.org][Python]]. *** [[https://git.savannah.gnu.org/cgit/emacs.git/tree/lisp/progmodes/python.el][Python-mode]] :PROPERTIES: :CUSTOM_ID: sec:python-mode :END: #+caption[Configure =python=]: #+caption: Configure =python=. #+name: lst:configure-python #+begin_src emacs-lisp (with-eval-after-load 'python (custom-set-variables '(python-indent-guess-indent-offset nil) '(python-shell-interpreter-args "-i -E"))) #+end_src *** [[https://github.com/pyenv/pyenv][Pyenv]] :PROPERTIES: :CUSTOM_ID: sec:pyenv :END: Listing [[lst:enable-pyenv-mode]] configures and enables =pyenv-mode=. #+caption[Enable =pyenv-mode=]: #+caption: Enable =pyenv-mode=. #+name: lst:enable-pyenv-mode #+begin_src emacs-lisp (when (and (executable-find "pyenv") (require 'pyenv-mode nil 'noerror)) (pyenv-mode +1) (pyenv-mode-set "3.9.9/envs/python-3.9.9")) #+end_src *** [[https://github.com/joaotavora/eglot][Eglot]] :PROPERTIES: :CUSTOM_ID: sec:eglot :END: Listing [[lst:configure-eglot+python-lsp-server-for-python]] (tangles to =user-init-file=) and [[lst:configure-eglot+jedi-language-server-for-python]] (does not tangle to =user-init-file=) configure [[https://github.com/joaotavora/eglot][eglot]] for [[https://www.python.org][Python]] using either the [[https://github.com/python-lsp/python-lsp-server][python-lsp-server]] or experimentally the [[https://github.com/pappasam/jedi-language-server][jedi-language-server]]. Listing [[lst:on-hack-local-variables-hook-eglot-maybe]] defines a hook function to launch [[https://github.com/joaotavora/eglot][eglot]] in presence of a proper [[info:emacs#Directory Variables][.dir-locals.el]] file in the root directory of any [[https://www.python.org][Python]] project. Listing [[lst:eglot-directory-variables-for-python]] shows such a proper [[info:emacs#Directory Variables][.dir-locals.el]] file. #+caption[Configure =eglot= with =python-lsp-server= for Python]: #+caption: Configure =eglot= with =python-lsp-server= for Python. #+name: lst:configure-eglot+python-lsp-server-for-python #+begin_src emacs-lisp (with-eval-after-load 'eglot ;; (setq eglot-server-programs '((python-mode "pylsp"))) (add-to-list 'eglot-server-programs '(python-mode "pylsp")) (setq-default eglot-workspace-configuration '((:pylsp . (:plugins (:jedi_completion (:cache_for ["astropy"])))) (:pylsp . (:plugins (:jedi (:auto_import_modules ["numpy"])))) (:pylsp . (:configurationSources ["flake8"]))))) #+end_src #+caption[Configure =eglot= with =jedi-language-server= for Python]: #+caption: Configure =eglot= with =jedi-langage-server= for Python. #+name: lst:configure-eglot+jedi-language-server-for-python #+begin_src emacs-lisp :tangle no (with-eval-after-load 'eglot ;; (setq eglot-server-programs '((python-mode "jedi-language-server"))) (add-to-list 'eglot-server-programs '(python-mode "jedi-language-server")) (setq-default eglot-workspace-configuration '((:jedi-language-server . (:plugins (:jedi (:jediSettings (:autoImportModules ["numpy"])))))))) #+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:on-hack-local-variables-hook-eglot-maybe #+begin_src emacs-lisp (when (fboundp '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 'hack-local-variables-hook (defun on-hack-local-variables-hook-eglot-maybe () (when (and (derived-mode-p 'python-mode) (assoc 'eglot-workspace-configuration dir-local-variables-alist)) (eglot-ensure))))) #+end_src #+caption[Propose =directory-variables= to launch =eglot=]: #+caption: Propose =directory-variables= in the root of any Python project to #+caption: launch =eglot=. #+name: lst:eglot-directory-variables-for-python #+begin_src emacs-lisp :tangle dir-locals.el ;; Proposal for a .dir-locals.el file in the root of any Python project. ((python-mode . ((eglot-workspace-configuration . ((:pylsp . (:plugins (:jedi (:auto_import_modules ["numpy"])))) (:pylsp . (:plugins (:jedi_completion (:cache_for ["astropy"])))) (:pylsp . (:configurationSources ["flake8"]))))))) #+end_src Here are a few links covering how to integrate Emacs, Python and a Python LSP server: 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://jedi.readthedocs.io/en/latest/][Jedi]] provides grammar checking and completion candidates to [[https://github.com/python-lsp/python-lsp-server][python-lsp-server]]. Only [[https://jedi.readthedocs.io/en/latest/docs/changelog.html][jedi-0.18.1]] works with for instance [[https://numpy.org/][numpy-1.22.0]] in the sense that it handles builtins and universal functions provided that [[https://jedi.readthedocs.io/en/latest/][jedi]] does not parse but imports [[https://numpy.org/][numpy-1.22.0]] (see [[https://github.com/davidhalter/jedi/issues/1744][jedi issue #1744]], [[https://github.com/davidhalter/jedi/issues/1745][#1745]], and [[https://github.com/davidhalter/jedi/issues/1746][#1746]]). #+header: :wrap "src diff :exports code :tangle pylsp-auto-import-modules.patch" #+begin_src shell :exports both :results org git -C $HOME/VCS/python-lsp-server diff #+end_src #+RESULTS: #+begin_src diff :exports code :tangle pylsp-auto-import-modules.patch diff --git a/pylsp/config/schema.json b/pylsp/config/schema.json index c29d78b..4f30101 100644 --- a/pylsp/config/schema.json +++ b/pylsp/config/schema.json @@ -69,6 +69,14 @@ "default": null, "description": "List of errors and warnings to enable." }, + "pylsp.plugins.jedi.auto_import_modules": { + "type": "array", + "default": ["numpy", "gi"], + "items": { + "type": "string" + }, + "description": "List of module names for jedi to import (jedi.settings.auto_import_modules)." + }, "pylsp.plugins.jedi.extra_paths": { "type": "array", "default": [], diff --git a/pylsp/workspace.py b/pylsp/workspace.py index bf312f6..4758b53 100644 --- a/pylsp/workspace.py +++ b/pylsp/workspace.py @@ -14,6 +14,8 @@ from . import lsp, uris, _utils log = logging.getLogger(__name__) +DEFAULT_AUTO_IMPORT_MODULES = ["numpy", "gi"] + # TODO: this is not the best e.g. we capture numbers RE_START_WORD = re.compile('[A-Za-z_0-9]*$') RE_END_WORD = re.compile('^[A-Za-z_0-9]*') @@ -252,6 +254,8 @@ class Document: if self._config: jedi_settings = self._config.plugin_settings('jedi', document_path=self.path) + jedi.settings.auto_import_modules = jedi_settings.get('auto_import_modules', + DEFAULT_AUTO_IMPORT_MODULES) environment_path = jedi_settings.get('environment') extra_paths = jedi_settings.get('extra_paths') or [] env_vars = jedi_settings.get('env_vars') #+end_src *** [[https://github.com/pythonic-emacs/anaconda-mode][Anaconda]] :PROPERTIES: :CUSTOM_ID: sec:anaconda :END: Listing [[lst:configure-anaconda+company-for-python]] and [[lst:define-my-toggle-anaconda-mode]] configure [[https://github.com/pythonic-emacs/anaconda-mode][anaconda]]. See [[https://github.com/jorgenschaefer/elpy/blob/8d0de310d41ebf06b22321a8534546447456870c/elpy.el#L2775][elpy-module-company]] for how to handle ~company-backends~ as a local variable in listing [[lst:configure-anaconda+company-for-python]]. The call to [[info:elisp#Advising Functions][advice-add]] in listing [[lst:define-my-toggle-anaconda-mode]] opens Python =org-edit-src-code= buffers in ~anaconda-mode~. #+caption[Configure =anaconda= with =company= for Python]: #+caption: Configure =anaconda= with =company= for Python. #+name: lst:configure-anaconda+company-for-python #+begin_src emacs-lisp (with-eval-after-load 'python (with-eval-after-load 'company (when (and (fboundp 'anaconda-mode) (fboundp 'company-anaconda)) (defun my-disable-anaconda-mode () (when (derived-mode-p 'python-mode) (anaconda-mode -1) (make-variable-buffer-local 'company-backends) (setq company-backends (delq 'company-anaconda (mapcar #'identity company-backends))) (anaconda-eldoc-mode -1))) (defun my-enable-anaconda-mode () (when (derived-mode-p 'python-mode) (anaconda-mode +1) (make-variable-buffer-local 'company-backends) (setq company-backends (cons 'company-anaconda (delq 'company-semantic (delq 'company-capf (mapcar #'identity company-backends))))) (anaconda-eldoc-mode (if (file-remote-p default-directory) -1 1))))))) #+end_src #+caption[Define =my-toggle-anaconda-mode= for Python]: #+caption: Define =my-toggle-anaconda-mode= for Python. #+name: lst:define-my-toggle-anaconda-mode #+begin_src emacs-lisp (with-eval-after-load 'python (unless (and (fboundp 'my-disable-anaconda-mode) (fboundp 'my-enable-anaconda-mode)) (when (fboundp 'anaconda-mode) (defun my-disable-anaconda-mode () (when (derived-mode-p 'python-mode) (anaconda-mode -1) (anaconda-eldoc-mode -1))) (defun my-enable-anaconda-mode () (when (derived-mode-p 'python-mode) (anaconda-mode +1) (anaconda-eldoc-mode (if (file-remote-p default-directory) -1 1)))))) (when (fboundp 'my-enable-anaconda-mode) (advice-add 'org-edit-src-code :after #'my-enable-anaconda-mode)) (when (and (fboundp 'my-disable-anaconda-mode) (fboundp 'my-enable-anaconda-mode)) (defun my-toggle-anaconda-mode () "Toggle anaconda-mode with bells and whistles." (interactive) (if (bound-and-true-p anaconda-mode) (my-disable-anaconda-mode) (my-enable-anaconda-mode))))) #+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 and either [[https://github.com/pythonic-emacs/anaconda-mode][anaconda]] or [[https://github.com/joaotavora/eglot][eglot]] works when coding certain functions of for instance [[https://numpy.org/][numpy]] and [[https://scipy.org/][scipy]]. #+caption[Tangle the =example.py= file]: #+caption: Tangle the =example.py= file. #+name: lst:example-py #+begin_src python :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 *** 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]] * Editing :PROPERTIES: :CUSTOM_ID: sec:editing :END: ** [[https://www.emacswiki.org/emacs/DisabledCommands][Enable disabled commands and inform]] :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. #+caption[Configure the =disabled-command-function=]: #+caption: Configure the =disabled-command-function=. #+name: lst:configure-disabled-command-function #+begin_src emacs-lisp (setq disabled-command-function (defun my-enable-this-command (&rest _args) "Called when a disabled command is executed. Enable it and re-execute it." (put this-command 'disabled nil) (message "You typed %s. %s was disabled until now." (key-description (this-command-keys)) this-command) (sit-for 0) (call-interactively this-command))) #+end_src ** [[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 tables by means of =org-narrow-to-table=. #+caption[Configure =narrow-or-widen-dwim=]: #+caption: Configure =narrow-or-widen-dwim=. #+name: lst:configure-narrow-or-widen-dwim #+begin_src emacs-lisp (defun org-narrow-to-table () "Narrow buffer to current table." (interactive) (if (org-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-dwim otherwise. Dwim means: region, org-src-block, org-subtree, or defun, whichever applies first. Narrowing to org-src-block actually calls `org-edit-src-code'. With prefix P, don't widen, just narrow even if buffer is already narrowed." (interactive "P") (declare (interactive-only)) (cond ((and (buffer-narrowed-p) (not p)) (widen)) ((and (bound-and-true-p org-src-mode) (not p)) (org-edit-src-exit)) ((region-active-p) (narrow-to-region (region-beginning) (region-end))) ((derived-mode-p 'org-mode) (or (ignore-errors (org-edit-src-code)) (ignore-errors (org-narrow-to-block)) (ignore-errors (org-narrow-to-table)) (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://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 (unless noninteractive (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 (unless noninteractive (when (require 'ws-butler nil 'noerror) (custom-set-variables '(ws-butler-keep-whitespace-before-point nil)) (add-hook 'prog-mode-hook #'ws-butler-mode) (add-hook 'text-mode-hook #'ws-butler-mode))) #+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 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. #+caption[Configure =smartparens=]: #+caption: Configure =smartparens=. #+name: lst:configure-smartparens #+begin_src emacs-lisp (unless noninteractive ;; To disables pairing of the quote character for lisp modes, ;; require smartparens-config instead of smartparens. (when (require 'smartparens-config nil 'noerror) (custom-set-variables '(sp-base-key-bindings 'sp) '(sp-override-key-bindings '(("C-(" . sp-backward-barf-sexp) ("C-)" . sp-forward-slurp-sexp)))) (dolist (hook '(prog-mode-hook text-mode-hook)) (add-hook hook #'smartparens-mode)) ;; Hook on the specific `eval-expression-minibuffer-setup-hook' ;; and not on the general `minibuffer-setup-hook'. (dolist (hook '(emacs-lisp-mode-hook eval-expression-minibuffer-setup-hook ielm-mode-hook python-mode-hook)) (add-hook hook #'smartparens-strict-mode)) ;; Tweak for the call to `smartparens-strict-mode' hooked on ;; `eval-expression-minibuffer-setup-hook'. (sp-with-modes '(fundamental-mode ; first usage. minibuffer-inactive-mode) ; later usage. (sp-local-pair "'" nil :actions nil)) ;; 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-mode]] 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-mode=]: #+caption: Configure =electric-operator-mode=. #+name: lst:configure-electric-operator-mode #+begin_src emacs-lisp (when (fboundp 'electric-operator-mode) (add-hook 'c-mode-common #'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 (when (require 'yasnippet nil 'noerror) (custom-set-variables '(yas-alias-to-yas/prefix-p nil)) (yas-global-mode +1)) #+end_src * [[https://lists.gnu.org/archive/html/emacs-devel/2020-04/msg00775.html][Appearance]] :PROPERTIES: :CUSTOM_ID: sec:appearance :END: ** [[info:emacs#Faces][Text faces (or styles)]] :PROPERTIES: :CUSTOM_ID: sec:text-faces-or-styles :END: 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. In case of proper initialization of all face heigths, font scaling is easy as listing [[lst:my-set-default-face-height]] shows. Finally, the code in listing [[lst:my-invert-default-face]] allows swapping the foreground and background colors of the default face on all frames. #+caption[Configure =face-attributse=]: #+caption: Configure =face-attributes=. #+name: lst:configure-face-attributes #+begin_src emacs-lisp (unless noninteractive ;; Set face attributes. (cond ((eq system-type 'darwin) (set-face-attribute 'default nil :family "Hack" :height 120) (set-face-attribute 'fixed-pitch nil :family "Hack") (set-face-attribute 'variable-pitch nil :family "FiraGo")) ((eq system-type 'gnu/linux) (set-face-attribute 'default nil :family "Hack" :height 110) (set-face-attribute 'fixed-pitch nil :family "Hack") (set-face-attribute 'variable-pitch nil :family "FiraGo")) (t (set-face-attribute 'default nil :family "Hack" :height 110) (set-face-attribute 'fixed-pitch nil :family "Hack") (set-face-attribute 'variable-pitch nil :family "DejaVu Sans")))) #+end_src #+caption[Implement =my-set-default-face-height=]: #+caption: Implement =my-set-default-face-height=. #+name: lst:my-set-default-face-height #+begin_src emacs-lisp (unless noninteractive (defun my-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[Implement =my-invert-default-face=]: #+caption: Implement =my-invert-default-face=. #+name: lst:my-invert-default-face #+begin_src emacs-lisp (unless noninteractive (defun my-invert-default-face () "Invert the default face." (interactive) (invert-face 'default))) #+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 (when (fboundp 'rainbow-mode) (custom-set-variables '(rainbow-x-colors-major-mode-list '(c++-mode c-mode emacs-lisp-mode inferior-emacs-lisp-mode java-mode lisp-interaction-mode org-mode python-mode))) (rainbow-mode +1)) #+end_src ** [[info:emacs#Custom Themes][Custom Themes (info)]] :PROPERTIES: :CUSTOM_ID: sec:custom-themes :END: This setup prefers the ~leuven~ and ~leuven-dark~ themes because the ~modus-operandi~ and ~modus-vivendi~ themes feel quirky: for instance those themes fail to display ~hl-line-mode~ properly with Emacs-27.2 on Darwin. [[https://emacs.stackexchange.com/questions/17431/how-do-i-change-portions-of-a-custom-theme][How to change custom theme faces]] #+caption[Hack =leuven-theme=]: #+caption: Hack =leuven-theme=. #+name: lst:hack-leuven-theme #+begin_src emacs-lisp (unless noninteractive ;; Try to detect `leuven-theme` from MELPA. (when (fboundp 'leuven-scale-font) (custom-set-variables '(leuven-scale-org-agenda-structure 1.0) '(leuven-scale-org-document-title 1.0) '(leuven-scale-outline-headlines 1.0) '(leuven-scale-volatile-highlight 1.0))) (defun my-leuven-hook-function () (when (member 'leuven custom-enabled-themes) (let ((custom-inhibit--theme-enable nil) (ol1 (list :height 1.0 :weight 'bold :foreground "#3C3C3C" :background "#F0F0F0")) (ol2 (list :height 1.0 :weight 'bold :foreground "#123555" :background "#E5F4FB"))) (custom-theme-set-faces 'leuven `(font-latex-sectioning-2-face ((t ,ol1))) `(font-latex-sectioning-3-face ((t ,ol2))) `(info-title-1 ((t ,ol1))) `(markdown-header-face-1 ((t ,ol1))) `(markdown-header-face-2 ((t ,ol2))) `(org-level-1 ((t ,ol1))) `(org-level-2 ((t ,ol2))) `(outline-1 ((t ,ol1))) `(outline-2 ((t ,ol1))))) (enable-theme 'leuven))) ;; (load-theme 'leuven 'no-confirm nil) (dolist (hook '(Info-mode-hook LaTeX-mode-hook markdown-mode-hook org-mode-hook outline-mode-hook)) (add-hook hook #'my-leuven-hook-function))) #+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: #+caption[Implement =my-pulse-one-line=]: #+caption: Implement =my-pulse-one-line=. #+name: lst:my-pulse-one-line #+begin_src emacs-lisp (unless noninteractive ;; https://karthinks.com/software/batteries-included-with-emacs/ ;; https://www.reddit.com/r/emacs/comments/jwhr6g/batteries_included_with_emacs/ (defun my-pulse-one-line (&rest _) "Pulse the current line." (let ((pulse-iterations 16) (pulse-delay 0.1)) (pulse-momentary-highlight-one-line (point)))) (dolist (command '(scroll-up-command scroll-down-command recenter-top-bottom other-window)) (advice-add command :after #'my-pulse-one-line))) #+end_src * Applications :PROPERTIES: :CUSTOM_ID: sec:applications :END: ** [[https://github.com/skeeto/elfeed#readme][Elfeed: Emacs web feed reader]] :PROPERTIES: :CUSTOM_ID: sec:emacs-web-feed-reader :END: #+caption[Enable =elfeed=]: #+caption: Enable =elfeed=. #+name: lst:enable-elfeed #+begin_src emacs-lisp (autoload 'elfeed "elfeed" nil t) (global-set-key (kbd "C-x w") #'elfeed) (with-eval-after-load 'elfeed (custom-set-variables '(elfeed-feeds '(("http://www.howardism.org/index.xml" h-abrams) ("https://ambrevar.xyz/atom.xml" p-neirhardt) ("https://emacshorrors.com/feed.atom" v-schneidermann) ("https://emacsninja.com/emacs.atom" v-schneidermann) ("https://feeds.feedburner.com/InterceptedWithJeremyScahill" j-scahill) ("https://nullprogram.com/feed/" c-wellons) ("https://oremacs.com/atom.xml" o-krehel) ("https://planet.emacslife.com/atom.xml" planet-emacs) ("https://protesilaos.com/codelog.xml" p-stavrou) ("https://sachachua.com/blog/category/emacs/feed" s-chua) ("https://sciencescitoyennes.org/feed/" sciences) ("https://updates.orgmode.org/feed/updates" org-updates) ("https://www.aclu.org/taxonomy/feed-term/2152/feed" aclu) ("https://www.bof.nl/rss/" bof) ("https://www.democracynow.org/podcast-video.xml" dn) ("https://www.laquadrature.net/fr/rss.xml" lqdn) ("https://www.lemonde.fr/blog/huet/feed/" sciences))))) #+end_src ** [[info:emms#Top][Emacs Multimedia System (info)]] :PROPERTIES: :CUSTOM_ID: sec:emacs-multimedia-system :END: #+caption[Enable =emms=]: #+caption: Enable =emms=. #+name: lst:enable-emms #+begin_src emacs-lisp (custom-set-variables '(emms-mode-line-format "") '(emms-player-list '(emms-player-mpd emms-player-mpv)) `(emms-player-mpd-music-directory ,(expand-file-name "~/Music")) '(emms-player-mpd-server-name "localhost") '(emms-player-mpd-server-port "6600") '(emms-player-mpd-verbose t) '(emms-playing-time-display-format " %s ") '(emms-playlist-mode-center-when-go t)) (defun my-emms-print-metadata-find () (require 'find-func) (locate-file "emms-print-metadata" (expand-file-name "src" (file-name-directory (find-library-name "emms"))) exec-suffixes #'file-executable-p)) (with-eval-after-load 'emms (require 'emms-info-libtag) (let ((emms-print-metadata (my-emms-print-metadata-find))) (when emms-print-metadata (custom-set-variables '(emms-info-functions nil) `(emms-info-libtag-program-name ,emms-print-metadata)) (add-hook 'emms-info-functions #'emms-info-libtag)))) (with-eval-after-load 'elfeed-show (when (require 'emms-setup nil 'noerror) (emms-all))) (autoload 'emms-streams "emms-streams" nil 'interactive) (with-eval-after-load 'emms-streams (emms-all)) #+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 (provide 'init) ;; Emacs looks for "Local variables:" after the last "?\n?\f". ;; Local Variables: ;; indent-tabs-mode: nil ;; End: ;;; init.el ends here #+end_src * Local variables linking to [[#sec:latexmk-save-compile-display-loop][Latexmk save-compile-display-loop]] :PROPERTIES: :CUSTOM_ID: sec:local-variables :END: Only the [[info:org#Top][Org]] source file shows the local variables footer. # Emacs looks for "Local variables:" after the last "\?n\?f". # Local Variables: # compile-command: "latexmk -interaction=nonstopmode -lualatex -pvc -shell-escape README.tex" # eval: (apply 'my-org-eval-blocks-named '("emacs-lisp-setup")) # fill-column: 80 # End: