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 early-init.el and init.el

2.1 early-init.el

Taken from DOOM's early init

;;; early-init.el --- Early Init File -*- lexical-binding: t; no-byte-compile: t -*-
;; 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)

;;; early-init.el ends here

2.2 init.el: startup optimization

Taken from DOOM's init

;;; init.el --- Personal configuration file -*- lexical-binding: t; no-byte-compile: t; -*-
;; 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)))

2.3 init.el: load modules

;; (add-to-list 'load-path "~/.emacs.d/lisp/")
(add-to-list 'load-path (expand-file-name "lisp" user-emacs-directory))

(let ((file-name-handler-alist nil)
      (gc-cons-threshold 100000000))
  (require 'init-core)
  (require 'init-extra)
  )

;;; init.el ends here

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 straight-check-for-modifications nil)
(setq use-package-always-defer t)
(defvar bootstrap-version)
(let* ((straight-repo-dir
        (expand-file-name "straight/repos" user-emacs-directory))
       (bootstrap-file
        (concat straight-repo-dir "/straight.el/bootstrap.el"))
       (bootstrap-version 5))
  (unless (file-exists-p bootstrap-file)
    (shell-command
     (concat
      "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")
 ("alert" . "7046393272686c7a1a9b3e7f7b1d825d2e5250a6")
 ("all-the-icons-dired" . "fc2dfa1e9eb8bf1c402a675e7089638d702a27a5")
 ("all-the-icons.el" . "6917b08f64dd8487e23769433d6cb9ba11f4152f")
 ("annalist.el" . "134fa3f0fb91a636a1c005c483516d4b64905a6d")
 ("avy" . "e92cb37457b43336b765630dbfbea8ba4be601fa")
 ("blacken" . "784da60033fe3743336d1da0f33239f1bf514266")
 ("bui.el" . "f3a137628e112a91910fd33c0cff0948fa58d470")
 ("centaur-tabs" . "9c7c936e4e1de6f4f4095d70e43c9ae738d05086")
 ("centered-cursor-mode.el" . "4093821cc9759ca5a3c6e527d4cc915fc3a5ad74")
 ("cfrs" . "7c42f2c82c7ae689f3ef291b066688c58ab96298")
 ("cider" . "9c137c52cf5b769fcc52b4e8108acda10638f766")
 ("clojure-mode" . "53ef8ac076ae7811627fbdd408e519ab7fca9a0b")
 ("consult" . "9dcecad2d448e2da0ae9fa17222358176ef58dd3")
 ("csv-mode" . "8da54e8b4ef9e5fe8a0afa147c625ced603dc0aa")
 ("dap-mode" . "88eda699e77e86410cdf3eb6afb790303dd27728")
 ("darkroom" . "27b928a6e91c3207742180f7e209bae754c9c1fe")
 ("dash.el" . "0e975782086020aa12863fdb658d6a3cc748a10c")
 ("diff-hl" . "89aeb2fc8b24b6c4de4394f85041c5dd5fa60dad")
 ("dired-hacks" . "7c0ef09d57a80068a11edc74c3568e5ead5cc15a")
 ("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" . "1f0612eb936d36abab0f27b09cca691e81fc6e74")
 ("emacs-libvterm" . "6f95a1b2949f60539fd92e3a63011801a7e765fd")
 ("emacs-memoize" . "51b075935ca7070f62fae1d69fe0ff7d8fa56fdd")
 ("emacs-python-pytest" . "4a1c4c8915c12e540d41aae1d4e326a2362da541")
 ("emacs-tree-sitter" . "7f5d0938002092ec08830a73f64961021303e1e9")
 ("emacs-undo-fu" . "c0806c1903c5a0e4c69b6615cdc3366470a9b8ca")
 ("emacs-web-server" . "22ce66ea43e0eadb9ec1d691a35d9695fc29cee6")
 ("emacs-websocket" . "34e11124fdd9d73e431499ba8a6b6a8023519664")
 ("emacs-which-key" . "428aedfce0157920814fbb2ae5d00b4aea89df88")
 ("emacs-winum" . "c5455e866e8a5f7eab6a7263e2057aff5f1118b9")
 ("emacs-zmq" . "790033363cf0e78c616cfe117a2f681381e96f29")
 ("emacsmirror-mirror" . "73d68771488284cceb42f70fda551e0a516cb249")
 ("embark" . "26e73117910e78afa209524ecb8f07add45a9ec3")
 ("envrc" . "a7c6ca84a2b0617c94594a23a0c05246f14fa4ee")
 ("epl" . "78ab7a85c08222cd15582a298a364774e3282ce6")
 ("eros" . "dd8910279226259e100dab798b073a52f9b4233a")
 ("ess-view-data" . "283251e8ac19ac0c0f89a4b0f0eb38482167e52b")
 ("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")
 ("gcmh" . "0089f9c3a6d4e9a310d0791cf6fa8f35642ecfd9")
 ("general.el" . "a0b17d207badf462311b2eef7c065b884462cb7c")
 ("git-timemachine" . "8d675750e921a047707fcdc36d84f8439b19a907")
 ("git.el" . "a3396a7027a7d986598c6a2d6d5599bac918f3da")
 ("gntp.el" . "767571135e2c0985944017dc59b0be79af222ef5")
 ("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")
 ("log4e" . "7df0c1ff4656f8f993b87064b1567618eadb5546")
 ("lsp-mode" . "8166a1fe04891efcb7f1262609e77fc48500537b")
 ("lsp-pyright" . "71ff088ac4c93b0edd012f305a3dfd1602c5d21e")
 ("lsp-treemacs" . "3bae4a91e05d55d5ca92da272ffcd497f370e9df")
 ("lsp-ui" . "62568188b7cbc0758a0c4bfb57647708406ddf51")
 ("magit" . "25f432551347468ce97b8b03987e59092e91f8f0")
 ("marginalia" . "d38a27867bcec0bafa43e8d1bd3fd96a32b15d31")
 ("markdown-mode" . "377ce39ffe69f058994ac4e98bde8cfb58661406")
 ("melpa" . "ef16b4c37ad80b034f0ed682f644cb5056743e53")
 ("modus-themes" . "1903cf653055482714c0f4cfa79e53c82fba047c")
 ("nix-mode" . "0023fc5b100ec0c939ffe699d1a7d1afcf1f417a")
 ("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" . "346de84be53cae3663b4e2512222c279933846d4")
 ("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")
 ("transient" . "90e640fe8fa3f309c7cf347501e86ca5cd0bd85e")
 ("transpose-frame" . "12e523d70ff78cc8868097b56120848befab5dbc")
 ("treemacs" . "332d4e0f1f606c472dd083c9cdd4f143ee23020a")
 ("use-package" . "caa92f1d64fc25480551757d854b4b49981dfa6b")
 ("vertico" . "cc23578c9143d2e320c9ed0283ad2e82c37ab65d")
 ("vterm-toggle" . "61cb072af997aa961e2aebe0125b883ff3bd6f43")
 ("weblorg" . "ca7136629fc3f3f1646fa9bd5ad51766062edb39")
 ("with-editor" . "6735180e73e787b79535c245b162249b70dbf841")
 ("xwwp" . "f67e070a6e1b233e60274deb717274b000923231")
 ("yaml-mode" . "fc5e1c58f94472944c4aa838f00f6adcac6fa992")
 ("yasnippet" . "5cbdbf0d2015540c59ed8ee0fcf4788effdf75b6"))
:beta

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
  :init
  (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))
  (setq native-comp-async-report-warnings-errors nil)
  (setq load-prefer-newer t)


  ;; 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 variables

(use-package emacs
  :init

  (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.3 Font

(use-package emacs
  :init
  (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.4 Adjust font size

(use-package emacs
  :init
  (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)
                          (lc/get-font-size)
                        (+ 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)."
    (interactive)
    (lc/adjust-font-size 5))

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

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

4.5 macOS

(use-package emacs
  :init
  (defun lc/is-macos? ()
    (and (eq system-type 'darwin)
         (= 0 (length (shell-command-to-string "uname -a | grep iPad")))))
  (when (lc/is-macos?)
    (setq mac-command-modifier 'super)     ; command as super
    (setq mac-option-modifier 'meta)     ; alt as meta
    (setq mac-control-modifier 'control)
    )
  ;; when on emacs-mac 
  (when (fboundp 'mac-auto-operator-composition-mode)
      (mac-auto-operator-composition-mode)   ;; enables font ligatures
      (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.6 Garbage collector magic hack

Used by DOOM to manage garbage collection

(use-package gcmh
  :demand
  :config
  (gcmh-mode 1))

4.7 helpful

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

4.8 eldoc

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

4.9 exec path from shell

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

4.10 no littering

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

4.11 server mode

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

4.12 Auto-pair parenthesis

(use-package emacs
  :hook
  ((org-jupyter-mode . (lambda () (setq-local electric-pair-text-pairs lc/default-electric-pairs)))
   (org-mode . (lambda () (lc/add-local-electric-pairs '((?= . ?=) (?~ . ?~))))))
  :init
  ;; 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/add-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
  :config
  (general-evil-setup)

  (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")

  (lc/leader-keys
    "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
    )

  (lc/local-leader-keys
    :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 ^)
g_
last character on the line (synonym to $)

Marks:

ma
mark a position in buffer and save it to register a
'a
go to mark a
mA
mark position and filename [
]'
go to next mark
''
go back to previous mark (kept track automatically)
g;
go to previous change location
gi
go back to insert mode where you left off
C-o
jump (out) to previous position (useful after gd)
C-i
jump (in) to previous position

Macros:

qq
record macro q
@q
execute macro q

Registers:

"ayio
save object in register a "
"ap
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
  :demand
  :general
  (lc/leader-keys
    "wv" 'evil-window-vsplit
    "ws" 'evil-window-split)
  :init
  (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)
  :config
  (evil-mode 1)
  (define-key evil-insert-state-map (kbd "C-g") 'evil-normal-state)
  (define-key evil-motion-state-map "_" 'evil-end-of-line)
  (define-key evil-motion-state-map "0" 'evil-beginning-of-line)
  (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
  :demand
  :init
  (setq evil-collection-magit-use-z-for-folds nil)
  :config
  (evil-collection-init))

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
  :config
  (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 EVAL-FUNC [ARGS...])
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
  :demand
  :init
  (setq evil-goggles-duration 0.05)
  :config
  (push '(evil-operator-eval
          :face evil-goggles-yank-face
          :switch evil-goggles-enable-yank
          :advice evil-goggles--generic-async-advice)
        evil-goggles--commands)
  (evil-goggles-mode)
  (evil-goggles-use-diff-faces)
  )

5.2.5 evil-snipe

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

5.2.6 evil-nerd-commenter

(use-package evil-nerd-commenter
  :general
  (general-nvmap
    "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
  :general
  (: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
  :demand
  :config
  (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
  :hook
  ((emacs-lisp-mode . evil-cleverparens-mode)
   (clojure-mode . evil-cleverparens-mode))
  :init
  (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

Keybindings:

TAB
toggle occurrence
n / N
next/prev occurrence
F
restrict scope to function
J / K
extend scope of match down/up
V
toggle visibility of matches
(use-package evil-iedit-state
  :general
  (lc/leader-keys
    "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
  :general
  (general-vmap
    "A" #'evil-mc-make-cursor-in-visual-selection-end
    "I" #'evil-mc-make-cursor-in-visual-selection-beg)
  (general-nmap
    "Q" #'evil-mc-undo-all-cursors)
  :config
  (global-evil-mc-mode 1)
  )

5.2.12 Fix scroll error when centaur tabs is active

Taken from this PR: https://github.com/emacs-evil/evil/pull/1323/files

(use-package evil
  :init
  (defun lc/evil-posn-x-y (position)
    (let ((xy (posn-x-y position)))
      (when header-line-format
        (setcdr xy (+ (cdr xy)
                      (or (and (fboundp 'window-header-line-height)
                               (window-header-line-height))
                          evil-cached-header-line-height
                          (setq evil-cached-header-line-height (evil-header-line-height))))))
      (when (fboundp 'window-tab-line-height)
        (setcdr xy (+ (cdr xy) (window-tab-line-height))))
      xy))
  :config
  (advice-add 'evil-posn-x-y :override #'lc/evil-posn-x-y)
  )

5.3 which-key

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

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))
  :general
  (lc/leader-keys
    "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"))
  (lc/local-leader-keys
    :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"))
  (org-mode-map
   :states 'normal
   "z i" '(org-toggle-inline-images :wk "inline images"))
  :init
  ;; general settings
  (when (file-directory-p "~/Dropbox")
    (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"
          ))  
  (setq ;; 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
        '(ol-docview
          org-habit))
  (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)
  :config
  ;; (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
  :general
  (lc/leader-keys
    "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"))
  :init
  ;; if on iPad, only add readme.org
  (setq org-agenda-files (list (concat user-emacs-directory "readme.org")))
  ;; if on Mac with Dropbox, use real agenda files
  (when (file-directory-p "~/Dropbox")
    (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
  :config
  ;; 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")))
    "9.2.4")

  (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"))))
    "9.2.4"
    )

  ;; (provide 'org-version)
  )

6.4 org capture templates

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

                 \*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"
                    ":PROPERTIES:\n"
                    ":CAPTURED: %U\n"
                    ":END:\n\n"
                    "%i%l"))
          ("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"
                    "%i%?"))))
  )

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

6.6 cycle only one heading

(use-package org
  :init
  (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)
      (save-excursion
        (org-beginning-of-line)
        (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))
            (org-cycle-internal-local)
            t)))))
  :config
  ;; 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."
  (make-process
   :command `("bash" "-c" ,command)
   :name (if name name
           "async-process")
   :filter (if filter filter
             (lambda (process output) (message (s-trim output))))))

(defun lc/tangle-config ()
  "Export code blocks from the literate config file
asynchronously."
  (interactive)
  (let ((command (if (file-directory-p "/Applications/Emacs.app")
                     "/Applications/Emacs.app/Contents/MacOS/Emacs %s --batch --eval '(org-babel-tangle nil \"%s\")'"
                   ;; on iPad
                   "emacs %s --batch --eval '(org-babel-tangle nil \"%s\")'"
                   )))
    ;; 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
    (lc/async-process
     (format command
             (expand-file-name "readme.org" user-emacs-directory)
             (expand-file-name "init.el" user-emacs-directory))
     "tangle-process")
    )

  )

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)
  :init
  (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)
  :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")))
    (hl-todo-mode))
  )

6.11 org babel

(use-package org
  :general
  (lc/local-leader-keys
    :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"))
  (lc/local-leader-keys
    :keymaps 'org-src-mode-map
    "'" '(org-edit-src-exit :wk "exit")) ;;FIXME
  :init
  (setq org-confirm-babel-evaluate nil)
  :config
  (org-babel-do-load-languages
   'org-babel-load-languages
   '((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)))
  :init
  (setq ob-async-no-async-languages-alist '("jupyter-python" "jupyter-R" "jupyter-julia")))

6.13 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))))
  :general
  (lc/leader-keys
    "t p" '(org-tree-slide-mode :wk "present"))
  (general-nmap
    :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)
  :init
  (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.14 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-normalize-keymaps)
                       (evil-org-set-key-theme '(textobjects))
                       (require 'evil-org-agenda)
                       (evil-org-agenda-set-keys))))
  :bind
  ([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>" 
  :general
  (general-nmap
    :keymaps 'org-mode-map :states 'normal
    "RET"   #'+org/dwim-at-point)
  :init
  (defun +org--insert-item (direction)
    (let ((context (org-element-lineage
                    (org-element-context)
                    '(table table-row headline inlinetask item plain-list)
                    t)))
      (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-beginning-of-item)
           (org-end-of-item)
           (backward-char))
         (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)))
           (org-end-of-item)
           (org-end-of-line)))

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

        ;; 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
             (`below
              (let (org-insert-heading-respect-content)
                (goto-char (line-end-position))
                (org-end-of-subtree)
                (insert "\n" (make-string level ?*) " ")))
             (`above
              (org-back-to-heading)
              (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)))
             (org-todo
              (cond ((eq todo-type 'done)
                     ;; Doesn't make sense to create more "DONE" headings
                     (car (+org-get-todo-keywords-for todo-keyword)))
                    (todo-keyword)
                    ('todo)))))))

      (when (org-invisible-p)
        (org-show-hidden-entry))
      (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
        (`headline
         (cond ((memq (bound-and-true-p org-goto-map)
                      (current-active-maps))
                (org-goto-ret))
               ((and (fboundp 'toc-org-insert-toc)
                     (member "TOC" (org-get-tags)))
                (toc-org-insert-toc)
                (message "Updating table of contents"))
               ((string= "ARCHIVE" (car-safe (org-get-tags)))
                (org-force-cycle-archived))
               ((or (org-element-property :todo-type context)
                    (org-element-property :scheduled context))
                (org-todo
                 (if (eq (org-element-property :todo-type context) 'done)
                     (or (car (+org-get-todo-keywords-for (org-element-property :todo-keyword context)))
                         'todo)
                   'done))))
         ;; Update any metadata or inline previews in this subtree
         (org-update-checkbox-count)
         (org-update-parent-todo-statistics)
         (when (and (fboundp 'toc-org-insert-toc)
                    (member "TOC" (org-get-tags)))
           (toc-org-insert-toc)
           (message "Updating table of contents"))
         (let* ((beg (if (org-before-first-heading-p)
                         (line-beginning-position)
                       (save-excursion (org-back-to-heading) (point))))
                (end (if (org-before-first-heading-p)
                         (line-end-position)
                       (save-excursion (org-end-of-subtree) (point))))
                (overlays (ignore-errors (overlays-in beg end)))
                (latex-overlays
                 (cl-find-if (lambda (o) (eq (overlay-get o 'org-overlay-type) 'org-latex-overlay))
                             overlays))
                (image-overlays
                 (cl-find-if (lambda (o) (overlay-get o 'org-image-overlay))
                             overlays)))
           ;; (+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))

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

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

        ((or `planning `timestamp)
         (org-follow-timestamp-link))

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

        (`table-cell
         (org-table-blank-field)
         (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)))

        (`babel-call
         (org-babel-lob-execute-maybe))

        (`statistics-cookie
         (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))

        (`link
         (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--toggle-inline-images-in-subtree
                (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--toggle-inline-images-in-subtree
            (org-element-property :begin context)
            (org-element-property :end context))))))))

6.15 exporters

6.15.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
  :straight
  (org-html-themify
   :type git
   :host github
   :repo "DogLooksGood/org-html-themify"
   :files ("*.el" "*.js" "*.css"))
  :hook (org-mode . org-html-themify-mode)
  :config
  ;; otherwise it complains about invalid face
  (require 'hl-line)
  )

6.15.2 ox-gfm

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

6.15.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.15.4 ox-reveal

(use-package org-re-reveal
  :after org
  :init
  ;; (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.15.5 weblorg

(use-package weblorg)

(use-package templatel)

(use-package htmlize)

6.15.6 ox-cv

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

6.16 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)
  :init
  (setq org-appear-autoemphasis  t)
  (setq org-appear-autolinks t)
  (setq org-appear-autosubmarkers t)
  )

6.17 automatic latex preview

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

6.18 org-encrypt

  • Use org-encrypt-entry on a headline
  • Use org-decrypt-entry to decrypt
(use-package org
  :config
  (require 'org-crypt)
  (require 'epa-file)
  (epa-file-enable)
  (org-crypt-use-before-save-magic)
  (setq org-tags-exclude-from-inheritance (quote ("crypt")))
  (setq org-crypt-key nil)
  (defun ag/reveal-and-move-back ()
    (org-reveal)
    (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.19 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
  :init
  (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."
    (interactive)
    (org-with-point-at pom
      (let ((id (org-entry-get nil "CUSTOM_ID")))
        (cond
         ((and id (stringp id) (string-match "\\S-" id))
          id)
         (create
          (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)))
          id)))))
  
  (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"
    (interactive)
    (save-excursion
      (widen)
      (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))))))
  :config
  (require 'org-id)
  (setq org-id-link-to-org-use-id 'create-if-interactive-and-no-custom-id)
  )

6.20 org-jupyter-mode

(use-package emacs
  :hook
  ((org-jupyter-mode . (lambda () (visual-line-mode -1)))
   (org-mode . (lambda () (when (lc/is-jupyter-org-buffer?) (org-jupyter-mode)))))
  :general
  (lc/local-leader-keys
    :states 'normal
    "k i" '(jupyter-org-interrupt-kernel :wk "interrupt")
    "k r" '(jupyter-repl-restart-kernel :wk "restart"))
  :init
  (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)
    )
  )

7 UI

7.1 all the icons

  (use-package all-the-icons)

7.2 doom modeline

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

7.3 Fancy titlebar for macOS

(use-package emacs
  :init
  (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")
  :demand
  :if (display-graphic-p)
  :hook (modus-themes-after-load-theme . lc/fix-fill-column-indicator)
  :general
  (lc/leader-keys
    "t t" '((lambda () (interactive) (modus-themes-toggle)) :wk "toggle theme"))
  :init
  (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/override-colors ()
    (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")))
    )
  (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))))
    (modus-themes-load-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")
    (modus-themes-load-operandi))
  (defun lc/change-theme-with-mac-system ()
    (let ((appearance (plist-get (mac-application-state) :appearance)))
      (cond ((equal appearance "NSAppearanceNameAqua")
             (lc/load-light-theme))
            ((equal appearance "NSAppearanceNameDarkAqua")
             (lc/load-dark-theme)))))
  (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 ()
    (when (display-graphic-p)
      (modus-themes-with-colors
        (custom-set-faces
         `(fill-column-indicator ((,class :background ,bg-inactive :foreground ,bg-inactive)))))))
  :config
  (when (display-graphic-p)
    (lc/override-colors))
  (if (and (boundp 'mac-effective-appearance-change-hook)
           (plist-get (mac-application-state) :appearance))
      (progn
        (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)
    ;; (add-hook 'emacs-startup-hook 'lc/load-light-theme)
    (add-hook 'emacs-startup-hook 'lc/change-theme-with-timers)
    )
  )

7.5 dashboard

(use-package dashboard
  :demand
  :init
  (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)
            "Github"
            "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)
            "JIRA"
            "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)
            "Restore"
            "Restore"
            (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))
                  (org-get-heading)
                  (org-outline-level)
                  (org-get-category)
                  nil;; (org-get-tags)
                  t))
           (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
                     dashboard-match-agenda-entry
                     'agenda
                     dashboard-filter-agenda-entry))
  (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
                     dashboard-match-next-entry
                     'agenda))
  (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)))
      (dashboard-insert-section
       "Next tasks"
       next
       list-size
       "n"
       `(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)))))
  :config
  ;; 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\""
                         ))))
  (dashboard-setup-startup-hook)
  (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
  (general-nmap "gt" 'centaur-tabs-forward
    "gT" 'centaur-tabs-backward)
  (lc/leader-keys
    "b K" '(centaur-tabs-kill-other-buffers-in-current-group :wk "kill other buffers"))
  :init
  (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)
  (setq centaur-tabs-enable-ido-completion nil)
  :config
  (centaur-tabs-mode t)
  ;; (centaur-tabs-headline-match)
  (centaur-tabs-group-by-projectile-project)
  )

7.7 centered cursor mode

(use-package centered-cursor-mode
  :general
  (lc/leader-keys
    "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))))

;; reuse existing windows
;; (setq display-buffer-alist
;;       '((".*"
;;          (display-buffer-reuse-window display-buffer-same-window)
;;          (reusable-frames . t))))

;; (setq even-window-sizes nil)  ; display-buffer hint: avoid resizing

7.10 winum

(use-package winum
:general
(lc/leader-keys
"1" '(winum-select-window-1 :wk "win 1")
"2" '(winum-select-window-2 :wk "win 2")
"3" '(winum-select-window-3 :wk "win 3"))
:config
(winum-mode))

7.11 transpose frame

  (use-package transpose-frame
    :general
    (lc/leader-keys
      "w t" '(transpose-frame :wk "transpose")
      "w f" '(rotate-frame :wk "flip")))

7.12 persistent scratch

(use-package persistent-scratch
  :hook
  (org-mode . (lambda ()
                "only set initial-major-mode after loading org"
                (setq initial-major-mode 'org-mode)))
  :general
  (lc/leader-keys
    "bs" '((lambda ()
             "Load persistent-scratch if not already loaded"
             (interactive)
             (progn 
               (unless (boundp 'persistent-scratch-mode)
                 (require 'persistent-scratch))
               (pop-to-buffer "*scratch*")))
           :wk "scratch"))
  :init
  (setq persistent-scratch-autosave-interval 60)
  :config
  (persistent-scratch-setup-default))

7.13 olivetti mode

  (use-package olivetti
    :general
    (lc/leader-keys
      "t o" '(olivetti-mode :wk "olivetti"))
    :init
    (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)
  :hook
  (prog-mode . display-fill-column-indicator-mode)
  :init
  (setq-default fill-column  90)
  ;; (setq display-fill-column-indicator-character "|")
  )

7.15 Whitespace mode

(use-package emacs
  :hook
  ((org-jupyter-mode . (lambda () (whitespace-mode -1)))
   (org-mode . whitespace-mode))
  :init
  (setq-default
   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)
  :init
  ;; (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
  :general
  (lc/leader-keys
    "w o" '(doom/window-enlargen :wk "enlargen"))
  :init
  (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))
        (set-frame-parameter
         nil param
         (if (and (equal window (selected-window))
                  (not arg)
                  wconf)
             (ignore
              (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))
               (unwind-protect
                   (progn
                     (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)))))))))
  )

7.18 Alerts

(use-package alert
  :if (lc/is-macos?)
  :config
  (setq
   ;; alert-default-style 'notifier
   alert-default-style 'osx-notifier
   )
  ;; (alert "This is an alert" :severity 'high)
  ;; (alert "This is an alert" :title "My Alert" :category 'debug)
  )

7.19 darkroom

(use-package darkroom
  :init
  ;; Don't scale the text, so ugly man!
  (setq darkroom-text-scale-increase 1)
  :general
  (lc/leader-keys
    "tf" '(darkroom-tentative-mode :wk "focus")))

7.20 8 colors theme

When using emacs on my jailbroken iPad, I cannot set TERM=xterm-256color because of some terminfo error. I therefore do what I can with the 8 colors I can use.

The default theme manoj-dark does a pretty good job OOTB. I add a few manual tweaks. The theme defintion gets saved in custom-theme-directory.

(deftheme 8colors
  "Theme using only 8 colors")

;; (custom-theme-set-variables
;;   '8colors
;;   '(overline-margin 0)
;; )

(custom-theme-set-faces
 '8colors
 '(centaur-tabs-unselected ((t (:foreground "white" :background "black"))) t)
 '(centaur-tabs-unselected-modified ((t (:foreground "white" :background "black"))) t)
 '(tool-bar ((t (:background "black"))) t)
 '(selectrum-current-candidate ((t (:background "blue"))) t)
 '(org-code ((t (:foreground "magenta"))) t)
 '(org-special-keyword ((t (:foreground "magenta"))) t)
 '(mode-line ((t (:background "black"))) t)
 '(doom-modeline-buffer-file ((t (:background "black"))) t)
 '(tab-line ((t (:background "black"))) t)
 '(magit-diff-removed-highlight ((t (:background "red" :foreground "white"))) t)
 '(magit-diff-added-highlight ((t (:background "green" :foreground "white"))) t)
 '(iedit-occurrence ((t (:background "blue" :foreground "white"))) t)
 )

(provide-theme '8colors)
(unless (> (display-color-cells) 8)
  (setq custom-theme-directory (concat user-emacs-directory "themes"))
  (custom-set-variables '(custom-enabled-themes '(8colors manoj-dark)))
  )

8 Completion framework

8.1 selectrum

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

8.2 prescient

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

8.3 marginalia

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

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
  (general-nmap "C-l" 'embark-act)
  (selectrum-minibuffer-map "C-l" #'embark-act)
  (embark-file-map "o" 'find-file-other-window) 
  :config
  ;; For Selectrum users:
  (defun current-candidate+category ()
    (when selectrum-active-p
      (cons (selectrum--get-meta 'category)
            (selectrum-get-current-candidate))))

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

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

  (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
  :hook
  (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
  :straight (consult :host github :repo "minad/consult" :branch "main")
  :commands (consult-ripgrep)
  :general
  (general-nmap
    :states '(normal insert)
    "C-p" 'consult-yank-pop)
  (lc/leader-keys
    "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"))
  :init
  (setq xref-show-xrefs-function #'consult-xref
        xref-show-definitions-function #'consult-xref)  
  ;; (setq consult-preview-key "C-l")
  ;; (setq consult-narrow-key ">")
  :config
  (autoload 'projectile-project-root "projectile")
  (setq consult-project-root-function #'projectile-project-root)
  (with-eval-after-load 'selectrum
    (require 'consult-selectrum))

  )

8.7 consult-selectrum

(use-package consult-selectrum
  :straight (consult-selectrum :host github :repo "minad/consult" :branch "main")
  :after consult
  :demand)

8.8 vertico

(use-package vertico
  :straight (vertico :type git :host github :repo "minad/vertico")
  :demand
  ;; :bind (:map vertico-map
  ;;        ("C-j" . vertico-next)
  ;;        ("C-k" . vertico-previous)
  ;;        ("<escape>" . vertico-exit))
  ;; :init
  ;; (vertico-mode)
  )

;; TODO: replace prescient with this?
;; (use-package savehist
;;   :init
;;   (savehist-mode))

;; TODO: use this when selectrum has been replaced
;; (use-package marginalia
;;   :after vertico
;;   :custom
;;   (marginalia-annotators '(marginalia-annotators-heavy marginalia-annotators-light nil))
;;   :init
;;   (marginalia-mode))

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
  :demand
  :general
  (lc/leader-keys
    :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"))
  :init
  (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)))
  :config
  (defadvice projectile-project-root (around ignore-remote first activate)
    (unless (file-remote-p default-directory) ad-do-it))
  (projectile-mode)
  ;; 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))
      (help-mode)
      (display-buffer (current-buffer) t))
    (projectile-commander))
  (def-projectile-commander-method ?t
    "Open a *shell* buffer for the project."
    (projectile-run-vterm))
  (def-projectile-commander-method ?\C-? ;; backspace
    "Go back to project selection."
    (projectile-switch-project))
  (def-projectile-commander-method ?d
    "Open project root in dired."
    (projectile-dired))
  (def-projectile-commander-method ?f
    "Find file in project."
    (projectile-find-file))
  (def-projectile-commander-method ?s
    "Ripgrep in project."
    (consult-ripgrep))
  (def-projectile-commander-method ?g
    "Git status in project."
    (projectile-vc))
  )

9.1.2 perspective

TODO:

  • 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)
  :general
  (lc/leader-keys
    "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"))
  :init
  (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."
    (interactive)
    (persp-switch "main")
    (switch-to-buffer dashboard-buffer-name)
    (dashboard-mode)
    (dashboard-insert-startupify-lists)
    (dashboard-refresh-buffer))
  :config
  (persp-mode)
  (add-hook 'kill-emacs-hook #'persp-state-save))

9.1.3 persp-projectile

(use-package persp-projectile
  :after projectile
  :general
  (lc/leader-keys
    "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

magit-cycle-margin-style to show more precise commit timestamps

(use-package magit
  :general
  (lc/leader-keys
    "g b" 'magit-blame
    "g g" 'magit-status
    "g G" 'magit-status-here
    "g l" 'magit-log)
  (general-nmap
    :keymaps '(magit-status-mode-map
               magit-stash-mode-map
               magit-revision-mode-map
               magit-process-mode-map
               magit-diff-mode-map)
    "TAB" #'magit-section-toggle
    "<escape>" #'transient-quit-one)
  :init
  (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)
  ;; (setq magit-log-margin (t "%Y-%m-%d %H:%M " magit-log-margin-width t 18))
  :config
  (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
    (general-nmap "SPC g t" 'git-timemachine-toggle)
    (git-timemachine-mode-map
     "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
  :demand
  :general
  (lc/leader-keys
    "g n" '(diff-hl-next-hunk :wk "next hunk")
    "g p" '(diff-hl-previous-hunk :wk "prev hunk"))
  :hook
  ((magit-pre-refresh . diff-hl-magit-pre-refresh)
   (magit-post-refresh . diff-hl-magit-post-refresh))
  :init
  (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)))
  :config
  (global-diff-hl-mode)
  )

9.2.5 hydra-smerge

(use-package smerge-mode
  :straight (:type built-in)
  :after hydra
  :general
  (lc/leader-keys "g m" 'smerge-hydra/body)
  :hook
  (magit-diff-visit-file . (lambda ()
                             (when smerge-mode
                               (smerge-hydra/body))))
  :init
  (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
  :demand)

9.4 rainbow parenthesis

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

9.5 emacs tree-sitter

(use-package tree-sitter
  :straight (tree-sitter :host github :repo "ubolonton/emacs-tree-sitter" :depth full)
  :hook (python-mode . (lambda ()
                         (require 'tree-sitter)
                         (require 'tree-sitter-langs)
                         (require 'tree-sitter-hl)
                         (tree-sitter-hl-mode)
                         )))

(use-package tree-sitter-langs
  :straight (tree-sitter-langs :host github :repo "ubolonton/emacs-tree-sitter" :depth full))

(use-package tsc
  :straight (tsc :host github :repo "ubolonton/emacs-tree-sitter" :depth full))

9.6 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.7 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
  :general
  (yas-minor-mode-map
   :states 'insert
   "TAB" 'nil
   "C-TAB" 'yas-expand)
  :hook
  ((prog-mode org-mode dap-ui-repl-mode vterm-mode) . yas-minor-mode)
  :init
  ;; (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)))
        (yas-expand))))
  :config
  (yas-reload-all)
  (add-hook 'post-command-hook #'lc/yas-try-expanding-auto-snippets)
  )

9.8 undo fu

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

9.9 vterm

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

9.10 vterm toggle

(use-package vterm-toggle
  :general
  (lc/leader-keys
    "'" 'vterm-toggle))

9.11 dired and friends

  • 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

9.11.1 dired

(use-package dired
  :straight (:type built-in)
  :hook
  (dired-mode . dired-hide-details-mode)
  :general
  (lc/leader-keys
    "f d" 'dired
    "f j" 'dired-jump)
  (general-nmap
    :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"))
  :init
  (setq dired-omit-files "^\\.[^.]\\|$Rhistory\\|$RData\\|__pycache__")
  (setq dired-listing-switches "-lah")
  (setq dired-dwim-target t)
  (defun my/dired-open-externally ()
    "Open marked dired file/folder(s) (or file/folder(s) at point if no marks)
  with external application"
    (interactive)
    (let ((files (dired-get-marked-files)))
      (dired-run-shell-command
       (dired-shell-stuff-it "xdg-open" files t))))
  )

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

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

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

9.11.2 dired subtree

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

9.12 restart-emacs

  (use-package restart-emacs
    :general
    (lc/leader-keys
      "R" '(restart-emacs :wk "restart"))
    )

9.13 toml mode

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

9.14 tramp

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

(use-package tramp
  :straight (:type built-in)
  :init
  ;; Disable version control on tramp buffers to avoid freezes.
  (setq vc-ignore-dir-regexp
        (format "\\(%s\\)\\|\\(%s\\)"
                vc-ignore-dir-regexp
                tramp-file-name-regexp))
  (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)
  :config
  (customize-set-variable 'tramp-ssh-controlmaster-options
                          (concat
                           "-o ControlPath=/tmp/ssh-tramp-%%r@%%h:%%p "
                           "-o ControlMaster=auto -o ControlPersist=yes"))
  (with-eval-after-load 'lsp-mode
    (lsp-register-client
     (make-lsp-client :new-connection (lsp-tramp-connection "pyright")
                      :major-modes '(python-mode)
                      :remote? t
                      :server-id 'pyright-remote))
    )
  )

(use-package docker-tramp)

9.15 yaml mode

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

10 Programming languages

10.1 lsp mode

(use-package lsp-mode
  :commands
  (lsp lsp-deferred)
  :hook
  ((lsp-mode . (lambda () (setq-local evil-lookup-func #'lsp-describe-thing-at-point)))
   (lsp-mode . lsp-enable-which-key-integration))
  :general
  (lc/local-leader-keys
    :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"))
  (lsp-mode-map
   :states 'normal
   "gD" 'lsp-find-references)
  :init
  (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)
  )

10.2 lsp-ui

(use-package lsp-ui
  :hook
  ((lsp-mode . lsp-ui-mode)
   ;; (lsp-mode . (lambda () (setq-local evil-goto-definition-functions '(lambda (&rest args) (lsp-ui-peek-find-definitions)))))
   )
  ;; :bind
  ;; (:map lsp-ui-mode-map
  ;;       ([remap lsp-find-references] . lsp-ui-peek-find-references))
  :general
  (lc/local-leader-keys
    "h" 'lsp-ui-doc-show
    "H" 'lsp-ui-doc-hide)
  (lsp-ui-peek-mode-map
   :states 'normal
   "C-j" 'lsp-ui-peek--select-next
   "C-k" 'lsp-ui-peek--select-prev)
  (outline-mode-map
   :states 'normal
   "C-j" 'nil
   "C-k" 'nil)
  :init
  (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
  :hook
  ((dap-terminated . lc/hide-debug-windows)
   (dap-ui-repl-mode . (lambda () (setq-local truncate-lines t))))
  :general
  (lc/local-leader-keys
    :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"))
  :init
  ;; (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"))
  :config
  ;; 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
  (lc/local-leader-keys
    :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"))))))
  :general
  (python-mode-map
   :states 'normal
   "gz" nil
   "C-j" nil)
  (python-mode-map
   :states 'insert
   "TAB" 'lc/py-indent-or-complete
   )
  :init
  (setq python-indent-offset 0)
  (defun lc/py-indent-or-complete ()
    (interactive "*")
    (window-configuration-to-register py--windows-config-register)
    (cond ((use-region-p)
           (py-indent-region (region-beginning) (region-end)))
          ((or (bolp)   
               (member (char-before) (list 9 10 12 13 32 ?:  ;; ([{
                                           ?\) ?\] ?\}))
               ;; (not (looking-at "[ \t]*$"))
               )
           (py-indent-line))
          ((comint-check-proc (current-buffer))
           (ignore-errors (completion-at-point)))
          (t
           (completion-at-point))))
  :config
  (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]+\\]: "
        python-shell-completion-setup-code
        "from IPython.core.completerlib import module_completion"
        python-shell-completion-string-code
        "';'.join(get_ipython().Completer.all_completions('''%s'''))\n"))

10.4.2 lsp-pyright

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

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

10.4.3 pytest

(use-package python-pytest
  :general
  (lc/local-leader-keys
    :keymaps 'python-mode-map
    "t" '(:ignore t :wk "test")
    "t d" '(python-pytest-dispatch :wk "dispatch")
    "t f" '(python-pytest-file :wk "file")
    "t t" '(python-pytest-function :wk "function"))
  :init
  (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)))
  :config
  (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)
  :init
  (setq python-flymake-command (executable-find "flake8"))
  (setq flymake-fringe-indicator-position 'right-fringe)
  :general
  (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
  :general
  (lc/local-leader-keys
    :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 "interrupt")
    "k r" '(jupyter-repl-restart-kernel :wk "restart"))
  (lc/local-leader-keys
    :keymaps 'python-mode-map
    :states 'visual
    "e" '(jupyter-eval-region :wk "eval"))
  :init
  (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")))
      (with-temp-buffer
        (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'."
    (interactive)
    (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."
    (interactive)
    (if (bound-and-true-p jupyter-current-client)
        (jupyter-repl-pop-to-buffer)
      (call-interactively 'jupyter-repl-associate-buffer)))
  (advice-add 'jupyter-command :override #'jupyter-command-venv))

10.4.6 ob-jupyter

Note:

  • 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
  :general
  (lc/local-leader-keys
    :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/add-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)
                                             (jupyter-org-interaction-mode))))
         (envrc-mode . lc/load-ob-jupyter))
  :init
  (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")
          (lc/org-load-jupyter)))))
  :config
  (cl-defmethod jupyter-org--insert-result (_req context result)
    (let ((str
           (org-element-interpret-data
            (jupyter-org--wrap-result-maybe
             context (if (jupyter-org--stream-result-p result)
                         (thread-last result
                           jupyter-org-strip-last-newline
                           jupyter-org-scalar)
                       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")))
  ;;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)))
  )

10.4.7 auto-import

(use-package pyimport
  :general
  (lc/local-leader-keys
    :keymaps 'python-mode-map
    "i i" '(pyimport-insert-missing :wk "autoimport")))

10.4.8 blacken

(use-package blacken
  :general
  (lc/local-leader-keys
      :keymaps 'python-mode-map
      "=" '(blacken-buffer :wk "format"))
  )

10.5 R

10.5.1 ess

(use-package ess
  :general
  (lc/local-leader-keys
    :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")
    )
  (lc/local-leader-keys
    :keymaps 'ess-r-mode-map
    :states 'visual
    "RET" '(ess-eval-region-or-line-visibly-and-step :wk "line and step"))
  :init
  (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
  :general
  (lc/local-leader-keys
    :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
  :hook
  (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
  :demand
  :init
  (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))
  :config
  (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)
  :general
  (lc/local-leader-keys
    :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"))
  (lc/local-leader-keys
    :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$"
  :init
  (setq clojure-align-forms-automatically t))

10.8.2 clojure-lsp

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

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))
  :general
  (lc/local-leader-keys
    :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")
    )
  (lc/local-leader-keys
    :keymaps 'clojure-mode-map
    :states 'visual
    "e" 'cider-eval-region)
  :init
  (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
  :init
  (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")
    (save-excursion
      (goto-char (- (cadr (cider-sexp-at-point 'bounds))
                    1))
      (cider-eval-last-sexp output-to-current-buffer)))
  )

10.8.5 ob-clojure

(use-package org
:config
(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 Gimmicks

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)
  :general
  (lc/leader-keys
    "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"))
  :init
  ;; :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)
  :general
  (lc/leader-keys
    "s r" '(elfeed :wk "elfeed"))
  (general-nmap
    :keymaps 'elfeed-search-mode-map
   "x" 'lc/elfeed-xwwp-open)
  :init
  (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))))
  :config
  (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)
                      ("https://www.reddit.com/search.rss?q=url%3A%28youtu.be+OR+youtube.com%29&sort=top&t=week&include_over_18=1&type=link"
                      reddit youtube popular))))

11.3 search google

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

11.4 search github

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

11.5 transient help commands

(use-package emacs
  :general
  (lc/leader-keys
    "h" 'lc/help-transient)
  :config
  (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)
      ]
     ["Describe"
      ("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)
      ]
     ]
    [
     ["Internals"
      ("e" "Echo Messages" view-echo-area-messages)
      ("l" "Lossage" view-lossage)
      ]
     ["Describe"
      ("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)
      ]
     ["Exit"
      ("q" "Quit" transient-quit-one)
      ("<escape>" "Quit" transient-quit-one)
      ]
     ;; ["External"
     ;;  ("W" "Dictionary" lookup-word-at-point)
     ;;  ("D" "Dash" dash-at-point)
     ;;  ]
     ]
    )
  )

12 Provide modules

12.1 init-core

(provide 'init-core)
;;; init-core.el ends here

12.2 init-extras

(provide 'init-extra)
;;; init-extra.el ends here