dark theme

Luca's literate Emacs config

Table of Contents

1 Introduction

1.1 This file

This file (readme.org) is my literate emacs configuration. Every time I save the file, the code blocks get tangled. By default, they get tangled (in sequence) to ./init.el. Some blocks override this default (e.g. see the section early-init.el).

This file also is exported to HTML, it can be viewed here. See this section for my configuration. You can access my blog at the same website.

1.2 Why vanilla?

I used DOOM emacs for an year and I was an happy user. One day I woke up with the wish to understand Emacs a little more.

After about a week (12/01/2021) I had restored my configuration and in the process I understood better concepts such as:

  • hooks
  • minor and major modes
  • advices

It is still a long journey but I am glad I started it.

1.3 Why literate?

Having your configuration in org-mode has some benefits and some drawbacks. It adds a layer of abstraction between me and my init.el file, is it worth it?

The main drawback is that it can happen that the org-mode file has a mistake and tangles an incorrect init.el file. In that case you can't use your nice bindings but you are thrown in barebones emacs and you have to C-x C-f your way to the init.el and run check-parens.

Another drawback is that a big configuration can be slow to tangle and tangling on save can block emacs. See this section for a solution to this drawback.

Let's consider some of the benefits:

  • I can export this file to HTML (here)
  • People can read this file on Github (here)
  • I can comfortably document my configuration (and not from within comments), include links, sh code blocks, etc.
  • I can organize my configuration blocks in sections, easily disable some headings with COMMENT
  • If I wanted, I could use noweb and tangle to break up the configuration in files

1.4 How can I fork it?

I guess one could fork this repo and use this org file as template. You can duplicate this file, give it another name and tangle that file to your init.el.

I would start with a "small" configuration, just with the "core" functionalities. For example the Startup, the Package manager and general sections would be a good starting point.

Then, you can start importing "sections" you are curious about, for example Completion framework . You could also COMMENT all headings and uncomment only those which are interesting to you. You could find the org-toggle-comment command useful, after selecting all headings in the file.

1.5 Structure of this configuration

  • In the second section some optimization of startup time, mostly stolen from smart people.
  • In the third section we bootstrap straight and use-package, our package managers
  • In the fourth section we configure emacs with sane defaults and extend some its core features (e.g. help-mode)
  • In the fifth section we set up general, which we use to manage our keybindings and lazy loading of packages. Afterwards we configure evil, for modal editing.
  • In the sixth section the invaluable org-mode with several extensions
  • The remaining sections declare my personal configuration of UI and core packages, leveraging the great tools described in this list.

1.6 Notable sections

I am particularly proud of some sections in this configuration, because of one or more of these reasons:

  • They improve my productivity considerably
  • They are non-standard solutions (or at least hard to find online)
  • They are particularly fine-tuned to my workflow

Here they are, without any ranking:

2 Startup

2.1 early-init.el

Taken from DOOM's early init

  ;; NOTE: early-init.el is now generated from readme.org.  Please edit that file instead

  ;; Defer garbage collection further back in the startup process
  (setq gc-cons-threshold most-positive-fixnum
        gc-cons-percentage 0.6)

  ;; In Emacs 27+, package initialization occurs before `user-init-file' is
  ;; loaded, but after `early-init-file'. Doom handles package initialization, so
  ;; we must prevent Emacs from doing it early!
  (setq package-enable-at-startup nil)
  ;; Do not allow loading from the package cache (same reason).
  (setq package-quickstart nil)

  ;; Prevent the glimpse of un-styled Emacs by disabling these UI elements early.
  (push '(menu-bar-lines . 0) default-frame-alist)
  (push '(tool-bar-lines . 0) default-frame-alist)
  (push '(vertical-scroll-bars) default-frame-alist)

  ;; Resizing the Emacs frame can be a terribly expensive part of changing the
  ;; font. By inhibiting this, we easily halve startup times with fonts that are
  ;; larger than the system default.
  (setq frame-inhibit-implied-resize t)

  ;; Disable GUI elements
  (menu-bar-mode -1)
  (tool-bar-mode -1)
  (scroll-bar-mode -1)
  (setq inhibit-splash-screen t)
  (setq use-file-dialog nil)

  ;; Prevent unwanted runtime builds in gccemacs (native-comp); packages are
  ;; compiled ahead-of-time when they are installed and site files are compiled
  ;; when gccemacs is installed.
  (setq comp-deferred-compilation nil)

2.2 Startup optimization

Taken from DOOM's init

;; NOTE: init.el is now generated from readme.org.  Please edit that file instead

;; `file-name-handler-alist' is consulted on every `require', `load' and various
;; path/io functions. You get a minor speed up by nooping this. However, this
;; may cause problems on builds of Emacs where its site lisp files aren't
;; byte-compiled and we're forced to load the *.el.gz files (e.g. on Alpine)
(unless (daemonp)
  (defvar doom--initial-file-name-handler-alist file-name-handler-alist)
  (setq file-name-handler-alist nil)
  ;; Restore `file-name-handler-alist' later, because it is needed for handling
  ;; encrypted or compressed files, among other things.
  (defun doom-reset-file-handler-alist-h ()
    ;; Re-add rather than `setq', because changes to `file-name-handler-alist'
    ;; since startup ought to be preserved.
    (dolist (handler file-name-handler-alist)
      (add-to-list 'doom--initial-file-name-handler-alist handler))
    (setq file-name-handler-alist doom--initial-file-name-handler-alist))
  (add-hook 'emacs-startup-hook #'doom-reset-file-handler-alist-h)
  (add-hook 'after-init-hook '(lambda ()
                                 ;; restore after startup
                                 (setq gc-cons-threshold 16777216
                                       gc-cons-percentage 0.1)))
;; Ensure Doom is running out of this file's directory
(setq user-emacs-directory (file-truename (file-name-directory load-file-name)))

3 Package manager

3.1 bootstrap straight and straight-use-package

Some rules/conventions:

  • Prefer :init to :custom. Prefer multiple setq expressions to one.
  • Default to :defer t, use :demand to force loading
  • When packages do not require installation e.g. dired, we need :straight (:type built-in)
  • If you specify :commands, they will be autoloaded and the package will be loaded when the commands are first executed
    • If you use :general and bind commands to keys it will automatically load the package on first invokation
(setq straight-use-package-by-default t)
(setq straight-vc-git-default-clone-depth 1)
(setq straight-recipes-gnu-elpa-use-mirror t)
(setq straight-check-for-modifications '(check-on-save find-when-checking))
(setq use-package-always-defer t)
(defvar bootstrap-version)
(let* ((straight-repo-dir
        (expand-file-name "straight/repos" user-emacs-directory))
        (concat straight-repo-dir "/straight.el/bootstrap.el"))
       (bootstrap-version 5))
  (unless (file-exists-p bootstrap-file)
      "mkdir -p " straight-repo-dir " && "
      "git -C " straight-repo-dir " clone "
      "https://github.com/raxod502/straight.el.git && "
      "git -C " straight-repo-dir " checkout 2d407bc")))
  (load bootstrap-file nil 'nomessage))
(straight-use-package 'use-package)
;; This is a variable that has been renamed but straight still refers when
;; doing :sraight (:no-native-compile t)
(setq comp-deferred-compilation-black-list nil)

3.2 straight lockfile

We can run M-x straight-freeze-versions to write the file straight/versions/default.el. The content of the file can then be kept in a code block, under version control. The code block can then be tangle again to straight/versions/default.el. We can then restore package versions using M-x straight-thaw-versions.

(("ESS" . "a9e9367976658391126c907b6a5dfc8ad3033ebd")
 ("a.el" . "3d341eb7813ee02b00ab28e11c915295bfd4b5a7")
 ("ace-window" . "c7cb315c14e36fded5ac4096e158497ae974bec9")
 ("aggressive-indent-mode" . "b0ec0047aaae071ad1647159613166a253410a63")
 ("all-the-icons-dired" . "fc2dfa1e9eb8bf1c402a675e7089638d702a27a5")
 ("all-the-icons.el" . "6917b08f64dd8487e23769433d6cb9ba11f4152f")
 ("annalist.el" . "134fa3f0fb91a636a1c005c483516d4b64905a6d")
 ("avy" . "e92cb37457b43336b765630dbfbea8ba4be601fa")
 ("blacken" . "784da60033fe3743336d1da0f33239f1bf514266")
 ("bui.el" . "f3a137628e112a91910fd33c0cff0948fa58d470")
 ("centaur-tabs" . "50fd573ce9ed9f914940c79c82e411511ca5c8a8")
 ("centered-cursor-mode.el" . "4093821cc9759ca5a3c6e527d4cc915fc3a5ad74")
 ("cfrs" . "7c42f2c82c7ae689f3ef291b066688c58ab96298")
 ("cider" . "9c137c52cf5b769fcc52b4e8108acda10638f766")
 ("clojure-mode" . "53ef8ac076ae7811627fbdd408e519ab7fca9a0b")
 ("company-box" . "ec8f44674dc10dd4d50785a1f97820b29d392ea2")
 ("company-mode" . "6116c4617a7934acfe84cb82a058e9b198f0f480")
 ("consult" . "b8ab017488b3f04bbfdc1a9610af3224339d943c")
 ("dap-mode" . "88eda699e77e86410cdf3eb6afb790303dd27728")
 ("dash.el" . "0e975782086020aa12863fdb658d6a3cc748a10c")
 ("diff-hl" . "89aeb2fc8b24b6c4de4394f85041c5dd5fa60dad")
 ("dired-hide-dotfiles" . "964b860a40ac0d585c71760ed2ecdfa6ce7c4184")
 ("dired-single" . "98c2102429fcac6fbfdba9198c126eb1b3dcc4e5")
 ("docker-tramp.el" . "8e2b671eff7a81af43b76d9dfcf94ddaa8333a23")
 ("doom-modeline" . "49816da1a6c05e6215ec3c8aac6c5eabeb47e74c")
 ("eldoc" . "b906386cf04029e01228fde239c3a2e3e5b53603")
 ("elfeed" . "e29c8b91450bd42d90041231f769c4e5fe5070da")
 ("elisp-refs" . "b3634a4567c655a1cda51b217629849cba0ac6a7")
 ("emacs-async" . "14f48de586b0977e3470f053b810d77b07ea427a")
 ("emacs-bind-map" . "bf4181e3a41463684adfffc6c5c305b30480e30f")
 ("emacs-dashboard" . "2b1ef13392be2f07d2a52636edf578b89512d501")
 ("emacs-hide-mode-line" . "88888825b5b27b300683e662fa3be88d954b1cea")
 ("emacs-htmlize" . "49205105898ba8993b5253beec55d8bddd820a70")
 ("emacs-jupyter" . "360cae2c70ab28c7a7848c0c56473d984f0243e5")
 ("emacs-libvterm" . "6f95a1b2949f60539fd92e3a63011801a7e765fd")
 ("emacs-memoize" . "51b075935ca7070f62fae1d69fe0ff7d8fa56fdd")
 ("emacs-python-pytest" . "4a1c4c8915c12e540d41aae1d4e326a2362da541")
 ("emacs-tree-sitter" . "076865a6c879840ab61e0aa7b336a2e3e1f97cd4")
 ("emacs-undo-fu" . "c0806c1903c5a0e4c69b6615cdc3366470a9b8ca")
 ("emacs-web-server" . "22ce66ea43e0eadb9ec1d691a35d9695fc29cee6")
 ("emacs-websocket" . "36deb3ff85368d000a88435d5a645ffbab490654")
 ("emacs-which-key" . "428aedfce0157920814fbb2ae5d00b4aea89df88")
 ("emacs-winum" . "c5455e866e8a5f7eab6a7263e2057aff5f1118b9")
 ("emacs-zmq" . "eb4e01715cbf2f356a8ae5e678ffec3380a907dc")
 ("emacsmirror-mirror" . "73d68771488284cceb42f70fda551e0a516cb249")
 ("embark" . "26e73117910e78afa209524ecb8f07add45a9ec3")
 ("envrc" . "a7c6ca84a2b0617c94594a23a0c05246f14fa4ee")
 ("epl" . "78ab7a85c08222cd15582a298a364774e3282ce6")
 ("eros" . "dd8910279226259e100dab798b073a52f9b4233a")
 ("evil" . "6316dae58e95fe019fcb41d2d1ca5847227a16e6")
 ("evil-cleverparens" . "8c45879d49bfa6d4e414b6c1df700a4a51cbb869")
 ("evil-collection" . "f53ef08224f709c732740d45b373ef3617f6d759")
 ("evil-goggles" . "08a22058fd6a167f9f1b684c649008caef571459")
 ("evil-iedit-state" . "30fcfa96ceebed0191337c493f5c2efc8ae090ad")
 ("evil-indent-plus" . "0c7501e6efed661242c3a20e0a6c79a6455c2c40")
 ("evil-lisp-state" . "3c65fecd9917a41eaf6460f22187e2323821f3ce")
 ("evil-mc" . "7dfb2ca5ac00c249cb2f55cd6fa91fb2bfb1117e")
 ("evil-nerd-commenter" . "563cdc154b1f29d181b883563dd37be7eafafdee")
 ("evil-org-mode" . "a9706da260c45b98601bcd72b1d2c0a24a017700")
 ("evil-snipe" . "6dcac7f2516c6137a2de532fc2c052f242559ee3")
 ("evil-surround" . "346d4d85fcf1f9517e9c4991c1efe68b4130f93a")
 ("exec-path-from-shell" . "d14d6d2966efe5a1409f84a6b9d998268f74761d")
 ("f.el" . "c4dbf8c8e83df834f5d6f72cd5649b9d8a8812ec")
 ("frame-local" . "7ee1106c3bcd4022f48421f8cb1ef4f995da816e")
 ("gcmh" . "0089f9c3a6d4e9a310d0791cf6fa8f35642ecfd9")
 ("general.el" . "a0b17d207badf462311b2eef7c065b884462cb7c")
 ("git-timemachine" . "8d675750e921a047707fcdc36d84f8439b19a907")
 ("git.el" . "a3396a7027a7d986598c6a2d6d5599bac918f3da")
 ("gnu-elpa-mirror" . "fcb3cf5ba5f16885f7851885c954222aee6f03ab")
 ("goto-chg" . "2af612153bc9f5bed135d25abe62f46ddaa9027f")
 ("helpful" . "584ecc887bb92133119f93a6716cdf7af0b51dca")
 ("hexrgb" . "90e5f07f14bdb9966648977965094c75072691d4")
 ("highlight-indent-guides" . "cf352c85cd15dd18aa096ba9d9ab9b7ab493e8f6")
 ("hl-todo" . "9661a462d86b22293caaa4c3d94f971a15dbf1d5")
 ("ht.el" . "c4c1be487d6ecb353d07881526db05d7fc90ea87")
 ("hydra" . "2d553787aca1aceb3e6927e426200e9bb9f056f1")
 ("iedit" . "5c792f5fd44797ece83169b7ef6ac6f4b259255d")
 ("inheritenv" . "13c0135ddd96519ddeb993ee21163d6e11b4f464")
 ("lsp-mode" . "8166a1fe04891efcb7f1262609e77fc48500537b")
 ("lsp-pyright" . "71ff088ac4c93b0edd012f305a3dfd1602c5d21e")
 ("lsp-treemacs" . "3bae4a91e05d55d5ca92da272ffcd497f370e9df")
 ("lsp-ui" . "62568188b7cbc0758a0c4bfb57647708406ddf51")
 ("magit" . "25f432551347468ce97b8b03987e59092e91f8f0")
 ("marginalia" . "d38a27867bcec0bafa43e8d1bd3fd96a32b15d31")
 ("markdown-mode" . "377ce39ffe69f058994ac4e98bde8cfb58661406")
 ("melpa" . "ea7b38971ea1ac02d1de128d3526f9b7b9f601f9")
 ("modus-themes" . "6d8b34b89b855eff7ac203b994b689f630e9b8b0")
 ("nix-mode" . "53ea839a52335d089699d3530bae8ea5914cdbb6")
 ("no-littering" . "6e8950ad296c0f57d80d034eb0b7adf538c02906")
 ("ob-async" . "de1cd6c93242a4cb8773bbe115b7be3d4dd6b97e")
 ("olivetti" . "b76a020aedb57a6a7d0ae61cde13434f5c802a44")
 ("org" . "0b117f72a82143f544ef41cc82696337d496fe97")
 ("org-appear" . "19ea96e6e2ce01b8583b25a6e5579f1be207a119")
 ("org-cv" . "2f86bab21d35f76c641b6175d33039422b6ed0db")
 ("org-fragtog" . "0151cabc7aa9f244f82e682b87713b344d780c23")
 ("org-html-themify" . "db0bdeedecb3f311ca39f165abdac809404c6e32")
 ("org-re-reveal" . "d404eb13d9e34354c081870ebdd69711937682b3")
 ("org-reverse-datetree" . "be24274dd62cd3c586cbea99c8f73db251bf319d")
 ("org-superstar-mode" . "7f83636db215bf5a10edbfdf11d12a132864a914")
 ("org-tree-slide" . "d6e8e91433dfe4968f1343b483f2680f45a77d52")
 ("ox-gfm" . "99f93011b069e02b37c9660b8fcb45dab086a07f")
 ("ox-ipynb" . "919b694763035c0ea04a3a368418355185f896b8")
 ("page-break-lines" . "69caea070379f3324c530e96e06625c3cd097cb9")
 ("paredit" . "8330a41e8188fe18d3fa805bb9aa529f015318e8")
 ("parseclj" . "eff941126859bc9e949eae5cd6c2592e731629f2")
 ("parseedn" . "90cfe3df51b96f85e346f336c0a0ee6bf7fee508")
 ("persistent-scratch" . "57221e5fdff22985c0ea2f3e7c282ce823ea5932")
 ("persp-projectile" . "533808b3e4f8f95a1e3ed9c55d9aa720277ebd5f")
 ("perspective-el" . "2f2b59e693f08b8d9c81062fca25e6076b6e7f8d")
 ("pfuture" . "d7926de3ba0105a36cfd00811fd6278aea903eef")
 ("pkg-info" . "76ba7415480687d05a4353b27fea2ae02b8d9d61")
 ("posframe" . "3454a4cb9d218c38f9c5b88798dfb2f7f85ad936")
 ("powerline" . "b293abf83c0a2b2988af19dd0ef0426c3b1d1501")
 ("prescient.el" . "42adc802d3ba6c747bed7ea1f6e3ffbbdfc7192d")
 ("projectile" . "c31bd41c0b9d6fba8837ebfd3a31dec0b3cd73c6")
 ("pyimport" . "a6f63cf7ed93f0c0f7c207e6595813966f8852b9")
 ("python-mode" . "41b123b4d4906cce7591900a952bb75a38c5296c")
 ("queue" . "52206c0f78afc0dfb9a287cb928c1e725103336d")
 ("rainbow-delimiters" . "f43d48a24602be3ec899345a3326ed0247b960c6")
 ("restart-emacs" . "1607da2bc657fe05ae01f7fdf26f716eafead02c")
 ("s.el" . "43ba8b563bee3426cead0e6d4ddc09398e1a349d")
 ("selectrum" . "015798542b441d993d53b967d49df3dc0162ca37")
 ("sesman" . "edee869c209c016e5f0c5cbb8abb9f3ccd2d1e05")
 ("shrink-path.el" . "c14882c8599aec79a6e8ef2d06454254bb3e1e41")
 ("shut-up" . "081d6b01e3ba0e60326558e545c4019219e046ce")
 ("smartparens" . "63695c64233d215a92bf08e762f643cdb595bdd9")
 ("spinner" . "61f59fab44d22cd5add61a1baf3f0b88a5d829d7")
 ("straight.el" . "2d407bccd9378f1d5218f8ba2ae85c6be73fbaf1")
 ("templatel" . "a3458234b8e0e83c46c6aca11a757c1134752c09")
 ("toml-mode.el" . "f6c61817b00f9c4a3cab1bae9c309e0fc45cdd06")
 ("tramp" . "f278fd50dcab872042aa798be5cf87c703814f6f")
 ("transient" . "90e640fe8fa3f309c7cf347501e86ca5cd0bd85e")
 ("transpose-frame" . "12e523d70ff78cc8868097b56120848befab5dbc")
 ("treemacs" . "332d4e0f1f606c472dd083c9cdd4f143ee23020a")
 ("use-package" . "caa92f1d64fc25480551757d854b4b49981dfa6b")
 ("vterm-toggle" . "61cb072af997aa961e2aebe0125b883ff3bd6f43")
 ("weblorg" . "ca7136629fc3f3f1646fa9bd5ad51766062edb39")
 ("with-editor" . "6735180e73e787b79535c245b162249b70dbf841")
 ("xwwp" . "f67e070a6e1b233e60274deb717274b000923231")
 ("yaml-mode" . "fc5e1c58f94472944c4aa838f00f6adcac6fa992")
 ("yasnippet" . "5cbdbf0d2015540c59ed8ee0fcf4788effdf75b6"))

3.3 Enable use-package statistics

If you'd like to see how many packages you've loaded, what stage of initialization they've reached, and how much aggregate time they've spent (roughly), you can enable use-package-compute-statistics after loading use-package but before any use-package forms, and then run the command M-x use-package-report to see the results. The buffer displayed is a tabulated list. You can use S in a column to sort the rows based on it.

(setq use-package-compute-statistics t)

From the report:

  • evil 0.56
  • embark 0.25
  • projectile 0.18

4 Emacs

4.1 Sane defaults

Inspired by https://github.com/natecox/dotfiles/blob/master/emacs/emacs.d/nathancox.org

To debug a LISP function use debug-on-entry. You step in with d and over with e

(use-package emacs
  (setq inhibit-startup-screen t
        initial-scratch-message nil
        sentence-end-double-space nil
        ring-bell-function 'ignore
        frame-resize-pixelwise t)

  (setq user-full-name "Luca Cambiaghi"
        user-mail-address "luca.cambiaghi@me.com")

  (setq read-process-output-max (* 1024 1024)) ;; 1mb

  ;; always allow 'y' instead of 'yes'.
  (defalias 'yes-or-no-p 'y-or-n-p)

  ;; default to utf-8 for all the things
  (set-charset-priority 'unicode)
  (setq locale-coding-system 'utf-8
        coding-system-for-read 'utf-8
        coding-system-for-write 'utf-8)
  (set-terminal-coding-system 'utf-8)
  (set-keyboard-coding-system 'utf-8)
  (set-selection-coding-system 'utf-8)
  (prefer-coding-system 'utf-8)
  (setq default-process-coding-system '(utf-8-unix . utf-8-unix))

  ;; write over selected text on input... like all modern editors do
  (delete-selection-mode t)

  ;; enable recent files mode.
  (recentf-mode t)
  (setq recentf-exclude `(,(expand-file-name "straight/build/" user-emacs-directory)
                          ,(expand-file-name "eln-cache/" user-emacs-directory)
                          ,(expand-file-name "etc/" user-emacs-directory)
                          ,(expand-file-name "var/" user-emacs-directory)))

  ;; don't want ESC as a modifier
  (global-set-key (kbd "<escape>") 'keyboard-escape-quit)

  ;; Don't persist a custom file, this bites me more than it helps
  (setq custom-file (make-temp-file "")) ; use a temp file as a placeholder
  (setq custom-safe-themes t)            ; mark all themes as safe, since we can't persist now
  (setq enable-local-variables :all)     ; fix =defvar= warnings

  ;; stop emacs from littering the file system with backup files
  (setq make-backup-files nil
        auto-save-default nil
        create-lockfiles nil)

  ;; follow symlinks 
  (setq vc-follow-symlinks t)

  ;; don't show any extra window chrome
  (when (window-system)
    (tool-bar-mode -1)
    (toggle-scroll-bar -1))

  ;; enable winner mode globally for undo/redo window layout changes
  (winner-mode t)

  (show-paren-mode t)

  ;; less noise when compiling elisp
  (setq byte-compile-warnings '(not free-vars unresolved noruntime lexical make-local))

  ;; clean up the mode line
  (display-time-mode -1)
  (setq column-number-mode t)
  ;; use common convention for indentation by default
  (setq-default indent-tabs-mode t)
  (setq-default tab-width 2)

4.2 custom minor modes

Reference: https://nullprogram.com/blog/2013/02/06/

  • Mode names should end in -mode
  • The command for toggling the mode should be the same name
  • They keymap for the mode should be called mode-map
  • Mode’s toggle hook should be called mode-hook.
  • The rest of define-minor-mode is a body for arbitrary Lisp, like a defun. It’s run every time the mode is toggled off or on, so it’s like a built-in hook function

4.2.1 org-jupyter-mode

(use-package emacs
  (defun lc/is-jupyter-org-buffer? ()
    (with-current-buffer (buffer-name)
      (goto-char (point-min))
      (re-search-forward "begin_src jupyter-" 10000 t)))
  (define-minor-mode org-jupyter-mode
    "Minor mode which is active when an org file has the string begin_src jupyter-python
    in the first few hundred rows"
    ;; :keymap (let ((map (make-sparse-keymap)))
    ;;             (define-key map (kbd "C-c f") 'insert-foo)
    ;;             map)

  (add-hook 'org-mode-hook (lambda ()
                             (when (lc/is-jupyter-org-buffer?)

4.3 custom variables

(use-package emacs

  (defcustom lc/default-font-family "fira code" 
    "Default font family"
    :type 'string
    :group 'lc)

  (defcustom lc/variable-pitch-font-family  "cantarell"
    "Variable pitch font family"
    :type 'string
    :group 'lc)
  (defcustom lc/laptop-font-size 150
    "Font size used for laptop"
    :type 'int
    :group 'lc)

  (defcustom lc/theme nil
    "Current theme (light or dark)"
    :type 'symbol
    :options '(light dark)
    :group 'lc)

4.4 Font

(use-package emacs
  (defun lc/get-font-size ()
    "font size is calculated according to the size of the primary screen"
    (let* (;; (command "xrandr | awk '/primary/{print sqrt( ($(nf-2)/10)^2 + ($nf/10)^2 )/2.54}'")
           (command "osascript -e 'tell application \"finder\" to get bounds of window of desktop' | cut -d',' -f3")
           (screen-width (string-to-number (shell-command-to-string command))))  ;;<
      (if (> screen-width 2560) lc/laptop-font-size lc/laptop-font-size))) 
  ;; Main typeface
  (set-face-attribute 'default nil :font lc/default-font-family :height (lc/get-font-size))
  ;; Set the fixed pitch face
  (set-face-attribute 'fixed-pitch nil :font lc/default-font-family :height (lc/get-font-size))
  ;; Set the variable pitch face
  (set-face-attribute 'variable-pitch nil :font lc/variable-pitch-font-family :height (lc/get-font-size) :weight 'regular)

4.5 Adjust font size

(use-package emacs
  (defun lc/adjust-font-size (height)
    "Adjust font size by given height. If height is '0', reset font
size. This function also handles icons and modeline font sizes."
    (interactive "nHeight ('0' to reset): ")
    (let ((new-height (if (zerop height)
                        (+ height (face-attribute 'default :height)))))
      (set-face-attribute 'default nil :height new-height)
      (set-face-attribute 'fixed-pitch nil :height new-height)
      (set-face-attribute 'variable-pitch nil :height new-height)
      (set-face-attribute 'mode-line nil :height new-height)
      (set-face-attribute 'mode-line-inactive nil :height new-height)
      (message "Font size: %s" new-height)))

  (defun lc/increase-font-size ()
    "Increase font size by 0.5 (5 in height)."
    (lc/adjust-font-size 5))

  (defun lc/decrease-font-size ()
    "Decrease font size by 0.5 (5 in height)."
    (lc/adjust-font-size -5))

  (defun lc/reset-font-size ()
    "Reset font size according to the `lc/default-font-size'."
    (lc/adjust-font-size 0))

4.6 macOS

(use-package emacs
  (when (eq system-type 'darwin)
    (setq mac-command-modifier 'super)     ; command as super
    (setq mac-option-modifier 'meta)     ; alt as meta
    (setq mac-control-modifier 'control)
    (global-set-key [(s c)] 'kill-ring-save)
    (global-set-key [(s v)] 'yank)
    (global-set-key [(s x)] 'kill-region)
    (global-set-key [(s q)] 'kill-emacs)

4.7 Garbage collector magic hack

Used by DOOM to manage garbage collection

(use-package gcmh
  (gcmh-mode 1))

4.8 helpful

  (use-package helpful
    :after evil
    (setq evil-lookup-func #'helpful-at-point)
    ([remap describe-function] . helpful-callable)
    ([remap describe-command] . helpful-command)
    ([remap describe-variable] . helpful-variable)
    ([remap describe-key] . helpful-key))

4.9 eldoc

  (use-package eldoc
    :hook (emacs-lisp-mode cider-mode))

4.10 exec path from shell

  (use-package exec-path-from-shell
    :if (memq window-system '(mac ns))
    :hook (emacs-startup . (lambda ()
                             (setq exec-path-from-shell-arguments '("-l")) ; removed the -i for faster startup
    ;; :config
    ;; (exec-path-from-shell-copy-envs
    ;;  '("GOPATH" "GO111MODULE" "GOPROXY"
    ;;    "NPMBIN" "LC_ALL" "LANG" "LC_TYPE"
    ;;    "JAVA_HOME"))

4.11 no littering

(use-package no-littering
  (with-eval-after-load 'recentf
    (add-to-list 'recentf-exclude no-littering-var-directory)
    (add-to-list 'recentf-exclude no-littering-etc-directory))

4.12 server mode

(use-package emacs
  (unless (and (fboundp 'server-running-p)

4.13 Auto-pair parenthesis

(use-package emacs
  ((org-jupyter-mode . (lambda () (lc/set-local-electric-pairs '())))
   (org-mode . (lambda () (lc/set-local-electric-pairs '((?= . ?=) (?~ . ?~))))))
  ;; auto-close parentheses
  (electric-pair-mode +1)
  (setq electric-pair-preserve-balance nil)
  ;; mode-specific local-electric pairs
  (defconst lc/default-electric-pairs electric-pair-pairs)
  (defun lc/set-local-electric-pairs (pairs)
    "Example usage: 
    (add-hook 'jupyter-org-interaction-mode '(lambda () (set-local-electric-pairs '())))
    (setq-local electric-pair-pairs (append lc/default-electric-pairs pairs))
    (setq-local electric-pair-text-pairs electric-pair-pairs))

  ;; disable auto pairing for <  >
  (add-function :before-until electric-pair-inhibit-predicate
                (lambda (c) (eq c ?<))))  ;; >

5 Keybindings

5.1 general

In this block we load general and define bindings for generic commands e.g. find-file. The commands provided by packages should be binded in the use-package block, thanks to the :general keyword. NOTE: We need to load general before evil, otherwise the :general keyword in the use-package blocks won't work.

(use-package general
  :demand t

  (general-create-definer lc/leader-keys
    :states '(normal insert visual emacs)
    :keymaps 'override
    :prefix "SPC"
    :global-prefix "C-SPC")

  (general-create-definer lc/local-leader-keys
    :states '(normal visual)
    :keymaps 'override
    :prefix ","
    :global-prefix "SPC m")

    "SPC" '(execute-extended-command :which-key "execute command")
    "`" '((lambda () (interactive) (switch-to-buffer (other-buffer (current-buffer) 1))) :which-key "prev buffer")
    ";" '(eval-expression :which-key "eval sexp")

    "b" '(:ignore t :which-key "buffer")
    "br"  'revert-buffer
    ;; "bs" '((lambda () (interactive)
    ;;          (pop-to-buffer "*scratch*"))
    ;;        :wk "scratch")
    "bd"  'kill-current-buffer

    "c" '(:ignore t :which-key "code")

    "f" '(:ignore t :which-key "file")
    "fD" '((lambda () (interactive) (delete-file (buffer-file-name))) :wk "delete")
    "ff"  'find-file
    "fs" 'save-buffer
    "fr" 'recentf-open-files
    "fR" '((lambda (new-path)
             (interactive (list (read-file-name "Move file to: ") current-prefix-arg))
             (rename-file (buffer-file-name) (expand-file-name new-path)))
           :wk "move/rename")

    "g" '(:ignore t :which-key "git")
    ;; keybindings defined in magit

    "h" '(:ignore t :which-key "describe")
    "he" 'view-echo-area-messages
    "hf" 'describe-function
    "hF" 'describe-face
    "hl" 'view-lossage
    "hL" 'find-library
    "hm" 'describe-mode
    "hk" 'describe-key
    "hK" 'describe-keymap
    "hp" 'describe-package
    "hv" 'describe-variable

    "k" '(:ignore t :which-key "kubernetes")
    ;; keybindings defined in kubernetes.el

    "o" '(:ignore t :which-key "org")
    ;; keybindings defined in org-mode

    ;; "p" '(:ignore t :which-key "project")
    ;; keybindings defined in projectile

    "s" '(:ignore t :which-key "search")
    ;; keybindings defined in consult

    "t"  '(:ignore t :which-key "toggle")
    "t d"  '(toggle-debug-on-error :which-key "debug on error")
    "t l" '(display-line-numbers-mode :wk "line numbers")
    "t w" '((lambda () (interactive) (toggle-truncate-lines)) :wk "word wrap")
    "t +" '(lc/increase-font-size :wk "+ font")
    "t -" '(lc/decrease-font-size :wk "- font")
    "t 0" '(lc/reset-font-size :wk "reset font")

    "u" '(universal-argument :wk "universal")

    "w" '(:ignore t :which-key "window")
    "wl"  'windmove-right
    "wh"  'windmove-left
    "wk"  'windmove-up
    "wj"  'windmove-down
    "wr" 'winner-redo
    "wd"  'delete-window
    "w=" 'balance-windows-area
    "wD" 'kill-buffer-and-window
    "wu" 'winner-undo
    "wr" 'winner-redo
    "wm"  '(delete-other-windows :wk "maximize")

    "x" '(:ignore t :which-key "browser")
    ;; keybindings defined in xwwp

    :states 'normal
    "d" '(:ignore t :which-key "debug")
    "e" '(:ignore t :which-key "eval")
    "t" '(:ignore t :which-key "test")))

5.2 evil

5.2.1 evil mode

Best VIM reference: https://countvajhula.com/2021/01/21/vim-tip-of-the-day-a-series/

Search tricks:

  • * / # to go to next/prev occurence of symbol under point
  • / starts a search, use n / N to go to next/prev
  • Use the gn noun to, for example, change next match with cgn

Some interesting vim nouns:

first character in the line (synonym to ^)
last character on the line (synonym to $)


mark a position in buffer and save it to register a
go to mark a
mark position and filename [
go to next mark
go back to previous mark (kept track automatically)
go to previous change location
go back to insert mode where you left off
jump (out) to previous position (useful after gd)
jump (in) to previous position


record macro q
execute macro q


save object in register a "
paste object in register a "
  • Macros are saved in registers so you can simply "qp and paste your macro!! "

NOTE: I inserted the above quotes because the single double quotes were breaking my VIM object detection in the rest of the file

(use-package evil
    "wv" 'evil-window-vsplit
    "ws" 'evil-window-split)
  (setq evil-want-integration t)
  (setq evil-want-keybinding nil)
  (setq evil-want-C-u-scroll t)
  (setq evil-want-C-i-jump nil)
  (setq evil-want-Y-yank-to-eol t)
  (setq evil-respect-visual-line-mode t)
  (setq evil-undo-system 'undo-fu)
  (setq evil-search-module 'evil-search)  ;; enables gn
  ;; move to window when splitting
  (setq evil-split-window-below t)
  (setq evil-vsplit-window-right t)
  ;; (setq-local evil-scroll-count 0)
  (setq evil-auto-indent nil)
  (evil-mode 1)
  (define-key evil-insert-state-map (kbd "C-g") 'evil-normal-state)
  (evil-set-initial-state 'messages-buffer-mode 'normal)
  (evil-set-initial-state 'dashboard-mode 'normal)
  ;; don't move cursor after ==
  (defun lc/evil-dont-move-cursor (orig-fn &rest args)
    (save-excursion (apply orig-fn args)))
  (advice-add 'evil-indent :around #'lc/evil-dont-move-cursor)

5.2.2 evil-collection

(use-package evil-collection
  :after evil
  (setq evil-collection-magit-use-z-for-folds nil)

5.2.3 eval operator

This section provides a custom eval operator, accessible with gr. This gives you super powers when coupled with custom text objects (provided by evil-indent-plus and evil-cleverparens )

For example:

  • grab evals the form at point
  • grad evals the top-level form (e.g. use-package blocks or functions)
  • grak evals the function in python
  • grr evals the line
(use-package evil
  (defcustom evil-extra-operator-eval-modes-alist
    '(;; (emacs-lisp eval-region)
      ;; (scheme-mode geiser-eval-region)
      (clojure-mode cider-eval-region)
      (jupyter-python jupyter-eval-region) ;; when executing in src block
      (python-mode jupyter-eval-region) ;; when executing in org-src-edit mode
    "Alist used to determine evil-operator-eval's behaviour.
Each element of this alist should be of this form:
MAJOR-MODE denotes the major mode of buffer. EVAL-FUNC should be a function
with at least 2 arguments: the region beginning and the region end. ARGS will
be passed to EVAL-FUNC as its rest arguments"
    :type '(alist :key-type symbol)
    :group 'evil-extra-operator)

  (evil-define-operator evil-operator-eval (beg end)
    "Evil operator for evaluating code."
    :move-point nil
    (interactive "<r>")
    (let* ((mode (if (org-in-src-block-p) (intern (car (org-babel-get-src-block-info))) major-mode))
           (ele (assoc mode evil-extra-operator-eval-modes-alist))
           (f-a (cdr-safe ele))
           (func (car-safe f-a))
           (args (cdr-safe f-a)))
      (if (fboundp func)
          (apply func beg end args)
        (eval-region beg end t))))
  (define-key evil-motion-state-map "gr" 'evil-operator-eval)

5.2.4 evil-goggles

(use-package evil-goggles
  :after evil
  (setq evil-goggles-duration 0.05)
  (push '(evil-operator-eval
          :face evil-goggles-yank-face
          :switch evil-goggles-enable-yank
          :advice evil-goggles--generic-async-advice)

5.2.5 evil-snipe

(use-package evil-snipe
  :after evil
  (evil-snipe-mode +1)
  (evil-snipe-override-mode +1))

5.2.6 evil-nerd-commenter

(use-package evil-nerd-commenter
    "gc" 'evilnc-comment-operator
    "gC" 'evilnc-copy-and-comment-operator)

5.2.7 evil-surround


  • Use S) to surround something without spaces e.g. (sexp)
  • Use S( to surround something with spaces e.g. ( sexp )


(use-package evil-surround
  (:states 'operator
   "s" 'evil-surround-edit
   "S" 'evil-Surround-edit)
  (:states 'visual
   "S" 'evil-surround-region
   "gS" 'evil-Surround-region))

5.2.8 evil-indent-plus

To select a function in python:

  • Stand on a line in the body of the function (root, not an if)
  • Select with vik
(use-package evil-indent-plus
  :after evil
  (define-key evil-inner-text-objects-map "i" 'evil-indent-plus-i-indent)
  (define-key evil-outer-text-objects-map "i" 'evil-indent-plus-a-indent)
  (define-key evil-inner-text-objects-map "k" 'evil-indent-plus-i-indent-up)
  (define-key evil-outer-text-objects-map "k" 'evil-indent-plus-a-indent-up)
  (define-key evil-inner-text-objects-map "j" 'evil-indent-plus-i-indent-up-down)
  (define-key evil-outer-text-objects-map "j" 'evil-indent-plus-a-indent-up-down)

5.2.9 evil-cleverparens

This package provides additional text objects for LISPs. For example:

  • Mark the outer form with v a d
  • Mark the current form with v a f (similar to the b text object)
(use-package evil-cleverparens
  ((emacs-lisp-mode . evil-cleverparens-mode)
   (clojure-mode . evil-cleverparens-mode))
  (setq evil-move-beyond-eol t
        evil-cleverparens-use-additional-bindings nil
        evil-cleverparens-use-s-and-S nil
        ;; evil-cleverparens-swap-move-by-word-and-symbol t
        ;; evil-cleverparens-use-regular-insert t
  ;; :config
  ;; (sp-local-pair 'emacs-lisp-mode "'" nil :actions nil)

5.2.10 evil-iedit-state


toggle occurrence
n / N
next/prev occurrence
restrict scope to function
J / K
extend scope of match down/up
toggle visibility of matches
(use-package evil-iedit-state
    "s e" '(evil-iedit-state/iedit-mode :wk "iedit")
    "s q" '(evil-iedit-state/quit-iedit-mode :wk "iedit quit")))

5.2.11 evil-mc

(use-package evil-mc
    "A" #'evil-mc-make-cursor-in-visual-selection-end
    "I" #'evil-mc-make-cursor-in-visual-selection-beg)
    "Q" #'evil-mc-undo-all-cursors)
  (global-evil-mc-mode 1)

5.3 which-key

(use-package which-key
  :demand t
  (setq which-key-separator " ")
  (setq which-key-prefix-prefix "+")
  ;; (setq which-key-idle-delay 0.5)

6 Org

6.1 org mode

Interesting bits:

  • If you use + in lists it will show up as below:
    • subitem
  • you can cycle to next TODO state with org-shiftright
  • You can access custom agenda views with org-agenda, mapped to SPC o A
  • Yo insert a src block use , i and then type initials e.g. jp for jupyter-python
(use-package org
  :hook ((org-mode . prettify-symbols-mode)
         (org-mode . visual-line-mode)
         (org-mode . variable-pitch-mode))
    "f t" '(org-babel-tangle :wk "tangle")
    "o C" '(org-capture :wk "capture")
    "o l" '(org-todo-list :wk "todo list")
    "o c" '((lambda () (interactive)
              (persp-switch "main")
              (find-file (concat user-emacs-directory "readme.org")))
            :wk "open config")
    "o t" '((lambda () (interactive)
              (persp-switch "main")
              (find-file (concat org-directory "/personal/todo.org")))
            :wk "open todos"))
    :keymaps 'org-mode-map
    "a" '(org-archive-subtree :wk "archive subtree")
    "E" '(org-export-dispatch :wk "export")
    "i" '(org-insert-structure-template :wk "insert src")
    "l" '(:ignore true :wk "link")
    "l l" '(org-insert-link :wk "insert link")
    "l s" '(org-store-link :wk "store link")
    "L" '((lambda () (interactive) (org-latex-preview)) :wk "latex preview")
    ;; "L" '((lambda () (interactive) (org--latex-preview-region (point-min) (point-max))) :wk "latex")
    "r" '(org-refile :wk "refile")
    "n" '(org-toggle-narrow-to-subtree :wk "narrow subtree")
    "p" '(org-priority :wk "priority")
    "s" '(org-sort :wk "sort")
    "t" '(:ignore true :wk "todo")
    "t t" '(org-todo :wk "heading todo")
    "t s" '(org-schedule :wk "schedule")
    "t d" '(org-deadline :wk "deadline"))
   :states 'normal
   "z i" '(org-toggle-inline-images :wk "inline images"))
  ;; general settings
  (setq org-directory "~/Dropbox/org"
        +org-export-directory "~/Dropbox/org/export"
        org-default-notes-file "~/Dropbox/org/personal/todo.org"
        org-id-locations-file "~/Dropbox/org/.orgids"
        ;; org-export-in-background t
        org-src-preserve-indentation t ;; do not put two spaces on the left
        org-startup-indented t
        ;; org-startup-with-inline-images t
        org-hide-emphasis-markers t
        org-catch-invisible-edits 'smart)
  (setq org-image-actual-width nil)
  (setq org-indent-indentation-per-level 1)
  (setq org-list-demote-modify-bullet '(("-" . "+") ("+" . "*")))
  ;; disable modules for faster startup
  (setq org-modules
  (setq org-todo-keywords
        '((sequence "TODO(t)" "NEXT(n)" "PROG(p)" "|" "HOLD(h)" "DONE(d)")))
  (setq-default prettify-symbols-alist '(("#+BEGIN_SRC" . "»")
                                         ("#+END_SRC" . "«")
                                         ("#+begin_src" . "»")
                                         ("#+end_src" . "«")
                                         ("lambda"  . "λ")
                                         ("->" . "→")
                                         ("->>" . "↠")))
  (setq prettify-symbols-unprettify-at-point 'right-edge)
  ;; (efs/org-font-setup)
  (add-to-list 'org-structure-template-alist '("sh" . "src shell"))
  (add-to-list 'org-structure-template-alist '("el" . "src emacs-lisp"))
  (add-to-list 'org-structure-template-alist '("py" . "src python"))
  (add-to-list 'org-structure-template-alist '("clj" . "src clojure"))
  (add-to-list 'org-structure-template-alist '("jp" . "src jupyter-python"))
  (add-to-list 'org-structure-template-alist '("jr" . "src jupyter-R"))
  ;; fontification
  (add-to-list 'org-src-lang-modes '("jupyter-python" . python))
  (add-to-list 'org-src-lang-modes '("jupyter-R" . R))
  ;; latex
  ;; (setq org-latex-compiler "xelatex")
  ;; see https://www.reddit.com/r/emacs/comments/l45528/questions_about_mving_from_standard_latex_to_org/gkp4f96/?utm_source=reddit&utm_medium=web2x&context=3
  (setq org-latex-pdf-process '("TEXINPUTS=:$HOME/git/AltaCV//: tectonic %f"))
  (add-to-list 'org-export-backends 'beamer)
  (plist-put org-format-latex-options :scale 1.5)

6.2 org agenda

(use-package org
    "f t" '(org-babel-tangle :wk "tangle")
    "o a" '(org-agenda-list :wk "agenda")
    "o A" '(org-agenda :wk "agenda")
    "o C" '(org-capture :wk "capture")
    "o l" '(org-todo-list :wk "todo list")
    "o c" '((lambda () (interactive)
              (find-file (concat user-emacs-directory "readme.org")))
            :wk "open config")
    "o n" '((lambda () (interactive) (org-agenda nil "n")) :wk "next")
    "o t" '((lambda () (interactive)
              (find-file (concat org-directory "/personal/todo.org")))
            :wk "open todos"))
  (setq org-agenda-files '("~/dropbox/org/personal/birthdays.org"
                           "~/dropbox/org/personal/todo.org" "~/dropbox/Notes/Test.inbox.org"))
  (setq org-agenda-custom-commands
        '(("d" "Dashboard"
           ((agenda "" ((org-deadline-warning-days 7)))
            (todo "NEXT"
                  ((org-agenda-overriding-header "Next Tasks")))
            (tags-todo "agenda/ACTIVE" ((org-agenda-overriding-header "Active Projects")))))
          ("n" "Next Tasks"
           ((todo "NEXT"
                  ((org-agenda-overriding-header "Next Tasks")))))
          ("w" "Work Tasks" tags-todo "+work")))

6.3 TODO Hack to get org-version to work

(use-package org
  ;; temporary hack until straight.el supports building org properly
  (defun org-git-version ()
    "The Git version of org-mode.
  Inserted by installing org-mode or when a release is made."
    ;; (require 'git)
    ;; (let ((git-repo (expand-file-name
    ;;                  "straight/repos/org/" user-emacs-directory)))
    ;;   (string-trim
    ;;    (git-run "describe"
    ;;             "--match=release\*"
    ;;             "--abbrev=6"
    ;;             "HEAD")))

  (defun org-release ()
    "The release version of org-mode.
  Inserted by installing org-mode or when a release is made."
    ;; (require 'git)
    ;; (let ((git-repo (expand-file-name
    ;;                  "straight/repos/org/" user-emacs-directory)))
    ;;   (string-trim
    ;;    (string-remove-prefix
    ;;     "release_"
    ;;     (git-run "describe"
    ;;              "--match=release\*"
    ;;              "--abbrev=0"
    ;;              "HEAD"))))

  ;; (provide 'org-version)

6.4 org capture templates

(use-package org
(setq org-capture-templates
        `(("b" "Blog" entry
           (file+headline "personal/todo.org" "Blog")
           ,(concat "* WRITE %^{Title} %^g\n"
                    "SCHEDULED: %^t\n"
                    ":CAPTURED: %U\n:END:\n\n"
          ("d" "New Diary Entry" entry(file+olp+datetree"~/Dropbox/org/personal/diary.org" "Daily Logs")
           "* %^{thought for the day}
                 :CATEGORY: %^{category}
                 :SUBJECT:  %^{subject}
                 :MOOD:     %^{mood}

                 \*What was one good thing you learned today?*:
                 - %^{whatilearnedtoday}

                 \*List one thing you could have done better*:
                 - %^{onethingdobetter}

                 \*Describe in your own words how your day was*:
                 - %?")
          ("i" "Inbox" entry
           (file+headline "personal/todo.org" "Inbox")
           ,(concat "* %^{Title}\n"
                    ":CAPTURED: %U\n"
          ("u" "New URL Entry" entry
           (file+function "~/Dropbox/org/personal/dailies.org" org-reverse-datetree-goto-date-in-file)
           "* [[%^{URL}][%^{Description}]] %^g %?")
          ("w" "Work" entry
           (file+headline "personal/todo.org" "Work")
           ,(concat "* TODO [#A] %^{Title} :@work:\n"
                    "SCHEDULED: %^t\n"
                    ":PROPERTIES:\n:CAPTURED: %U\n:END:\n\n"

6.5 org-toc

This function walks a particular directory searching for .org files and headings within them. It creates a table of contents of that directory, with links. It is useful to create an automatic index.org file for org directories. I currently use it for my knowledge library.

(use-package org
  (defun lc/org-toc ()
    (let ((headings (delq nil (cl-loop for f in (f-entries "." (lambda (f) (f-ext? f "org")) t)
                                       (with-current-buffer (find-file-noselect f)
                                          (lambda ()                 ;; <
                                            (when (> 2 (car (org-heading-components)))
                                              (cons f (nth 4 (org-heading-components)))))))))))
      (switch-to-buffer (get-buffer-create "*toc*"))
      (cl-loop for (file . file-headings) in (seq-group-by #'car headings) 
               (insert (format "* [[%s][%s]] \n" file (file-relative-name file)))
               (cl-loop for (file . heading) in file-headings 
                        (insert (format "** [[%s::*%s][%s]]\n" file heading heading))))))

6.6 cycle only one heading

(use-package org
  (defun +org-cycle-only-current-subtree-h (&optional arg)
    "Toggle the local fold at the point, and no deeper.
`org-cycle's standard behavior is to cycle between three levels: collapsed,
subtree and whole document. This is slow, especially in larger org buffer. Most
of the time I just want to peek into the current subtree -- at most, expand
*only* the current subtree.

All my (performant) foldings needs are met between this and `org-show-subtree'
(on zO for evil users), and `org-cycle' on shift-TAB if I need it."
    (interactive "P")
    (unless (eq this-command 'org-shifttab)
        (let (invisible-p)
          (when (and (org-at-heading-p)
                     (or org-cycle-open-archived-trees
                         (not (member org-archive-tag (org-get-tags))))
                     (or (not arg)
                         (setq invisible-p (outline-invisible-p (line-end-position)))))
            (unless invisible-p
              (setq org-cycle-subtree-status 'subtree))
  ;; Only fold the current tree, rather than recursively
  (add-hook 'org-tab-first-hook #'+org-cycle-only-current-subtree-h)

6.7 async tangle

Taken from https://github.com/KaratasFurkan/.emacs.d

(defun lc/async-process (command &optional name filter)
  "Start an async process by running the COMMAND string with bash. Return the
process object for it.

NAME is name for the process. Default is \"async-process\".

FILTER is function that runs after the process is finished, its args should be
\"(process output)\". Default is just messages the output."
   :command `("bash" "-c" ,command)
   :name (if name name
   :filter (if filter filter
             (lambda (process output) (message (s-trim output))))))

(defun lc/tangle-config ()
  "Export code blocks from the literate config file
  ;; prevent emacs from killing until tangle-process finished
  (add-to-list 'kill-emacs-query-functions
               (lambda ()
                 (or (not (process-live-p (get-process "tangle-process")))
                     (y-or-n-p "\"fk/tangle-config\" is running; kill it? "))))
  ;; tangle config asynchronously
   (format "/Applications/Emacs.app/Contents/MacOS/Emacs %s --batch --eval '(org-babel-tangle nil \"%s\")'"
           (expand-file-name "readme.org" user-emacs-directory)
           (expand-file-name "init.el" user-emacs-directory))

6.8 org reverse datetree

(use-package org-reverse-datetree
  :after org :demand)

6.9 org-superstar

(use-package org-superstar
  :hook (org-mode . org-superstar-mode)
  (setq org-superstar-headline-bullets-list '("✖" "✚" "◉" "○" "▶")
        ;; org-superstar-special-todo-items t
        org-ellipsis " ↴ ")

6.10 highlight todo

Look at hl-todo-keyword-faces to know the keywords (can't get to change them..).

(use-package hl-todo
  :hook ((prog-mode org-mode) . lc/hl-todo-init)
  (defun lc/hl-todo-init ()
    (setq-local hl-todo-keyword-faces '(("HOLD" . "#cfdf30")
                                        ("TODO" . "#ff9977")
                                        ("NEXT" . "#b6a0ff")
                                        ("PROG" . "#00d3d0")
                                        ("FIXME" . "#ff9977")
                                        ("DONE" . "#44bc44")
                                        ("REVIEW" . "#6ae4b9")
                                        ("DEPRECATED" . "#bfd9ff")))

6.11 org babel

(use-package org
    :keymaps 'org-mode-map
    "'" '(org-edit-special :wk "edit")
    "-" '(org-babel-demarcate-block :wk "split block")
    "z" '(org-babel-hide-result-toggle :wk "fold result"))
    :keymaps 'org-src-mode-map
    "'" '(org-edit-src-exit :wk "exit")) ;;FIXME
  (setq org-confirm-babel-evaluate nil)
   '((emacs-lisp . t)
     (shell . t)))
  (add-hook 'org-babel-after-execute-hook 'org-display-inline-images 'append)

;; enable mermaid diagram blocks
;; (use-package ob-mermaid
;;   :custom (ob-mermaid-cli-path "~/.asdf/shims/mmdc"))

6.12 ob-async

(use-package ob-async
  :hook (org-load . (lambda () (require 'ob-async)))
  (setq ob-async-no-async-languages-alist '("jupyter-python" "jupyter-R" "jupyter-julia")))

6.13 ob-jupyter


  • We can only load ob-jupyter when we have jupyter on our PATH.
    • We assume jupyter is always installed in a virtual env associated with an .envrc file
    • We load jupyter when we activate envrc-mode if jupyter is available
(use-package jupyter
  :straight (:no-native-compile t :no-byte-compile t) ;; otherwise we get jupyter-channel void
    :keymaps 'org-mode-map
    "=" '((lambda () (interactive) (jupyter-org-insert-src-block t nil)) :wk "block below")
    "m" '(jupyter-org-merge-blocks :wk "merge")
    "+" '(jupyter-org-insert-src-block :wk "block above")
    "?" '(jupyter-inspect-at-point :wk "inspect")
    "x" '(jupyter-org-kill-block-and-results :wk "kill block"))
  :hook ((jupyter-org-interaction-mode . (lambda () (lc/set-local-electric-pairs '((?' . ?')))))
         (jupyter-repl-persistent-mode . (lambda ()  ;; we activate org-interaction-mode ourselves
                                           (when (derived-mode-p 'org-mode)
                                             ;; (setq-local company-backends '((company-capf)))
                                             (setq-local evil-lookup-func #'jupyter-inspect-at-point)
         (envrc-mode . lc/load-ob-jupyter))
  (setq org-babel-default-header-args:jupyter-python '((:async . "yes")
                                                       (:pandoc t)
                                                       (:kernel . "python3")))
  (setq org-babel-default-header-args:jupyter-R '((:pandoc t)
                                                  (:async . "yes")
                                                  (:kernel . "ir")))
  (defun lc/org-load-jupyter ()
    (org-babel-do-load-languages 'org-babel-load-languages
                                 (append org-babel-load-languages
                                         '((jupyter . t)))))
  (defun lc/load-ob-jupyter ()
    ;; only try to load in org-mode
    (when (derived-mode-p 'org-mode)
      ;; skip if already loaded
      (unless (member '(jupyter . t) org-babel-load-languages)
        ;; only load if jupyter is available
        (when (executable-find "jupyter")
  (cl-defmethod jupyter-org--insert-result (_req context result)
    (let ((str
             context (if (jupyter-org--stream-result-p result)
                         (thread-last result
      (if (< (length str) 100000)  ;; >
          (insert str)
        (insert (format ": Result was too long! Length was %d" (length str)))))
    (when (/= (point) (line-beginning-position))
      ;; Org objects such as file links do not have a newline added when
      ;; converting to their string representation by
      ;; `org-element-interpret-data' so insert one in these cases.
      (insert "\n")))
  ;; :config
  ;;Remove text/html since it's not human readable
  ;; (delete :text/html jupyter-org-mime-types)
  ;; (with-eval-after-load 'org-src
  ;;   (add-to-list 'org-src-lang-modes '("jupyter-python" . python))
  ;;   (add-to-list 'org-src-lang-modes '("jupyter-R" . R)))

6.14 org-tree-slide

(use-package org-tree-slide
  :after org
  :hook ((org-tree-slide-play . (lambda () (+remap-faces-at-start-present)))
         (org-tree-slide-stop . (lambda () (+remap-faces-at-stop-present))))
    "t p" '(org-tree-slide-mode :wk "present"))
    :keymaps '(org-tree-slide-mode-map org-mode-map)
    "C-j" 'org-tree-slide-move-next-tree
    "C-k" 'org-tree-slide-move-previous-tree)
  (setq org-tree-slide-activate-message "Presentation mode ON")
  (setq org-tree-slide-deactivate-message "Presentation mode OFF")
  (setq org-tree-slide-indicator nil)
  (setq org-tree-slide-breadcrumbs "    >    ")
  (setq org-tree-slide-heading-emphasis t)
  (setq org-tree-slide-slide-in-waiting 0.025)
  (setq org-tree-slide-content-margin-top 4)
  (defun +remap-faces-at-start-present ()
    (setq-local face-remapping-alist '((default (:height 1.50) variable-pitch)
                                       (fixed-pitch (:height 1.2) fixed-pitch)
                                       ;; (org-verbatim (:height 1.2) org-verbatim)
                                       ;; (org-block (:height 1.2) org-block)
    ;; (setq-local olivetti-body-width 95)
    (olivetti-mode 1)
    (display-fill-column-indicator-mode 0)
    (hide-mode-line-mode 1)
    (diff-hl-mode 0)
    (centaur-tabs-mode 0))
  (defun +remap-faces-at-stop-present ()
    (setq-local face-remapping-alist '((default variable-pitch default)))
    ;; (setq-local olivetti-body-width 120)
    (olivetti-mode 0)
    (display-fill-column-indicator-mode 1)
    (hide-mode-line-mode 0)
    (doom-modeline-mode 1)
    (diff-hl-mode 1)
    (centaur-tabs-mode 1))
  (setq org-tree-slide-breadcrumbs nil)
  (setq org-tree-slide-header nil)
  (setq org-tree-slide-slide-in-effect nil)
  (setq org-tree-slide-heading-emphasis nil)
  (setq org-tree-slide-cursor-init t)
  (setq org-tree-slide-modeline-display nil)
  (setq org-tree-slide-skip-done nil)
  (setq org-tree-slide-skip-comments t)
  (setq org-tree-slide-fold-subtrees-skipped t)
  (setq org-tree-slide-skip-outline-level 8) ;; or 0?
  (setq org-tree-slide-never-touch-face t)
  ;; :config
  ;; (org-tree-slide-presentation-profile)
  ;; :custom-face
  ;; (org-tree-slide-heading-level-1 ((t (:height 1.8 :weight bold))))
  ;; (org-tree-slide-heading-level-2 ((t (:height 1.5 :weight bold))))
  ;; (org-tree-slide-heading-level-3 ((t (:height 1.5 :weight bold))))
  ;; (org-tree-slide-heading-level-4 ((t (:height 1.5 :weight bold))))

6.15 evil-org-mode

Taken from DOOM:

  • nice +org/insert-item-below and +org/dwim-at-point functions
  • evil bindings for org-agenda
  • text objects:
    • use vie to select everything inside a src block
    • use vir to select everything inside a heading
    • use =ie to format
(use-package evil-org-mode
  :straight (evil-org-mode :type git :host github :repo "hlissner/evil-org-mode")
  :hook ((org-mode . evil-org-mode)
         (org-mode . (lambda () 
                       (require 'evil-org)
                       (evil-org-set-key-theme '(textobjects))
                       (require 'evil-org-agenda)
  ([remap evil-org-org-insert-heading-respect-content-below] . +org/insert-item-below) ;; "<C-return>" 
  ([remap evil-org-org-insert-todo-heading-respect-content-below] . +org/insert-item-above) ;; "<C-S-return>" 
    :keymaps 'org-mode-map :states 'normal
    "RET"   #'+org/dwim-at-point)
  (defun +org--insert-item (direction)
    (let ((context (org-element-lineage
                    '(table table-row headline inlinetask item plain-list)
      (pcase (org-element-type context)
        ;; Add a new list item (carrying over checkboxes if necessary)
        ((or `item `plain-list)
         ;; Position determines where org-insert-todo-heading and org-insert-item
         ;; insert the new list item.
         (if (eq direction 'above)
         (org-insert-item (org-element-property :checkbox context))
         ;; Handle edge case where current item is empty and bottom of list is
         ;; flush against a new heading.
         (when (and (eq direction 'below)
                    (eq (org-element-property :contents-begin context)
                        (org-element-property :contents-end context)))

        ;; Add a new table row
        ((or `table `table-row)
         (pcase direction
           ('below (save-excursion (org-table-insert-row t))
           ('above (save-excursion (org-shiftmetadown))

        ;; Otherwise, add a new heading, carrying over any todo state, if
        ;; necessary.
         (let ((level (or (org-current-level) 1)))
           ;; I intentionally avoid `org-insert-heading' and the like because they
           ;; impose unpredictable whitespace rules depending on the cursor
           ;; position. It's simpler to express this command's responsibility at a
           ;; lower level than work around all the quirks in org's API.
           (pcase direction
              (let (org-insert-heading-respect-content)
                (goto-char (line-end-position))
                (insert "\n" (make-string level ?*) " ")))
              (insert (make-string level ?*) " ")
              (save-excursion (insert "\n"))))
           (when-let* ((todo-keyword (org-element-property :todo-keyword context))
                       (todo-type    (org-element-property :todo-type context)))
              (cond ((eq todo-type 'done)
                     ;; Doesn't make sense to create more "DONE" headings
                     (car (+org-get-todo-keywords-for todo-keyword)))

      (when (org-invisible-p)
      (when (and (bound-and-true-p evil-local-mode)
                 (not (evil-emacs-state-p)))
        (evil-insert 1))))

  (defun +org/insert-item-below (count)
    "Inserts a new heading, table cell or item below the current one."
    (interactive "p")
    (dotimes (_ count) (+org--insert-item 'below)))

  (defun +org/insert-item-above (count)
    "Inserts a new heading, table cell or item above the current one."
    (interactive "p")
    (dotimes (_ count) (+org--insert-item 'above)))

  (defun +org/dwim-at-point (&optional arg)
    "Do-what-I-mean at point.
      If on a:
      - checkbox list item or todo heading: toggle it.
      - clock: update its time.
      - headline: cycle ARCHIVE subtrees, toggle latex fragments and inline images in
        subtree; update statistics cookies/checkboxes and ToCs.
      - footnote reference: jump to the footnote's definition
      - footnote definition: jump to the first reference of this footnote
      - table-row or a TBLFM: recalculate the table's formulas
      - table-cell: clear it and go into insert mode. If this is a formula cell,
        recaluclate it instead.
      - babel-call: execute the source block
      - statistics-cookie: update it.
      - latex fragment: toggle it.
      - link: follow it
      - otherwise, refresh all inline images in current tree."
    (interactive "P")
    (let* ((context (org-element-context))
           (type (org-element-type context)))
      ;; skip over unimportant contexts
      (while (and context (memq type '(verbatim code bold italic underline strike-through subscript superscript)))
        (setq context (org-element-property :parent context)
              type (org-element-type context)))
      (pcase type
         (cond ((memq (bound-and-true-p org-goto-map)
               ((and (fboundp 'toc-org-insert-toc)
                     (member "TOC" (org-get-tags)))
                (message "Updating table of contents"))
               ((string= "ARCHIVE" (car-safe (org-get-tags)))
               ((or (org-element-property :todo-type context)
                    (org-element-property :scheduled context))
                 (if (eq (org-element-property :todo-type context) 'done)
                     (or (car (+org-get-todo-keywords-for (org-element-property :todo-keyword context)))
         ;; Update any metadata or inline previews in this subtree
         (when (and (fboundp 'toc-org-insert-toc)
                    (member "TOC" (org-get-tags)))
           (message "Updating table of contents"))
         (let* ((beg (if (org-before-first-heading-p)
                       (save-excursion (org-back-to-heading) (point))))
                (end (if (org-before-first-heading-p)
                       (save-excursion (org-end-of-subtree) (point))))
                (overlays (ignore-errors (overlays-in beg end)))
                 (cl-find-if (lambda (o) (eq (overlay-get o 'org-overlay-type) 'org-latex-overlay))
                 (cl-find-if (lambda (o) (overlay-get o 'org-image-overlay))
           ;; (+org--toggle-inline-images-in-subtree beg end)
           (if (or image-overlays latex-overlays)
               (org-clear-latex-preview beg end)
             (org--latex-preview-region beg end))))

        (`clock (org-clock-update-time-maybe))

         (org-footnote-goto-definition (org-element-property :label context)))

         (org-footnote-goto-previous-reference (org-element-property :label context)))

        ((or `planning `timestamp)

        ((or `table `table-row)
         (if (org-at-TBLFM-p)
               (goto-char (org-element-property :contents-begin context))
               (org-call-with-arg 'org-table-recalculate (or arg t))))))

         (org-table-recalculate arg)
         (when (and (string-empty-p (string-trim (org-table-get-field)))
                    (bound-and-true-p evil-local-mode))
           (evil-change-state 'insert)))


         (save-excursion (org-update-statistics-cookies arg)))

        ((or `src-block `inline-src-block)
         (org-babel-execute-src-block arg))

        ((or `latex-fragment `latex-environment)
         (org-latex-preview arg))

         (let* ((lineage (org-element-lineage context '(link) t))
                (path (org-element-property :path lineage)))
           (if (or (equal (org-element-property :type lineage) "img")
                   (and path (image-type-from-file-name path)))
                (org-element-property :begin lineage)
                (org-element-property :end lineage))
             (org-open-at-point arg))))

        ((guard (org-element-property :checkbox (org-element-lineage context '(item) t)))
         (let ((match (and (org-at-item-checkbox-p) (match-string 1))))
           (org-toggle-checkbox (if (equal match "[ ]") '(16)))))

         (if (or (org-in-regexp org-ts-regexp-both nil t)
                 (org-in-regexp org-tsr-regexp-both nil  t)
                 (org-in-regexp org-link-any-re nil t))
             (call-interactively #'org-open-at-point)
            (org-element-property :begin context)
            (org-element-property :end context))))))))

6.16 exporters

6.16.1 org-html-themify

I use this package to export my config to HTML. I then push it to the gh-pages branch

(use-package org-html-themify
  :after modus-themes
   :type git
   :host github
   :repo "DogLooksGood/org-html-themify"
   :files ("*.el" "*.js" "*.css"))
  :hook (org-mode . org-html-themify-mode)
  ;; otherwise it complains about invalid face
  (require 'hl-line)

6.16.2 ox-gfm

(use-package ox-gfm
  :commands (org-gfm-export-as-markdown org-gfm-export-to-markdown)
  :after org

6.16.3 ox-ipynb

(use-package ox-ipynb
  :straight (ox-ipynb :type git :host github :repo "jkitchin/ox-ipynb")
  :commands (ox-ipynb-export-org-file-to-ipynb-file))

6.16.4 ox-reveal

(use-package org-re-reveal
  :after org
  ;; (setq org-re-reveal-root (expand-file-name "../../" (locate-library "dist/reveal.js" t))
  ;;       org-re-reveal-revealjs-version "4")
  (setq org-re-reveal-root "./reveal.js"
        org-re-reveal-revealjs-version "3.8"
        org-re-reveal-external-plugins  '((progress . "{ src: '%s/plugin/toc-progress/toc-progress.js', async: true, callback: function() { toc_progress.initialize(); toc_progress.create();} }"))

6.16.5 weblorg

(use-package weblorg)

(use-package templatel)

(use-package htmlize)

6.16.6 ox-cv

(use-package ox-altacv
  :straight (ox-altacv :type git :host github :repo "lccambiaghi/org-cv")
  :config (require 'ox-altacv))

6.17 org-appear

Automatically disaply emphasis markers and links when the cursor is on them.

(use-package org-appear
  :straight (org-appear :type git :host github :repo "awth13/org-appear")
  :hook (org-mode . org-appear-mode)
  (setq org-appear-autoemphasis  t)
  (setq org-appear-autolinks t)
  (setq org-appear-autosubmarkers t)

6.18 automatic latex preview

(use-package org-fragtog
  :hook (org-mode . org-fragtog-mode))

6.19 org-encrypt

  • Use org-encrypt-entry on a headline
  • Use org-decrypt-entry to decrypt
(use-package org
  (require 'org-crypt)
  (require 'epa-file)
  (setq org-tags-exclude-from-inheritance (quote ("crypt")))
  (setq org-crypt-key nil)
  (defun ag/reveal-and-move-back ()
    (goto-char ag/old-point))
  (defun ag/org-reveal-after-save-on ()
    (setq ag/old-point (point))
    (add-hook 'after-save-hook 'ag/reveal-and-move-back))
  (defun ag/org-reveal-after-save-off ()
    (remove-hook 'after-save-hook 'ag/reveal-and-move-back))
  (add-hook 'org-babel-pre-tangle-hook 'ag/org-reveal-after-save-on)
  (add-hook 'org-babel-post-tangle-hook 'ag/org-reveal-after-save-off)

6.20 use org-id in links

Taken from https://writequit.org/articles/emacs-org-mode-generate-ids.html

Problem: when exporting org files to HTML, the header anchors are volatile. Once I publish a new HTML version of this file, the previous version's links are no longer valid.

This function adds CUSTOM_ID property to all headings in a file (one-time). We can then use this to link to that heading forever.

Adding it as a after-save-hook automatically adds a CUSTOM_ID to newly created headers.

(use-package org
  (defun lc/org-custom-id-get (&optional pom create prefix)
    "Get the CUSTOM_ID property of the entry at point-or-marker POM.
   If POM is nil, refer to the entry at point. If the entry does
   not have an CUSTOM_ID, the function returns nil. However, when
   CREATE is non nil, create a CUSTOM_ID if none is present
   already. PREFIX will be passed through to `org-id-new'. In any
   case, the CUSTOM_ID of the entry is returned."
    (org-with-point-at pom
      (let ((id (org-entry-get nil "CUSTOM_ID")))
         ((and id (stringp id) (string-match "\\S-" id))
          (setq id (org-id-new (concat prefix "h")))
          (org-entry-put pom "CUSTOM_ID" id)
          (org-id-add-location id (buffer-file-name (buffer-base-buffer)))
  (defun lc/org-add-ids-to-headlines-in-file ()
    "Add CUSTOM_ID properties to all headlines in the current
   file which do not already have one. Only adds ids if the
   `auto-id' option is set to `t' in the file somewhere. ie,
   #+OPTIONS: auto-id:t"
      (goto-char (point-min))
      (when (re-search-forward "^#\\+OPTIONS:.*auto-id:t" 10000 t)
        (org-map-entries (lambda () (lc/org-custom-id-get (point) 'create))))))
  (require 'org-id)
  (setq org-id-link-to-org-use-id 'create-if-interactive-and-no-custom-id)

7 UI

7.1 all the icons

  (use-package all-the-icons)

7.2 doom modeline

  (use-package doom-modeline
    (setq doom-modeline-buffer-encoding nil)
    (setq doom-modeline-env-enable-python nil)
    (setq doom-modeline-height 15)
    (setq doom-modeline-project-detection 'projectile)
    (doom-modeline-mode 1)
    (set-face-attribute 'doom-modeline-evil-insert-state nil :foreground "orange")

7.3 Fancy titlebar for macOS

(use-package emacs
  (add-to-list 'default-frame-alist '(ns-transparent-titlebar . t))
  (add-to-list 'default-frame-alist '(ns-appearance . dark))
  (setq ns-use-proxy-icon  nil)
  (setq frame-title-format nil)

7.4 Modus themes + alternate light/dark themes

I use a particular emacs build which has additional feature for macOS: https://github.com/railwaycat/homebrew-emacsmacport This defines a hook that is run when macOS system theme changes from light to dark. We use this hook to switch from light to dark theme.

(use-package modus-themes
  :straight (modus-themes :type git :host gitlab :repo "protesilaos/modus-themes" :branch "main")
  :hook (modus-themes-after-load-theme . lc/fix-fill-column-indicator)
    "t t" '((lambda () (interactive) (modus-themes-toggle)) :wk "toggle theme"))
  (setq modus-themes-operandi-color-overrides
        '((bg-main . "#fefcf4")
          (bg-dim . "#faf6ef")
          (bg-alt . "#f7efe5")
          (bg-hl-line . "#f4f0e3")
          (bg-active . "#e8dfd1")
          (bg-inactive . "#f6ece5")
          (bg-region . "#c6bab1")
          (bg-header . "#ede3e0")
          (bg-tab-bar . "#dcd3d3")
          (bg-tab-active . "#fdf6eb")
          (bg-tab-inactive . "#c8bab8")
          (fg-unfocused ."#55556f")))
  (setq modus-themes-vivendi-color-overrides
        '((bg-main . "#100b17")
          (bg-dim . "#161129")
          (bg-alt . "#181732")
          (bg-hl-line . "#191628")
          (bg-active . "#282e46")
          (bg-inactive . "#1a1e39")
          (bg-region . "#393a53")
          (bg-header . "#202037")
          (bg-tab-bar . "#262b41")
          (bg-tab-active . "#120f18")
          (bg-tab-inactive . "#3a3a5a")
          (fg-unfocused . "#9a9aab")))
  (setq modus-themes-slanted-constructs t
        ;; modus-themes-no-mixed-fonts t
        modus-themes-bold-constructs t
        modus-themes-fringes 'nil ; {nil,'subtle,'intense}
        modus-themes-mode-line '3d ; {nil,'3d,'moody}
        modus-themes-intense-hl-line nil
        modus-themes-prompts nil ; {nil,'subtle,'intense}
        modus-themes-completions 'moderate ; {nil,'moderate,'opinionated}
        modus-themes-diffs nil ; {nil,'desaturated,'fg-only}
        modus-themes-org-blocks 'greyscale ; {nil,'greyscale,'rainbow}
        modus-themes-headings  ; Read further below in the manual for this one
        '((1 . line-no-bold)
          (t . rainbow-line-no-bold))
        modus-themes-variable-pitch-headings t
        modus-themes-scale-headings t
        modus-themes-scale-1 1.1
        modus-themes-scale-2 1.15
        modus-themes-scale-3 1.21
        modus-themes-scale-4 1.27
        modus-themes-scale-5 1.33)
  (defun lc/load-dark-theme ()
    (setq lc/theme 'dark)
    (with-eval-after-load 'org (plist-put org-format-latex-options :foreground "whitesmoke"))
    (with-eval-after-load 'org-html-themify
      (setq org-html-themify-themes '((light . modus-vivendi) (dark . modus-vivendi))))
  (defun lc/load-light-theme ()
    (setq lc/theme 'light)
    (with-eval-after-load 'org (plist-put org-format-latex-options :foreground "dark"))
    (with-eval-after-load 'org-html-themify
      (setq org-html-themify-themes '((light . modus-operandi) (dark . modus-operandi))))
    (setenv "BAT_THEME" "ansi")
  (defun lc/change-theme-with-mac-system ()
    (let ((appearance (plist-get (mac-application-state) :appearance)))
      (cond ((equal appearance "NSAppearanceNameAqua")
            ((equal appearance "NSAppearanceNameDarkAqua")
  (defun lc/change-theme-with-timers ()
    (run-at-time "00:00" (* 60 60 24) (lc/load-dark-theme))
    (run-at-time "08:00" (* 60 60 24) (lc/load-light-theme))
    (run-at-time "18:00" (* 60 60 24) (lc/load-dark-theme)))
  (defun lc/fix-fill-column-indicator ()
       `(fill-column-indicator ((,class :background ,bg-inactive :foreground ,bg-inactive))))))
  (if (boundp 'mac-effective-appearance-change-hook)
        (add-hook 'after-init-hook 'lc/change-theme-with-mac-system)
        (add-hook 'mac-effective-appearance-change-hook 'lc/change-theme-with-mac-system))
    (add-hook 'after-init-hook 'lc/change-theme-with-timers))

7.5 dashboard

(use-package dashboard
  (setq initial-buffer-choice (lambda () (get-buffer "*dashboard*")))
  (setq dashboard-center-content t)
  (setq dashboard-projects-backend 'projectile)
  (setq dashboard-set-heading-icons t)
  (setq dashboard-set-file-icons t)
  (defun lc/is-after-17-or-weekends? ()
    (or (thread-first (nth 4 (split-string (current-time-string) " ")) ;; time of the day e.g. 18
            (substring 0 2)
            (string-to-number)   ;;<
            (> 16))
        (thread-first (substring (current-time-string) 0 3) ;; day of the week e.g. Fri
            (member  '("Sat" "Sun")))))
  (setq dashboard-banner-logo-title nil)
  (setq dashboard-set-footer nil)
  ;; (setq dashboard-startup-banner [VALUE])
  (setq dashboard-set-navigator t)
  (setq dashboard-navigator-buttons
        `((;; Github
           (,(all-the-icons-octicon "mark-github" :height 1.1 :v-adjust 0.0)
            "Go to wondercast"
            (lambda (&rest _) (browse-url "https://github.com/Maersk-Global/wondercast")))
           ;; Codebase
           (,(all-the-icons-faicon "briefcase" :height 1.1 :v-adjust -0.1)
            "Go to Kanban"
            (lambda (&rest _) (browse-url "https://jira.maerskdev.net/secure/RapidBoard.jspa?rapidView=6378&projectKey=AVOC&quickFilter=15697")))
           ;; Perspectives
           (,(all-the-icons-octicon "history" :height 1.1 :v-adjust 0.0)
            (lambda (&rest _) (persp-state-load persp-state-default-file)))
  (defun lc/dashboard-agenda-entry-format ()
    "Format agenda entry to show it on dashboard. Compared to the original, we remove tags at the end"
    (let* ((schedule-time (org-get-scheduled-time (point)))
           (deadline-time (org-get-deadline-time (point)))
           (item (org-agenda-format-item
                  (dashboard-agenda-entry-time (or schedule-time deadline-time))
                  nil;; (org-get-tags)
           (loc (point))
           (file (buffer-file-name)))
      (dashboard-agenda--set-agenda-headline-face item)
      (list item loc file)))
  (defun lc/dashboard-get-agenda ()
    "Get agenda items for today or for a week from now."
    (org-compile-prefix-format 'agenda)
    (org-map-entries 'lc/dashboard-agenda-entry-format
  (defun lc/dashboard-get-next ()
    "Get agenda items for today or for a week from now."
    (org-compile-prefix-format 'agenda)
    (org-map-entries 'lc/dashboard-agenda-entry-format
  (defun lc/dashboard-insert-next (list-size)
    "Add the list of LIST-SIZE items of next tasks"
    (require 'org-agenda)
    (let ((next (lc/dashboard-get-next)))
       "Next tasks"
       `(lambda (&rest ignore)
          (let ((buffer (find-file-other-window (nth 2 ',el))))
            (with-current-buffer buffer
              (goto-char (nth 1 ',el))
              (switch-to-buffer buffer))))
       (format "%s" (nth 0 el)))))
  ;; exclude work items after 17 and on weekends
  (setq dashboard-match-next-entry "TODO=\"NEXT\"-work")
  (run-at-time "00:00" (* 60 60 24)
               (lambda ()
                 (if (lc/is-after-17-or-weekends?)
                     (setq dashboard-match-agenda-entry "life|habits"
                           dashboard-match-next-entry "TODO=\"NEXT\"-work")
                   (setq dashboard-match-agenda-entry "work|life|habits"
                         dashboard-match-next-entry "TODO=\"NEXT\""
  (set-face-attribute 'dashboard-items-face nil :height (lc/get-font-size))
  ;; do not show tags in agenda view
  (advice-add 'dashboard-get-agenda :override #'lc/dashboard-get-agenda)
  ;; show next tasks in dashboard
  (add-to-list 'dashboard-item-generators  '(next . lc/dashboard-insert-next))
  (setq dashboard-items '((agenda . 5)
                          (next . 10)
                          ;; (bookmarks . 5)
                          ;; (recents  . 5)
                          (projects . 5)))

7.6 centaur tabs

(use-package centaur-tabs
  :hook (emacs-startup . centaur-tabs-mode)
  (general-nmap "gt" 'centaur-tabs-forward
    "gT" 'centaur-tabs-backward)
    "b K" '(centaur-tabs-kill-other-buffers-in-current-group :wk "kill other buffers"))
  (setq centaur-tabs-set-icons t)
  (setq centaur-tabs-set-modified-marker t
        centaur-tabs-modified-marker "M"
        centaur-tabs-cycle-scope 'tabs)
  (setq centaur-tabs-set-close-button nil)
  (centaur-tabs-mode t)
  ;; (centaur-tabs-headline-match)

7.7 centered cursor mode

(use-package centered-cursor-mode
    "t =" '((lambda () (interactive) (centered-cursor-mode 'toggle)) :wk "center cursor")

7.8 hide mode line

  (use-package hide-mode-line
    :commands (hide-mode-line-mode))

7.9 popup management

Taken from https://emacs.stackexchange.com/questions/46210/reuse-help-window

  (setq display-buffer-alist
        `((,(rx bos (or "*Apropos*" "*Help*" "*helpful" "*info*" "*Summary*") (0+ not-newline))
           (display-buffer-reuse-mode-window display-buffer-below-selected)
           (window-height . 0.33)
           (mode apropos-mode help-mode helpful-mode Info-mode Man-mode))))

7.10 winum

(use-package winum
"1" '(winum-select-window-1 :wk "win 1")
"2" '(winum-select-window-2 :wk "win 2")
"3" '(winum-select-window-3 :wk "win 3"))

7.11 transpose frame

  (use-package transpose-frame
      "w t" '(transpose-frame :wk "transpose")
      "w f" '(rotate-frame :wk "flip")))

7.12 persistent scratch

(use-package persistent-scratch
  (org-mode . (lambda ()
                "only set initial-major-mode after loading org"
                (setq initial-major-mode 'org-mode)))
    "bs" '((lambda ()
             "Load persistent-scratch if not already loaded"
               (unless (boundp 'persistent-scratch-mode)
                 (require 'persistent-scratch))
               (pop-to-buffer "*scratch*")))
           :wk "scratch"))
  (setq persistent-scratch-autosave-interval 60)

7.13 olivetti mode

  (use-package olivetti
      "t o" '(olivetti-mode :wk "olivetti"))
    (setq olivetti-body-width 100)
    (setq olivetti-recall-visual-line-mode-entry-state t))

7.14 Fill column indicator

With evil you can:

  • gww to fill the line
  • gqq to fill the line and move to the end of it
  • gwp to fill paragraph
(use-package display-fill-column-indicator
  :straight (:type built-in)
  (prog-mode . display-fill-column-indicator-mode)
  (setq-default fill-column  90)
  ;; (setq display-fill-column-indicator-character "|")

7.15 Whitespace mode

(use-package emacs
  ((org-jupyter-mode . (lambda () (whitespace-mode -1)))
   (org-mode . whitespace-mode))
   whitespace-line-column 90
   whitespace-style       '(face lines-tail)))

7.16 Highlight indentation guides

;; add a visual intent guide
(use-package highlight-indent-guides
  :hook (prog-mode . highlight-indent-guides-mode)
  ;; (setq highlight-indent-guides-method 'column)
  (setq highlight-indent-guides-method 'character)
  ;; (setq highlight-indent-guides-character ?|)
  ;; (setq highlight-indent-guides-character ?❚)
  (setq highlight-indent-guides-character ?‖)
  ;; (setq highlight-indent-guides-responsive 'stack)
  (setq highlight-indent-guides-responsive 'top)
  ;; (setq highlight-indent-guides-auto-enabled nil)
  ;; (set-face-background 'highlight-indent-guides-odd-face "darkgray")
  ;; (set-face-background 'highlight-indent-guides-even-face "dimgray")
  ;; (set-face-foreground 'highlight-indent-guides-character-face "dimgray")

7.17 Enlarge window

Taken from DOOM

(use-package emacs
    "w o" '(doom/window-enlargen :wk "enlargen"))
  (defun doom/window-enlargen (&optional arg)
    "Enlargen the current window to focus on this one. Does not close other
windows (unlike `doom/window-maximize-buffer'). Activate again to undo."
    (interactive "P")
    (let ((param 'doom--enlargen-last-wconf))
      (cl-destructuring-bind (window . wconf)
          (or (frame-parameter nil param)
              (cons nil nil))
         nil param
         (if (and (equal window (selected-window))
                  (not arg)
              (let ((source-window (selected-window)))
                (set-window-configuration wconf)
                (when (window-live-p source-window)
                  (select-window source-window))))
           (prog1 (cons (selected-window) (or wconf (current-window-configuration)))
             (let* ((window (selected-window))
                    (dedicated-p (window-dedicated-p window))
                    (preserved-p (window-parameter window 'window-preserved-size))
                    (ignore-window-parameters t)
                    (window-resize-pixelwise nil)
                    (frame-resize-pixelwise nil))
                     (when dedicated-p
                       (set-window-dedicated-p window nil))
                     (when preserved-p
                       (set-window-parameter window 'window-preserved-size nil))
                     (maximize-window window))
                 (set-window-dedicated-p window dedicated-p)
                 (when preserved-p
                   (set-window-parameter window 'window-preserved-size preserved-p))
                 (add-hook 'doom-switch-window-hook #'doom--enlargened-forget-last-wconf-h)))))))))

8 Completion framework

8.1 selectrum

  (use-package selectrum
    (selectrum-minibuffer-map "C-j" 'selectrum-next-candidate
                              "C-k" 'selectrum-previous-candidate)
    (selectrum-mode t)

8.2 prescient

  (use-package selectrum-prescient
    :after selectrum
    (prescient-persist-mode t)
    (selectrum-prescient-mode t)

  (use-package company-prescient
    :after company
    (company-prescient-mode t))

8.3 marginalia

  (use-package marginalia
    :after selectrum
    (setq marginalia-annotators '(marginalia-annotators-heavy marginalia-annotators-light nil))
    :config (marginalia-mode t))

8.4 embark

Taken from https://github.com/oantolin/embark

You can act on candidates with C-l and ask to remind bindings with C-h

(use-package embark
  (general-nmap "C-l" 'embark-act)
  (selectrum-minibuffer-map "C-l" #'embark-act)
  (embark-file-map "o" 'find-file-other-window) 
  ;; For Selectrum users:
  (defun current-candidate+category ()
    (when selectrum-active-p
      (cons (selectrum--get-meta 'category)

  (add-hook 'embark-target-finders #'current-candidate+category)

  (defun current-candidates+category ()
    (when selectrum-active-p
      (cons (selectrum--get-meta 'category)
             ;; Pass relative file names for dired.

  (add-hook 'embark-candidate-collectors #'current-candidates+category)

  ;; No unnecessary computation delay after injection.
  (add-hook 'embark-setup-hook 'selectrum-set-selected-candidate)

8.5 embark-consult

(use-package embark-consult
  :straight (embark-consult :type git :host github :repo "oantolin/embark" :files ("embark-consult.el"))
  :after (embark consult)
  :demand t ; only necessary if you have the hook below
  ;; if you want to have consult previews as you move around an
  ;; auto-updating embark collect buffer
  (embark-collect-mode . embark-consult-preview-minor-mode))

8.6 consult

To search for multiple words with consult-ripgrep you should search e.g. for #defun#some words . The first filter is passed to an async ripgrep process and the second filter to the completion-style filtering (?).

(use-package consult
  :commands (consult-ripgrep)
    :states '(normal insert)
    "C-p" 'consult-yank-pop)
    "s i" '(consult-isearch :wk "isearch")
    "s o" '(consult-outline :which-key "outline")
    "s s" 'consult-line
    "s p" '(consult-ripgrep :wk "ripgrep project")
    "b b" 'consult-buffer
    ;; TODO consult mark
    "f r" 'consult-recent-file
    "s !" '(consult-flymake :wk "flymake"))
  ;; (with-eval-after-load 'projectile
  ;;   (lc/leader-keys
  ;;     "s p" '((lambda () (interactive) (consult-ripgrep (projectile-project-root))) :wk "ripgrep")))
  (autoload 'projectile-project-root "projectile")
  (setq consult-project-root-function #'projectile-project-root)
  ;; :init
  ;; (setq consult-preview-key "C-l")
  ;; (setq consult-narrow-key ">")

(use-package consult-selectrum
  :after selectrum

9 Core packages

9.1 project

9.1.1 projectile

projectile struggles with monorepos where .git folder is at the root but each subproject has e.g a pyproject.toml. In those cases, we need to create a .projectile file in the root of the subprojects.

(use-package projectile
    :states 'normal
    "p" '(:keymap projectile-command-map :which-key "project")
    "p <escape>" 'keyboard-escape-quit
    "p a" '(projectile-add-known-project :wk "add known")
    "p t" '(projectile-run-vterm :wk "term"))
  (when (file-directory-p "~/git")
    (setq projectile-project-search-path '("~/git")))
  (setq projectile-completion-system 'default)
  (setq projectile-project-root-files '(".envrc" ".projectile" "project.clj" "deps.edn"))
  (setq projectile-switch-project-action 'projectile-commander)
  ;; Do not include straight repos (emacs packages) to project list
  (setq projectile-ignored-project-function
   (lambda (project-root)
     (string-prefix-p (expand-file-name "straight/" user-emacs-directory) project-root)))
  (defadvice projectile-project-root (around ignore-remote first activate)
    (unless (file-remote-p default-directory) ad-do-it))
  ;; projectile commander methods
  (setq projectile-commander-methods nil)
  (def-projectile-commander-method ?? "Commander help buffer."
    (ignore-errors (kill-buffer projectile-commander-help-buffer))
    (with-current-buffer (get-buffer-create projectile-commander-help-buffer)
      (insert "Projectile Commander Methods:\n\n")
      (dolist (met projectile-commander-methods)
        (insert (format "%c:\t%s\n" (car met) (cadr met))))
      (goto-char (point-min))
      (display-buffer (current-buffer) t))
  (def-projectile-commander-method ?t
    "Open a *shell* buffer for the project."
  (def-projectile-commander-method ?\C-? ;; backspace
    "Go back to project selection."
  (def-projectile-commander-method ?d
    "Open project root in dired."
  (def-projectile-commander-method ?f
    "Find file in project."
  (def-projectile-commander-method ?s
    "Ripgrep in project."
  (def-projectile-commander-method ?g
    "Git status in project."

9.1.2 perspective


  • SPC <tab> o opens the "org" persp
  • same for main (maybe get ride of new tab)
(use-package perspective
  :commands (persp-new persp-switch persp-state-save)
    "<tab>" '(:ignore true :wk "tab")
    "<tab> <tab>" 'persp-switch
    "<tab> `" 'persp-switch-last
    "<tab> d" 'persp-kill
    "<tab> x" '((lambda () (interactive) (persp-kill (persp-current-name))) :wk "kill current")
    "<tab> X" '((lambda () (interactive) (persp-kill (persp-names))) :wk "kill all")
    "<tab> m" '(lc/main-tab :wk "main"))
  (setq persp-state-default-file (expand-file-name ".persp" user-emacs-directory))
  (defun lc/main-tab ()
    "Jump to the dashboard buffer, if doesn't exists create one."
    (persp-switch "main")
    (switch-to-buffer dashboard-buffer-name)
  (add-hook 'kill-emacs-hook #'persp-state-save))

9.1.3 persp-projectile

(use-package persp-projectile
  :after projectile
    "p p" 'projectile-persp-switch-project
    ;; "<tab> o"  '((lambda () (interactive)
    ;;               (let ((projectile-switch-project-action #'projectile-find-file))
    ;;                 (projectile-persp-switch-project "org")))
    ;;             :wk "org")

9.2 git

9.2.1 magit

50/72 rule for commit messages: https://www.midori-global.com/blog/2018/04/02/git-50-72-rule

(use-package magit
    "g b" 'magit-blame
    "g g" 'magit-status
    "g G" 'magit-status-here
    "g l" 'magit-log)
    :keymaps '(magit-status-mode-map
    "<tab>" #'magit-section-toggle
    "<escape>" #'transient-quit-one)
  (setq magit-display-buffer-function #'magit-display-buffer-same-window-except-diff-v1)
  (setq magit-log-arguments '("--graph" "--decorate" "--color"))
  (setq git-commit-fill-column 72)
  (evil-define-key* '(normal visual) magit-mode-map
    "zz" #'evil-scroll-line-to-center)

9.2.2 TODO forge

;; NOTE: Make sure to configure a GitHub token before using this package!
;; - https://magit.vc/manual/forge/Token-Creation.html#Token-Creation
;; - https://magit.vc/manual/ghub/Getting-Started.html#Getting-Started
(use-package forge :after magit)

9.2.3 git-timemachine

  (use-package git-timemachine
    :hook (git-time-machine-mode . evil-normalize-keymaps)
    :init (setq git-timemachine-show-minibuffer-details t)
    (general-nmap "SPC g t" 'git-timemachine-toggle)
     "C-k" 'git-timemachine-show-previous-revision
     "C-j" 'git-timemachine-show-next-revision
     "q" 'git-timemachine-quit))

9.2.4 diff-hl

When an heading includes a change, the~org-ellipsis~ not shown correctly. This is caused by an empty line with diff-hl fringe that gets appended to the heading. To work around this and show the ellipsis, you have to add a whitespace in that empty line.

(use-package diff-hl
    "g n" '(diff-hl-next-hunk :wk "next hunk")
    "g p" '(diff-hl-previous-hunk :wk "prev hunk"))
  ((magit-pre-refresh . diff-hl-magit-pre-refresh)
   (magit-post-refresh . diff-hl-magit-post-refresh))
  (setq diff-hl-draw-borders nil)
  ;; (setq diff-hl-global-modes '(not org-mode))
  ;; (setq diff-hl-fringe-bmp-function 'diff-hl-fringe-bmp-from-type)
  ;; (setq diff-hl-global-modes (not '(image-mode org-mode)))

9.2.5 hydra-smerge

(use-package smerge-mode
  :straight (:type built-in)
  :after hydra
  (lc/leader-keys "g m" 'smerge-hydra/body)
  (magit-diff-visit-file . (lambda ()
                             (when smerge-mode
  (defhydra smerge-hydra (:hint nil
                                :pre (smerge-mode 1)
                                ;; Disable `smerge-mode' when quitting hydra if
                                ;; no merge conflicts remain.
                                :post (smerge-auto-leave))
  Movement   Keep           Diff              Other │ smerge │
     ^_g_^       [_b_] base       [_<_] upper/base    [_C_] Combine
     ^_C-k_^     [_u_] upper      [_=_] upper/lower   [_r_] resolve
     ^_k_ ↑^     [_l_] lower      [_>_] base/lower    [_R_] remove
     ^_j_ ↓^     [_a_] all        [_H_] hightlight
     ^_C-j_^     [_RET_] current  [_E_] ediff             ╭──────────
     ^_G_^                                            │ [_q_] quit"
    ("g" (progn (goto-char (point-min)) (smerge-next)))
    ("G" (progn (goto-char (point-max)) (smerge-prev)))
    ("C-j" smerge-next)
    ("C-k" smerge-prev)
    ("j" next-line)
    ("k" previous-line)
    ("b" smerge-keep-base)
    ("u" smerge-keep-upper)
    ("l" smerge-keep-lower)
    ("a" smerge-keep-all)
    ("RET" smerge-keep-current)
    ("\C-m" smerge-keep-current)
    ("<" smerge-diff-base-upper)
    ("=" smerge-diff-upper-lower)
    (">" smerge-diff-base-lower)
    ("H" smerge-refine)
    ("E" smerge-ediff)
    ("C" smerge-combine-with-next)
    ("r" smerge-resolve)
    ("R" smerge-kill-current)
    ("q" nil :color blue)))

9.2.6 emacs git

This is a dependency for Hack to get org-version to work

(use-package git)

9.3 hydra

(use-package hydra

9.4 rainbow parenthesis

  (use-package rainbow-delimiters
    :hook ((emacs-lisp-mode . rainbow-delimiters-mode)
           (clojure-mode . rainbow-delimiters-mode))

9.5 syntax highlighting

  (use-package tree-sitter
    :hook (python-mode . (lambda ()
                           (require 'tree-sitter)
                           (require 'tree-sitter-langs)
                           (require 'tree-sitter-hl)

  (use-package tree-sitter-langs
    :after tree-sitter)

9.6 company-mode

company-tng-mode (tab-n-go):

  • Select candidates with C-j / C-k or <tab> / S-<tab>
  • don't press RET to confirm
(use-package company
  ;; :hook
  ;; (python-mode . (lambda ()
  ;;                (setq-local company-backends '((company-capf :with company-files)))))
  (setq company-minimum-prefix-length 1)
  (setq company-idle-delay 0.0)
  (setq company-tooltip-align-annotations t)
  (setq company-tooltip-maximum-width 50
        company-tooltip-minimum-width 50)
  ;; don't autocomplete when single candidate
  (setq company-auto-complete nil)
  (setq company-auto-complete-chars nil)
  (setq company-dabbrev-code-other-buffers nil)
  ;; manually configure tng
  ;; (setq company-tng-auto-configure nil)
  ;; (setq company-frontends '(company-tng-frontend
  ;;                           company-pseudo-tooltip-frontend
  ;;                           company-echo-metadata-frontend))
  ;; (setq company-selection-default nil)
  (setq company-backends '((company-capf company-keywords company-files :with company-yasnippet)))
   ((t (:family "Fira Code"))))
  (with-eval-after-load 'evil
    (add-hook 'company-mode-hook #'evil-normalize-keymaps))
  ;; needed in case we only have one candidate
  (define-key company-active-map (kbd "C-j") 'company-select-next)

9.7 envrc

Running direnv is expensive so I only do it when it is necessary. I need it in two situations:

  • python-mode
  • ob-jupyter

Instead of simply enabling envrc-mode in every org buffer, I check with the buffer includes a jupyter-python block. In the ob-jupyter section I then load ob-jupyter only when envrc-mode is loaded and jupyter is found on the PATH

(use-package inheritenv
  :straight (inheritenv :type git :host github :repo "purcell/inheritenv"))
(use-package envrc
  :straight (envrc :type git :host github :repo "purcell/envrc")
  :commands (envrc-mode)
  :hook ((python-mode . envrc-mode)
         (org-jupyter-mode . envrc-mode))

9.8 yasnippet

We use C-<tab> to expand snippets instead of <tab> .

You can have #condition: 'auto for the snippet to auto-expand.

See here to share snippets across modes

(use-package yasnippet
   :states 'insert
   "<tab>" 'nil
   "C-<tab>" 'yas-expand)
  ((prog-mode org-mode dap-ui-repl-mode vterm-mode) . yas-minor-mode)
  ;; (setq yas-prompt-functions '(yas-ido-prompt))
  (defun lc/yas-try-expanding-auto-snippets ()
    (when (and (boundp 'yas-minor-mode) yas-minor-mode)
      (let ((yas-buffer-local-condition ''(require-snippet-condition . auto)))
  (add-hook 'post-command-hook #'lc/yas-try-expanding-auto-snippets)

9.9 undo fu

(use-package undo-fu
  (:states 'normal
           "u" 'undo-fu-only-undo
           "\C-r" 'undo-fu-only-redo))

(when (eq system-type 'darwin)
  (use-package undo-fu
    (:states '(normal insert)
             "s-z" 'undo-fu-only-undo)))

9.10 vterm

(use-package vterm
  (setq vterm-shell (executable-find "fish")
        vterm-max-scrollback 10000))

9.11 vterm toggle

(use-package vterm-toggle
    "'" 'vterm-toggle))

9.12 dired

  • Jump to current file with SPC f j
  • With a dired buffer open, use dired-other-window to open another folder where you want to move/copy files from/to
  • Hide details with ( )
  • Show/hide dotfiles with H
  • Mark with m, unmark with u
  • Invert selection with t
  • * has some helpers for marking
  • First mark some files and then K to "hide" them
  • Open directory in right window with S-RET
    • When copying from left window, target will be right window
    • Copy with C
  • Open subdir in buffer below with I
  • Open files with macos with O
  • View files with go and exit with q
(use-package dired
  :straight (:type built-in)
  (dired-mode . dired-hide-details-mode)
    "f d" 'dired
    "f j" 'dired-jump)
    :keymaps 'dired-mode-map
    :states 'normal
    "F" '((lambda () (interactive)
            (let ((fn (dired-get-file-for-visit)))
              (start-process "open-directory" nil "open" "-R" fn)))
          :wk "open finder")
    "X" '((lambda () (interactive)
            (let ((fn (dired-get-file-for-visit)))
              (start-process "open-external" nil "open" fn)))
          :wk "open external"))
  (setq dired-omit-files "^\\.[^.]\\|$Rhistory\\|$RData\\|__pycache__")
  (setq dired-listing-switches "-lah")
  (setq dired-dwim-target t))

(use-package dired-single
  :after dired
   :states 'normal
   "h" 'dired-single-up-directory
   "l" 'dired-single-buffer
   "q" 'kill-current-buffer))

(use-package all-the-icons-dired
  :hook (dired-mode . (lambda () (interactive)
                        (unless (file-remote-p default-directory)

(use-package dired-hide-dotfiles
  :hook (dired-mode . dired-hide-dotfiles-mode)
  (evil-collection-define-key 'normal 'dired-mode-map
    "H" 'dired-hide-dotfiles-mode))

(use-package dired-subtree
   :states 'normal
   "i" 'dired-subtree-toggle)
  (advice-add 'dired-subtree-toggle
              :after (lambda () (interactive)
                       (when all-the-icons-dired-mode

9.13 restart-emacs

  (use-package restart-emacs
      "R" '(restart-emacs :wk "restart"))

9.14 toml mode

(use-package toml-mode
  :mode "\\.toml\\'")

9.15 tramp

Call e.g. dired and input /ssh:user@hostname:/path/to/file

(use-package tramp
  :straight (:type built-in)
  ;; Disable version control on tramp buffers to avoid freezes.
  (setq vc-ignore-dir-regexp
        (format "\\(%s\\)\\|\\(%s\\)"
  (setq tramp-default-method "ssh")
  (setq tramp-auto-save-directory
        (expand-file-name "tramp-auto-save" user-emacs-directory))
  (setq tramp-persistency-file-name
        (expand-file-name "tramp-connection-history" user-emacs-directory))
  (setq password-cache-expiry nil)
  (setq tramp-use-ssh-controlmaster-options nil)
  (customize-set-variable 'tramp-ssh-controlmaster-options
                           "-o ControlPath=/tmp/ssh-tramp-%%r@%%h:%%p "
                           "-o ControlMaster=auto -o ControlPersist=yes")))

(use-package docker-tramp)

9.16 yaml mode

(use-package yaml-mode
  :mode ((rx ".yml" eos) . yaml-mode))

9.17 kubernetes

(use-package kubernetes
  (kubernetes-overview-mode . lc/load-kubernetes-evil)
    "k k" 'kubernetes-overview)
  ;; refresh manually with gr
  (setq kubernetes-poll-frequency 3600)
  (setq kubernetes-redraw-frequency 3600)
  (defun lc/load-kubernetes-evil ()
    "Copied from kubernetes-evil.el"
    (evil-set-initial-state 'kubernetes-mode 'motion)
    (evil-set-initial-state 'kubernetes-display-thing-mode 'motion)
    (evil-set-initial-state 'kubernetes-log-line-mode 'motion)
    (evil-set-initial-state 'kubernetes-logs-mode 'motion)
    (evil-set-initial-state 'kubernetes-overview-mode 'motion)

    (evil-define-key 'motion kubernetes-mode-map
      (kbd "p")   #'magit-section-backward
      (kbd "n")   #'magit-section-forward
      (kbd "M-p") #'magit-section-backward-sibling
      (kbd "M-n") #'magit-section-forward-sibling
      (kbd "C-i") #'magit-section-toggle
      (kbd "^")   #'magit-section-up
      [tab]       #'magit-section-toggle
      [C-tab]     #'magit-section-cycle
      [M-tab]     #'magit-section-cycle-diffs
      [S-tab]     #'magit-section-cycle-global

      [remap evil-next-line] #'next-line
      [remap evil-previous-line] #'previous-line
      [remap evil-next-visual-line] #'next-line
      [remap evil-previous-visual-line] #'previous-line

      (kbd "q") #'quit-window
      (kbd "RET") #'kubernetes-navigate
      (kbd "M-w") #'kubernetes-copy-thing-at-point

      (kbd "?") #'kubernetes-overview-popup
      (kbd "c") #'kubernetes-config-popup
      (kbd "g r") #'kubernetes-refresh
      (kbd "h") #'describe-mode
      (kbd "d") #'kubernetes-describe-popup
      (kbd "D") #'kubernetes-mark-for-delete
      (kbd "e") #'kubernetes-exec-popup
      (kbd "u") #'kubernetes-unmark
      (kbd "U") #'kubernetes-unmark-all
      (kbd "x") #'kubernetes-execute-marks
      (kbd "l") #'kubernetes-logs-popup
      (kbd "L") #'kubernetes-labels-popup)

    (evil-define-key 'motion kubernetes-overview-mode-map
      (kbd "v") #'kubernetes-overview-set-sections)

    (evil-define-key 'motion kubernetes-logs-mode-map
      (kbd "n") #'kubernetes-logs-forward-line
      (kbd "p") #'kubernetes-logs-previous-line
      (kbd "RET") #'kubernetes-logs-inspect-line)

    (evil-define-key 'motion kubernetes-log-line-mode-map
      (kbd "n") #'kubernetes-logs-forward-line
      (kbd "p") #'kubernetes-logs-previous-line))

10 Programming languages

10.1 lsp mode

(use-package lsp-mode
  :commands (lsp lsp-deferred)
  ((lsp-mode . (lambda () (setq-local evil-lookup-func #'lsp-describe-thing-at-point)))
   (lsp-mode . lsp-enable-which-key-integration))
    :states 'normal
    :keymaps 'lsp-mode-map
    "i" '(:ignore t :which-key "import")
    "i o" '(lsp-organize-imports :wk "optimize")
    "l" '(:keymap lsp-command-map :wk "lsp")
    "r" '(lsp-rename :wk "rename"))
   :states 'normal "gD" 'lsp-find-references)
  (setq lsp-restart 'ignore)
  (setq lsp-eldoc-enable-hover nil)
  (setq lsp-enable-file-watchers nil)
  (setq lsp-signature-auto-activate nil)
  (setq lsp-modeline-diagnostics-enable nil)
  (setq lsp-keep-workspace-alive nil)
  (setq lsp-auto-execute-action nil)
  (setq lsp-before-save-edits nil)
  (setq lsp-diagnostics-provider :flymake)
  ;; :config
  ;; (lsp-register-client
  ;;   (make-lsp-client :new-connection (lsp-tramp-connection "<binary name (e. g. pyls, rls)>")
  ;;                    :major-modes '(python-mode)
  ;;                    :remote? t
  ;;                    :server-id 'pyls-remote))

10.2 lsp-ui

(use-package lsp-ui
  ((lsp-mode . lsp-ui-mode)
   (lsp-mode . (lambda () (setq-local evil-goto-definition-functions
                                      '(lambda (&rest args) (lsp-ui-peek-find-definitions)))))
  (:map lsp-ui-mode-map
        ([remap lsp-find-references] . lsp-ui-peek-find-references))
    "h" 'lsp-ui-doc-show
    "H" 'lsp-ui-doc-hide)
   :states 'normal
   "C-j" 'lsp-ui-peek--select-next
   "C-k" 'lsp-ui-peek--select-prev)
   :states 'normal
   "C-j" 'nil
   "C-k" 'nil)
  (setq lsp-ui-doc-show-with-cursor nil)
  (setq lsp-ui-doc-show-with-mouse nil)
  (setq lsp-ui-peek-always-show t)
  (setq lsp-ui-peek-fontify 'always)

10.3 dap-mode

(use-package dap-mode
  ((dap-terminated . lc/hide-debug-windows)
   (dap-ui-repl-mode . (lambda () (setq-local truncate-lines t))))
    :keymaps 'python-mode-map
    "d d" '(dap-debug :wk "debug")
    "d b" '(dap-breakpoint-toggle :wk "breakpoint")
    "d c" '(dap-continue :wk "continue")
    "d n" '(dap-next :wk "next")
    "d e" '(dap-eval-thing-at-point :wk "eval")
    "d i" '(dap-step-in :wk "step in")
    "d q" '(dap-disconnect :wk "quit")
    "d r" '(dap-ui-repl :wk "repl")
    "d h" '(dap-hydra :wk "hydra"))
  ;; (setq dap-auto-configure-features '(locals repl))
  (setq dap-auto-configure-features '(repl))
  (setq dap-python-debugger 'debugpy)
  ;; show stdout
  (setq dap-auto-show-output t)
  (setq dap-output-window-max-height 20)
  (setq dap-output-window-min-height 10)
  (setq dap-overlays-use-overlays nil)
  ;; hide stdout window  when done
  (defun lc/hide-debug-windows (session)
    "Hide debug windows when all debug sessions are dead."
    (unless (-filter 'dap--session-running (dap--get-sessions))
      (kill-buffer (dap--debug-session-output-buffer (dap--cur-session-or-die)))))
  (defun lc/dap-python--executable-find (orig-fun &rest args)
    (executable-find "python"))
  ;; configure windows
  (require 'dap-ui)
  (setq dap-ui-buffer-configurations
        `(;; (,dap-ui--locals-buffer . ((side . right) (slot . 1) (window-width . 0.50)))
          ;; (,dap-ui--breakpoints-buffer . ((side . left) (slot . 1) (window-width . ,treemacs-width)))
          ;; (,dap-ui--sessions-buffer . ((side . left) (slot . 2) (window-width . ,treemacs-width)))
          (,dap-ui--repl-buffer . ((side . right) (slot . 2) (window-width . 0.50)))))
  (dap-ui-mode 1)
  ;; python virtualenv
  (require 'dap-python)
  (advice-add 'dap-python--pyenv-executable-find :around #'lc/dap-python--executable-find)
  ;; debug templates
  (defvar dap-script-args (list :type "python"
                                :args []
                                :cwd "${workspaceFolder}"
                                :justMyCode :json-false
                                :request "launch"
                                :debugger 'debugpy
                                :name "dap-debug-script"))
  (defvar dap-test-args (list :type "python-test-at-point"
                              :args ""
                              :justMyCode :json-false
                              ;; :cwd "${workspaceFolder}"
                              :request "launch"
                              :module "pytest"
                              :debugger 'debugpy
                              :name "dap-debug-test-at-point"))
  (defvar empties-forecast (list
                            :name "empties forecast"
                            :type "python"
                            :request "launch"
                            :program "./src/empties/forecasting/predict.py"
                            :env '(("NO_JSON_LOG" . "true"))
                            :args ["--source01" "./data/empties-history-sample.parquet"
                                   "--source02" "./data/model_selection.files"
                                   "--source03" "./data/booking-feature-sample.parquet"
                                   "--source04" "./data/holiday-2019-05-24-1558683595"
                                   "--output-data" "./data/predictions.parquet"
                                   "--output-metrics" "./data/metrics.json"]
  (dap-register-debug-template "dap-debug-script" dap-script-args)
  (dap-register-debug-template "dap-debug-test-at-point" dap-test-args)
  ;; bind the templates
    :keymaps 'python-mode-map
    "d t" '((lambda () (interactive) (dap-debug dap-test-args)) :wk "test")
    "d s" '((lambda () (interactive) (dap-debug dap-script-args)) :wk "script")

10.4 Python

10.4.1 python mode

(use-package python-mode
  :hook ((envrc-mode . (lambda ()
                         (when (executable-find "ipython")
                           (setq python-shell-interpreter (executable-find "ipython"))))))
   :states 'normal
   "gz" nil
   "C-j" nil)
  (setq python-indent-offset 0)
  (setq python-shell-interpreter (executable-find "ipython")     ;; FIXME
        python-shell-interpreter-args "-i --simple-prompt --no-color-info"
        python-shell-prompt-regexp "In \\[[0-9]+\\]: "
        python-shell-prompt-block-regexp "\\.\\.\\.\\.: "
        python-shell-prompt-output-regexp "Out\\[[0-9]+\\]: "
        "from IPython.core.completerlib import module_completion"

10.4.2 lsp-pyright

Here the configuration options: https://github.com/emacs-lsp/lsp-pyright#configuration

(use-package lsp-pyright
  (setq lsp-pyright-typechecking-mode "basic") ;; too much noise in "real" projects
  :hook (python-mode . (lambda ()
                         (require 'lsp-pyright)

10.4.3 pytest

(use-package python-pytest
    :keymaps 'python-mode-map
    "t" '(:ignore t :wk "test")
    "t d" '(python-pytest-dispatch :wk "dispatch")
    "t f" '(python-pytest-file-dwim :wk "file")
    "t t" '(python-pytest-function :wk "function"))
  (setq python-pytest-arguments '("--color" "--failed-first"))
  (defun lc/pytest-use-venv (orig-fun &rest args)
    (if-let ((python-pytest-executable (executable-find "pytest")))
        (apply orig-fun args)
      (apply orig-fun args)))
  (advice-add 'python-pytest--run :around #'lc/pytest-use-venv)

10.4.4 flymake

(use-package flymake
  :straight (:type built-in)
  :hook (emacs-lisp-mode . flymake-mode)
  (setq python-flymake-command (executable-find "flake8"))
  (setq flymake-fringe-indicator-position 'right-fringe)
  (general-nmap "] !" 'flymake-goto-next-error)
  (general-nmap "[ !" 'flymake-goto-prev-error)

10.4.5 jupyter

zmq installation:

  • Need to have automake, autoconf
  • In straight/build/zmq/src run autoreconf -i
  • In straight/build/zmq run make

emacs-zmq installation:

  • In straight/build/emacs-zmq run wget https://github.com/nnicandro/emacs-zmq/releases/download/v0.10.10/emacs-zmq-x86_64-apple-darwin17.4.0.tar.gz
  • Then tar -xzf emacs-zmq-x86_64-apple-darwin17.4.0.tar.gz
  • Finally cp emacs-zmq-x86_64-apple-darwin17.4.0/emacs-zmq.so emacs-zmq.dylib
  • In the REPL you can use M-p / M-n to navigate previous prompts
(use-package jupyter
  :straight (:no-native-compile t :no-byte-compile t) ;; otherwise we get jupyter-channel void
    :keymaps 'python-mode-map
    "e" '(:ignore true :wk "eval")
    "e e" '(jupyter-eval-line-or-region :wk "line")
    "e d" '(jupyter-eval-defun :wk "defun")
    "e b" '((lambda () (interactive) (lc/jupyter-eval-buffer)) :wk "buffer")
    "J" '(lc/jupyter-repl :wk "jupyter REPL")
    "k" '(:ignore true :wk "kernel")
    "k i" '(jupyter-org-interrupt-kernel :wk "restart kernel")
    "k r" '(jupyter-repl-restart-kernel :wk "restart kernel"))
    :keymaps 'python-mode-map
    :states 'visual
    "e" '(jupyter-eval-region :wk "eval"))
  (setq jupyter-repl-prompt-margin-width 4)
  (defun jupyter-command-venv (&rest args)
    "This overrides jupyter-command to use the virtualenv's jupyter"
    (let ((jupyter-executable (executable-find "jupyter")))
        (when (zerop (apply #'process-file jupyter-executable nil t nil args))
          (string-trim-right (buffer-string))))))
  (defun lc/jupyter-eval-buffer ()
    "Send the contents of BUFFER using `jupyter-current-client'."
    (jupyter-eval-string (jupyter-load-file-code (buffer-file-name))))
  (defun lc/jupyter-repl ()
    "If a buffer is already associated with a jupyter buffer, then pop to it. Otherwise start a jupyter kernel."
    (if (bound-and-true-p jupyter-current-client)
      (call-interactively 'jupyter-repl-associate-buffer)))
  (advice-add 'jupyter-command :override #'jupyter-command-venv))

10.4.6 auto-import

(use-package pyimport
    :keymaps 'python-mode-map
    "i i" '(pyimport-insert-missing :wk "autoimport")))

10.4.7 blacken

(use-package blacken
      :keymaps 'python-mode-map
      "=" '(blacken-buffer :wk "format"))

10.5 R

10.5.1 ess

(use-package ess
    :keymaps 'ess-r-mode-map
    :states 'normal
    "R" '(R :wk "R")
    "q" '(ess-quit :wk "quit")
    "RET" '(ess-eval-line-visibly-and-step :wk "line and step")
    ;; debug
    "d b" '(ess-bp-set :wk "breakpoint")
    "d n" '(ess-debug-command-next :wk "next")
    "d q" '(ess-debug-command-quit :wk "quit")
    "d c" '(ess-bp-next :wk "continue")
    "d f" '(ess-debug-flag-for-debugging :wk "flag function")
    "d F" '(ess-debug-unflag-for-debugging :wk "unflag function")
    "d p" '(ess-debug-goto-debug-point :wk "go to point")
    ;; "e l" '(ess-eval-line :wk "eval line")
    "e p" '(ess-eval-paragraph :wk "paragraph")
    "e f" '(ess-eval-function :wk "function")
    "h" '(:keymap ess-doc-map :which-key "help")
    ;; "h" '(ess-display-help-on-object :wk "help")
    :keymaps 'ess-r-mode-map
    :states 'visual
    "RET" '(ess-eval-region-or-line-visibly-and-step :wk "line and step"))
  (setq ess-eval-visibly 'nowait)
  (setq ess-R-font-lock-keywords '((ess-R-fl-keyword:keywords . t)
                                   (ess-R-fl-keyword:constants . t)
                                   (ess-R-fl-keyword:modifiers . t)
                                   (ess-R-fl-keyword:fun-defs . t)
                                   (ess-R-fl-keyword:assign-ops . t)
                                   (ess-R-fl-keyword:%op% . t)
                                   (ess-fl-keyword:fun-calls . t)
                                   (ess-fl-keyword:numbers . t)
                                   (ess-fl-keyword:operators . t)
                                   (ess-fl-keyword:delimiters . t)
                                   (ess-fl-keyword:= . t)
                                   (ess-R-fl-keyword:F&T . t)))
  ;; (setq ess-first-continued-statement-offset 2
  ;;         ess-continued-statement-offset 0
  ;;         ess-expression-offset 2
  ;;         ess-nuke-trailing-whitespace-p t
  ;;         ess-default-style 'DEFAULT)
  ;; (setq ess-r-flymake-linters "line_length_linter = 120")

10.5.2 ESS data viewer

(use-package ess-view-data
    :keymaps 'ess-r-mode-map
    :states 'normal
    "hd" 'ess-R-dv-pprint
    "ht" 'ess-R-dv-ctable

10.5.3 enable lsp-mode

(use-package lsp-mode
  (ess-r-mode . lsp-deferred)

10.6 emacs-lisp

10.6.1 evil-lisp state

  • Enter lisp-state with SPC l .
  • Navigate symbols with j and k
  • Navigate forms with h and l
  • Go to parent sexp with U
(use-package evil-lisp-state
  :after evil
  (setq evil-lisp-state-enter-lisp-state-on-command nil)
  (setq evil-lisp-state-global t)
  ;; (setq evil-lisp-state-major-modes '(org-mode emacs-lisp-mode clojure-mode clojurescript-mode lisp-interaction-mode))
  (evil-lisp-state-leader "SPC l")

10.6.2 eros: results in overlays

(use-package eros
  :hook ((emacs-lisp-mode org-mode lisp-interaction-mode) . eros-mode)
    :keymaps '(org-mode-map emacs-lisp-mode-map lisp-interaction-mode-map)
    :states 'normal
    "e l" '(eros-eval-last-sexp :wk "last sexp")
    ;; "e d" '((lambda () (interactive) (eros-eval-defun t)) :wk "defun")
    "e b" '(eval-buffer :wk "buffer"))
    :keymaps '(org-mode-map emacs-lisp-mode-map lisp-interaction-mode-map)
    :states 'visual
    "e" '((lambda (start end)
            (interactive (list (region-beginning) (region-end)))
            (eval-region start end t))
          :wk "region")
    ;; "e" '((lambda (start end)
    ;;         (interactive (list (region-beginning) (region-end)))
    ;;         (eros--eval-overlay
    ;;          (eval-region start end t)
    ;;          end))
    ;;       :wk "region")

10.7 Nix

10.7.1 nix mode

(use-package nix-mode
:mode "\\.nix\\'")

10.8 Clojure

10.8.1 Clojure mode

(use-package clojure-mode
  :mode "\\.clj$"
  (setq clojure-align-forms-automatically t))

10.8.2 clojure-lsp

(use-package clojure-mode
  ((clojure-mode clojurescript-mode)
   . (lambda ()
       (setq-local lsp-enable-indentation nil ; cider indentation
                   lsp-enable-completion-at-point nil ; cider completion

10.8.3 Cider

(use-package cider
  :hook ((cider-repl-mode . evil-normalize-keymaps)
         (cider-mode . (lambda ()
                           (setq-local evil-lookup-func #'cider-doc)))
         (cider-mode . eldoc-mode))
    :keymaps 'clojure-mode-map
    "c" '(cider-connect-clj :wk "connect")
    "C" '(cider-connect-cljs :wk "connect (cljs)")
    "j" '(cider-jack-in :wk "jack in")
    "J" '(cider-jack-in-cljs :wk "jack in (cljs)")
    "d d" 'cider-debug-defun-at-point 
    "e b" 'cider-eval-buffer
    "e l" 'cider-eval-last-sexp
    "e L" 'cider-pprint-eval-last-sexp-to-comment
    "e d" '(cider-eval-defun-at-point :wk "defun")
    "e D" 'cider-pprint-eval-defun-to-comment
    "h" 'cider-clojuredocs-web 
    "K" 'cider-doc
    "q" '(cider-quit :qk "quit")
    :keymaps 'clojure-mode-map
    :states 'visual
    "e" 'cider-eval-region)
  (setq nrepl-hide-special-buffers t)
  (setq nrepl-sync-request-timeout nil)
  (setq cider-repl-display-help-banner nil)

10.8.4 TODO Eval sexp at point

(use-package cider
  (defun mpereira/cider-eval-sexp-at-point (&optional output-to-current-buffer)
    "Evaluate the expression around point.
If invoked with OUTPUT-TO-CURRENT-BUFFER, output the result to current buffer."
    (interactive "P")
      (goto-char (- (cadr (cider-sexp-at-point 'bounds))
      (cider-eval-last-sexp output-to-current-buffer)))

10.8.5 ob-clojure

(use-package org
(require 'ob-clojure)
(setq org-babel-clojure-backend 'cider))

10.8.6 aggressive-indent

  ;; keep the file indented
  (use-package aggressive-indent
    :hook ((clojure-mode . aggressive-indent-mode)
           (emacs-lisp-mode . aggressive-indent-mode)))

10.9 markdown

10.9.1 markdown-mode

(use-package markdown-mode
  :commands (markdown-mode gfm-mode)
  :mode (("README\\.md\\'" . gfm-mode)
         ("\\.md\\'" . markdown-mode)
         ("\\.markdown\\'" . markdown-mode))
  :init (setq markdown-command "multimarkdown"))

11 Extras

11.1 web browser

Use SPC s w to search the web! You can also visit a URL in the buffer. With the web browser open, scroll with j / k . To visit a link, SPC s l

(use-package xwwp
  :straight (xwwp :type git :host github :repo "canatella/xwwp")
  :commands (xwwp)
    "x x" '((lambda () (interactive)
              (let ((current-prefix-arg 4)) ;; emulate C-u universal arg
                (call-interactively 'xwwp)))
            :wk "search or visit")
    "x l" '(xwwp-follow-link :wk "link")
    "x b" '(xwidget-webkit-back :wk "back"))
  ;; :custom
  ;; (setq xwwp-follow-link-completion-system 'ivy)
  ;; :bind (:map xwidget-webkit-mode-map
  ;;             ("v" . xwwp-follow-link))

11.2 elfeed

  • Search with s
  • Open in browser with S-RET
  • Open in xwwp with x
(use-package elfeed
  :straight (elfeed :type git :host github :repo "skeeto/elfeed")
  :hook (elfeed-search-mode . elfeed-update)
    "s r" '(elfeed :wk "elfeed"))
    :keymaps 'elfeed-search-mode-map
   "x" 'lc/elfeed-xwwp-open)
  (defun lc/elfeed-xwwp-open (&optional use-generic-p)
    "open with eww"
    (interactive "P")
    (let ((entries (elfeed-search-selected)))
      (cl-loop for entry in entries
               do (elfeed-untag entry 'unread)
               when (elfeed-entry-link entry)
               do (xwwp it))
      (mapc #'elfeed-search-update-entry entries)
      (unless (use-region-p) (forward-line))))
  (setq elfeed-feeds'(("https://www.reddit.com/r/emacs.rss?sort=new" reddit emacs)
                      ("http://emacsredux.com/atom.xml" emacs)
                      ("http://irreal.org/blog/?tag=emacs&amp;feed=rss2" emacs)
                      reddit youtube popular))))

11.3 search google

(use-package emacs
    "s g" '(google-search :wk "google"))
  (defun google-search-str (str)
     (concat "https://www.google.com/search?q=" str)))
  (defun google-search ()
    "Google search region, if active, or ask for search string."
    (if (region-active-p)
         (buffer-substring-no-properties (region-beginning)
      (google-search-str (read-from-minibuffer "Search: "))))

11.4 search github

(use-package emacs
    "s c" '(github-code-search :wk "code (github)"))
  (defun github-code-search ()
    "Search code on github for a given language."
    (let ((language (completing-read
                     "Language: "
                     '("Emacs Lisp" "Python"  "Clojure" "R")))
          (code (read-string "Code: ")))
       (concat "https://github.com/search?l=" language
               "&type=code&q=" code))))

11.5 transient help commands

(use-package emacs
    "h" 'lc/help-transient)
  (require 'transient)
  (transient-define-prefix lc/help-transient ()
    ["Help Commands"
     ["Mode & Bindings"
      ("m" "Mode" describe-mode)
      ("b" "Major Bindings" which-key-show-full-major-mode)
      ("B" "Minor Bindings" which-key-show-full-minor-mode-keymap)
      ("d" "Descbinds" describe-bindings)
      ("c" "Command" helpful-command)
      ("f" "Function" helpful-callable)
      ("v" "Variable" helpful-variable)
      ("k" "Key" helpful-key)
     ["Info on"
      ("C-c" "Emacs Command" Info-goto-emacs-command-node)
      ("C-f" "Function" info-lookup-symbol) 
      ("C-v" "Variable" info-lookup-symbol)
      ("C-k" "Emacs Key" Info-goto-emacs-key-command-node)
     ["Goto Source"
      ("L" "Library" find-library)
      ("F" "Function" find-function)
      ("V" "Variable" find-variable)
      ("K" "Key" find-function-on-key)
      ("e" "Echo Messages" view-echo-area-messages)
      ("l" "Lossage" view-lossage)
      ("s" "Symbol" helpful-symbol)
      ("." "At Point   " helpful-at-point)
      ;; ("C-f" "Face" counsel-describe-face)
      ("w" "Where Is" where-is)
      ("=" "Position" what-cursor-position)
     ["Info Manuals"
      ("C-i" "Info" info)
      ("C-4" "Other Window " info-other-window)
      ("C-e" "Emacs" info-emacs-manual)
      ;; ("C-l" "Elisp" info-elisp-manual)
      ("q" "Quit" transient-quit-one)
      ("<escape>" "Quit" transient-quit-one)
     ;; ["External"
     ;;  ("W" "Dictionary" lookup-word-at-point)
     ;;  ("D" "Dash" dash-at-point)
     ;;  ]