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

1.4. Modules

I tangle this file with the function lc/tangle-config, you can read source code in this section. Every time I save this .org file, it is tangled to multiple .el files.

I achieve that by means of this file's "local variables", which I put at the end of the .org file:

# Local Variables:
# eval: (add-hook 'after-save-hook (lambda ()(progn (lc/org-add-ids-to-headlines-in-file) (lc/tangle-config))) nil t)
# End:

To design modules, I look at blocks in my config that I would like to toggle on and off. For example if I am on an iPad I may want to not load anything related to Python or vterm.

I assign org properties to each heading. These are determine which .el file they will be britten to. For example the header of the section concerning lsp-mode has the following properties:

:PROPERTIES:
:CUSTOM_ID: h:6BC08822-D2B3-4BE9-9EBE-C42F89F0E688
:header-args:    :emacs-lisp :tangle ./lisp/init-prog-lsp.el
:END:

All subheadings under it will "inherit those properties and will be tangled to the same file. We also need to write some emacs-lisp at the end of the tanged file to "provide" those modules. Here an example of one of these "footer" headers.

I then have a lean init.el (written in this section) which I use to control which modules I want to use in my session. On my main computer I typically want to enable all of them.

1.5. 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.6. 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.7. 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-ui-extra)
  (require 'init-org-roam)
  (require 'init-org-export)
  (require 'init-prog-vterm)
  (require 'init-prog-nix)
  (require 'init-prog-lsp)
  (require 'init-prog-python)
  ;; (require 'init-prog-jupyter)
  (require 'init-prog-elisp)
  (require 'init-prog-markdown)
  ;; (require 'init-prog-r)
  ;; (require 'init-prog-clojure)
  (require 'init-prog-tree-sitter)
  (require 'init-extra-focus)
  (require 'init-extra-web)
  ;; (require 'init-extra-rss)
  ;; (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

NOTE: if you change a package recipe from melpa to github in a use-package block but that package is used as a dependency is used in a previous use-package block with a melpa recipe, you will get a warning. Just make sure to declare the "base" package with the github recipe first.

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

(("Emacs-wgrep" . "f9687c28bbc2e84f87a479b6ce04407bb97cfb23")
 ("ace-window" . "0577c426a9833ab107bab46c60d1885c611b2fb9")
 ("all-the-icons-dired" . "5e9b097f9950cc9f86de922b07903a4e5fefc733")
 ("all-the-icons.el" . "6d48bc9e970ab559bc35a125c55fd83732595706")
 ("annalist.el" . "134fa3f0fb91a636a1c005c483516d4b64905a6d")
 ("avy" . "ba5f035be33693d1a136a5cbeedb24327f551a92")
 ("blacken" . "880cf502198753643a3e2ccd4131ee6973be2e8a")
 ("bui.el" . "f3a137628e112a91910fd33c0cff0948fa58d470")
 ("centaur-tabs" . "5860a5c40c2318797f1274ea4c6907ae77ea1ec9")
 ("centered-cursor-mode.el" . "4093821cc9759ca5a3c6e527d4cc915fc3a5ad74")
 ("cfrs" . "c1f639d7bfd3e728cf85dbe224b06a4be76158f4")
 ("consult" . "0940ca016531f3412003c231b476e5023a510ff9")
 ("corfu" . "2f5e15add5e1fdd20ff83d647e460cd8f85327a5")
 ("csv-mode" . "a4ad2e59d4039bf3b1bdbebdfab21c122ad5b487")
 ("dap-mode" . "76cad34de8984f57c2b1e374e9c985cc7ec8dad0")
 ("darkroom" . "27b928a6e91c3207742180f7e209bae754c9c1fe")
 ("dash.el" . "da167c51e9fd167a48d06c7c0ee8e3ac7abd9718")
 ("diff-hl" . "6fa3af0843093f44e028584a93eef091ec7e79d2")
 ("dired-hacks" . "7c0ef09d57a80068a11edc74c3568e5ead5cc15a")
 ("dired-hide-dotfiles" . "6a379f23f64045f5950d229254ce6f32dbbf5364")
 ("dired-single" . "b254f9b7bfc96a5eab5760a56811f2872d2c590a")
 ("docker-tramp.el" . "7bfbb55417e7d2aac53adf92cb0e3fd329c495c1")
 ("doom-modeline" . "5f30d231176186cbe5206aa133f91cea967717d9")
 ("el-get" . "9353309744e4f8a7c9b1adf22ec99536fb2146b0")
 ("eldoc" . "37e51a5810c496356cdb7107d14c2b7eb2dbdf0b")
 ("elisp-refs" . "c06aec4486c034d0d4efae98cb7054749f9cc0ec")
 ("elisp-tree-sitter" . "48b06796a3b2e76ce004972d929de38146eafaa0")
 ("emacs-async" . "0d52411d3accc3e11a2c64838703a8ce9755c77c")
 ("emacs-bind-map" . "bf4181e3a41463684adfffc6c5c305b30480e30f")
 ("emacs-dashboard" . "1bb5c43b6be65f72c2ff3ab948697c902458a32f")
 ("emacs-hide-mode-line" . "bc5d293576c5e08c29e694078b96a5ed85631942")
 ("emacs-libvterm" . "a940dd2ee8a82684860e320c0f6d5e15d31d916f")
 ("emacs-python-pytest" . "b603c5c7f21d351364deeb78e503d3a54d08a152")
 ("emacs-undo-fu" . "ab8bc10e424bccc847800c31ab41888db789d55d")
 ("emacs-which-key" . "9f64733e4ac563c0cda3685acf4e1c2cf600319b")
 ("emacs-winum" . "c5455e866e8a5f7eab6a7263e2057aff5f1118b9")
 ("emacsmirror-mirror" . "a122c213c5b3ff1bb7fd7995f07b7a18334a19e7")
 ("emacsql" . "c82a0e6b4d256a3743b718cfb640fa9efc045f6e")
 ("embark" . "c9b26c2e18f01ae401df6a69b7a0c1a6bc44b90c")
 ("envrc" . "456c4100de41d2cb50813058a9e727b6e83c5d1e")
 ("epl" . "78ab7a85c08222cd15582a298a364774e3282ce6")
 ("eros" . "dd8910279226259e100dab798b073a52f9b4233a")
 ("evil" . "b5c6950f65e2cde92bd978ec2bc98e67820d91a0")
 ("evil-cleverparens" . "8c45879d49bfa6d4e414b6c1df700a4a51cbb869")
 ("evil-collection" . "e6be41bed7b4399db116038c7f0bf2f484065b48")
 ("evil-goggles" . "08a22058fd6a167f9f1b684c649008caef571459")
 ("evil-iedit-state" . "93e4cbfcee802adbb9dd0ebd5836fea4fa932849")
 ("evil-indent-plus" . "0c7501e6efed661242c3a20e0a6c79a6455c2c40")
 ("evil-lisp-state" . "3c65fecd9917a41eaf6460f22187e2323821f3ce")
 ("evil-mc" . "246aecc17481dd23c172a9b845f02a9d9e322c7f")
 ("evil-nerd-commenter" . "63baf2d1c796edd11bbec5fe1dee711173d4155d")
 ("evil-org-mode" . "a9706da260c45b98601bcd72b1d2c0a24a017700")
 ("evil-snipe" . "a79177df406a79b4ffa25743c752f21363bba1cc")
 ("evil-surround" . "282a975bda83310d20a2c536ac3cf95d2bf188a5")
 ("exec-path-from-shell" . "3a8d97c096c2c5714b667130fd8a80d5622ee067")
 ("f.el" . "50af874cd19042f17c8686813d52569b1025c76a")
 ("flycheck" . "784f184cdd9f9cb4e3dbb997c09d93e954142842")
 ("gcmh" . "0089f9c3a6d4e9a310d0791cf6fa8f35642ecfd9")
 ("general.el" . "9651024e7f40a8ac5c3f31f8675d3ebe2b667344")
 ("git-timemachine" . "3381797bcbf906b18dff654a2361032d2d01b4a3")
 ("gnu-elpa-mirror" . "ebddf266c5234580c9458a139b419c5db1374793")
 ("goto-chg" . "a7c69fa6a33774ee8ca759a064845a88483a4995")
 ("helpful" . "f865f17ad04cd270685187b0a5331ec8eb06e541")
 ("highlight-indent-guides" . "cf352c85cd15dd18aa096ba9d9ab9b7ab493e8f6")
 ("hl-todo" . "42f744ffb513cf2b95517144c64dbf3fc69f711a")
 ("ht.el" . "c4c1be487d6ecb353d07881526db05d7fc90ea87")
 ("hydra" . "9e9e00cb240ea1903ffd36a54956b3902c379d29")
 ("iedit" . "3c7159a107b01b8f740ced72f10351b10bb69784")
 ("inheritenv" . "7e4c8b0d0a43b6f1c6c4d6dbd2f3bf5ce7f20067")
 ("isearch-mb" . "27c19935dd7f067c9feb9a5fda17844c39749346")
 ("kind-icon" . "6e0e0c5c2f846685861ef6c157044b1a55572359")
 ("let-alist" . "592553db5929b54db40af0df90c5add0aaca045b")
 ("lsp-mode" . "a82a4fa3467ec918273ab65d48c5c7d2dbfaec74")
 ("lsp-pyright" . "d428dbcf1802fbe147271c8dc74b073bd9cd7403")
 ("lsp-treemacs" . "72d367757a89453a712f6ba1df9b6e789ece2bbd")
 ("lsp-ui" . "21ce926eedd41ef305c2d89412506ce59b1a7eac")
 ("magit" . "2e73b66c2980abb9211d9881a8710c8ac5a33184")
 ("marginalia" . "9229d88ae4757f3439e81f51799758c009838cb4")
 ("markdown-mode" . "4469553a7395359e96b8796e1fac4de73cb6ccc4")
 ("melpa" . "4e3d46311b4d15314b6d1a0d5ff95c5f7e366223")
 ("modus-themes" . "fe5e1f7a16bae0df0cc305dcb3e5be32cf10f0b5")
 ("nix-mode" . "3d04d92d9c3896d07bc9fed7e4f40032025fbe7b")
 ("no-littering" . "665e324abb690fb50e9d255bc656eb12bb83b0c6")
 ("ob-async" . "9aac486073f5c356ada20e716571be33a350a982")
 ("olivetti" . "a31ac05a161a91fe5c157930b62a6c07037982ee")
 ("orderless" . "1ccf74ffdbb0dd34caa63022e92f947c09c49c86")
 ("org" . "b8656a2cef3d5da7270e391222d8139fc042edd5")
 ("org-appear" . "a4d10fc346ba14f487eb7aa95761b9295089ba55")
 ("org-fragtog" . "15861261a437aca2ec858317de71603d2957b423")
 ("org-reverse-datetree" . "e7a7109e4c34811d471bf685b710234564a556f6")
 ("org-roam" . "679ef6ef001fd1a69b691108178721aa913e7f0f")
 ("org-superstar-mode" . "03be6c0a3081c46a59b108deb8479ee24a6d86c0")
 ("org-tree-slide" . "917612a0d1593de533b7bf0a2792d7e37bb2ca3d")
 ("paredit" . "8330a41e8188fe18d3fa805bb9aa529f015318e8")
 ("persistent-scratch" . "57221e5fdff22985c0ea2f3e7c282ce823ea5932")
 ("persp-projectile" . "4e374d7650c7e041df5af5ac280a44d4a4ec705a")
 ("perspective-el" . "d8211a80fbc2cc0d9e163ef6a3e1d0a693b4e00e")
 ("pfuture" . "bde5b06795e3e35bfb2bba4c34b538d506a0856e")
 ("pkg-info" . "76ba7415480687d05a4353b27fea2ae02b8d9d61")
 ("posframe" . "80cb98aff695a102772cc414d96611bdaf96f00e")
 ("powerline" . "390a95fe5b71cfc20e18d034b4b35b5c159a83fc")
 ("projectile" . "24de2940a8a1f46a7715175a66be67733f1c8fa8")
 ("python-mode" . "dcb376044d020dfe30f8e4273e61863b7d9615ce")
 ("rainbow-delimiters" . "a32b39bdfe6c61c322c37226d66e1b6d4f107ed0")
 ("restart-emacs" . "1607da2bc657fe05ae01f7fdf26f716eafead02c")
 ("s.el" . "08661efb075d1c6b4fa812184c1e5e90c08795a9")
 ("shrink-path.el" . "c14882c8599aec79a6e8ef2d06454254bb3e1e41")
 ("smartparens" . "f59a40d54f35299007c396bd667ce3e9ec4714e3")
 ("spinner" . "34905eae12a236753fa88abc831eff1e41e8576e")
 ("straight.el" . "af5437f2afd00936c883124d6d3098721c2d306c")
 ("svg-lib" . "d736ea09d6aa7064ff974f5bee0f3aadc0d157d4")
 ("toml-mode.el" . "f6c61817b00f9c4a3cab1bae9c309e0fc45cdd06")
 ("transient" . "45241225cf6bec864964191c0b3dc8bfad989723")
 ("transpose-frame" . "12e523d70ff78cc8868097b56120848befab5dbc")
 ("tree-sitter-langs" . "3c0c82f9fb0a796f5ebd7e1e4c89f13d5ab6ef58")
 ("treemacs" . "deb7f2cd9eb06960798edd7393df2602902ed071")
 ("use-package" . "a7422fb8ab1baee19adb2717b5b47b9c3812a84c")
 ("vertico" . "9a3cdb754b018bd43a34fec464d4a88eae167c0e")
 ("vterm-toggle" . "ea0bfeaa200d9ed02b51bbbd83222657b02637e7")
 ("with-editor" . "36d36957628621e8340f755b22082e1f4ed8e2d7")
 ("xwwp" . "f67e070a6e1b233e60274deb717274b000923231")
 ("yaml-mode" . "a79d2a7b9281f8c56f461d717b1ba40fc58e22fd")
 ("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)

  ;; Enable indentation+completion using the TAB key.
  ;; Completion is often bound to M-TAB.
  (setq tab-always-indent 'complete)
  )

4.2. custom variables

(use-package emacs
  :init
  (setq lc/is-ipad (  ;; <
                    > (length (shell-command-to-string "uname -a | grep iPad")) 0))

  (setq lc/is-windows (eq system-type 'windows-nt))

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

  (defcustom lc/variable-pitch-font-family "Sans Serif" ;; "cantarell" ;; 
    "Variable pitch font family"
    :type 'string
    :group 'lc)
  
  (defcustom lc/laptop-font-size
    (if lc/is-windows 100 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)
  
  ;; (setq lc/is-low-power (string= (system-name) "pntk"))

  
  ;; (setq lc/is-slow-ssh (string= (getenv "IS_TRAMP") "true"))
  
  )

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 (monospace)
  (set-face-attribute 'fixed-pitch nil :font lc/default-font-family)
  ;; Set the variable pitch face
  (set-face-attribute 'variable-pitch nil :font lc/variable-pitch-font-family)
  )

4.4. Zoom

(use-package emacs
  :init
  (global-set-key (kbd "C-=") 'text-scale-increase)
  (global-set-key (kbd "C--") 'text-scale-decrease)
  )

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 () (lc/add-local-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 ?<   ;; >
                                )))
  )  

You can use below block to print the value of electric-pair-pairs

(mapcar (lambda (elm) (char-to-string (car elm))) electric-pair-pairs)

4.13. Rename file

(use-package emacs
  :init
  (defun lc/rename-current-file ()
    "Rename the current visiting file and switch buffer focus to it."
    (interactive)
    (let ((new-filename (lc/expand-filename-prompt
                         (format "Rename %s to: " (file-name-nondirectory (buffer-file-name))))))
      (if (null (file-writable-p new-filename))
          (user-error "New file not writable: %s" new-filename))
      (rename-file (buffer-file-name) new-filename 1)
      (find-alternate-file new-filename)
      (message "Renamed to and now visiting: %s" (abbreviate-file-name new-filename))))
  (defun lc/expand-filename-prompt (prompt)
    "Return expanded filename prompt."
    (expand-file-name (read-file-name prompt)))
  )

4.14. xref

(use-package xref
  :straight (:type built-in)
  :init
  (setq xref-prompt-for-identifier nil) ;; always find references of symbol at point
  ;; configured in consult
  ;; (setq xref-show-definitions-function #'xref-show-definitions-completing-read)
  ;; (setq xref-show-xrefs-function #'xref-show-definitions-buffer) ; for grep and the like
  ;; (setq xref-file-name-display 'project-relative)
  ;; (setq xref-search-program 'grep)
  )

4.15. Don't close windows on escape

Sometimes ESC kills my window layout. This advice prevents that from happening.

(use-package emacs
  :init
  (defadvice keyboard-escape-quit
      (around keyboard-escape-quit-dont-close-windows activate)
    (let ((buffer-quit-function (lambda () ())))
      ad-do-it))
  )

4.16. emacs extra packages

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

  (general-nmap
    :states 'normal
    "gD" '(xref-find-references :wk "references")
    )

  (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")
    "<escape>" 'keyboard-escape-quit
    
    ";" '(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" '(lc/rename-current-file :wk "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 +"  'text-scale-increase
    ;; "t -"  'text-scale-decrease
    ;; "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 t)
  (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 )

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-mode eros-eval-region)
      ;; (scheme-mode geiser-eval-region)
      (clojure-mode cider-eval-region)
      (jupyter-python jupyter-eval-region) ;; when executing in src block
      (python-mode python-shell-send-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: outer form text object

NOTE:

  • Mark the outer form with v a f
(use-package evil-cleverparens
  :after evil
  :hook (emacs-lisp-mode . lc/init-cleverparens)
  :init
  (defun lc/init-cleverparens ()
    (require 'evil-cleverparens-util)
    (evil-define-text-object evil-cp-a-defun (count &optional beg end type)
      "An outer text object for a top level sexp (defun)."
      (if (evil-cp--inside-form-p)
          (let ((bounds (evil-cp--top-level-bounds)))
            (evil-range (car bounds) (cdr bounds) 'inclusive :expanded t))
        (error "Not inside a sexp.")))

    (evil-define-text-object evil-cp-inner-defun (count &optional beg end type)
      "An inner text object for a top level sexp (defun)."
      (if (evil-cp--inside-form-p)
          (let ((bounds (evil-cp--top-level-bounds)))
            (evil-range (1+ (car bounds)) (1- (cdr bounds)) 'inclusive :expanded t))
        (error "Not inside a sexp.")))
    
    (define-key evil-outer-text-objects-map "f" #'evil-cp-a-defun)
    (define-key evil-inner-text-objects-map "f" #'evil-cp-inner-defun)
    )
  )

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
  :straight (evil-iedit-state :type git :host github :repo "kassick/evil-iedit-state" :branch "master")
  :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: multi cursor

(use-package evil-mc
  :after evil
  :demand
  :general
  (general-nmap
    "M-n" #'evil-mc-make-and-goto-next-match
    )
  (general-vmap
    ;; "gm" '(:keymap evil-mc-cursors-map)
    "A" #'evil-mc-make-cursor-in-visual-selection-end
    "I" #'evil-mc-make-cursor-in-visual-selection-beg)
  (general-nmap
    "gm" '(:keymap evil-mc-cursors-map)
    "Q" #'evil-mc-undo-all-cursors
    ;; "M-p" #'evil-mc-make-and-goto-prev-cursor
    )
  :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
  :general
  (lc/leader-keys
    "?" 'which-key-show-top-level
    )
  :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
  ;; :straight org-plus-contrib
  ;; :straight (:type built-in)
  :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")
    )
  (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")
    "q" '(org-set-tags-command :wk "tag")
    "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")
    "x" '(org-toggle-checkbox :wk "toggle checkbox")
    )
  (org-mode-map
   :states 'insert
   "TAB" 'nil
   "S-TAB" nil)
  (org-mode-map
   :states 'normal
   "z i" '(org-toggle-inline-images :wk "inline images"))
  :init
  ;; general settings
  (when (file-directory-p "~/org")
    (setq org-directory "~/org"
          +org-export-directory "~/org/export"
          org-default-notes-file "~/org/personal/todo.org"
          org-id-locations-file "~/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"))
  (setq org-latex-pdf-process '("tectonic %f"))
  (add-to-list 'org-export-backends 'beamer)
  (plist-put org-format-latex-options :scale 1.2)
  )

6.2. org code blocks in monospace font

(use-package org
:config
(defun my-adjoin-to-list-or-symbol (element list-or-symbol)
  (let ((list (if (not (listp list-or-symbol))
                  (list list-or-symbol)
                list-or-symbol)))
    (require 'cl-lib)
    (cl-adjoin element list)))

(eval-after-load "org"
  '(mapc
    (lambda (face)
      (set-face-attribute
       face nil
       :inherit
       (my-adjoin-to-list-or-symbol
        'fixed-pitch
        (face-attribute face :inherit))))
    (list 'org-code 'org-block
          ;; 'org-table 'org-block-background
          )))
  )


6.3. org agenda

(use-package org
  :general
  (lc/leader-keys
    "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
  (setq org-agenda-files '())
  ;; if org folder exists, use agenda files
  (when (file-directory-p "~/org/personal")
    (setq org-agenda-files
          (append org-agenda-files
                  '("~/org/personal/birthdays.org"))))   
  
  ;; if roam work folder exists, add to agenda files
  (when (file-directory-p "~/roam/work")
    (setq org-agenda-files
          (append org-agenda-files
                  '("~/roam/work/todo.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.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"~/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 "~/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. 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.6. async tangle

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

(use-package org
  :config
  (require 's)
  (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\")'  2>&1 | grep -v '^Loading.*\.\.\.$' | grep -v '^Using ' | grep -v '^dump '| grep -v '^Finding '"
                   )))
    ;; 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.7. org reverse datetree

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

6.8. 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.9. 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.10. 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)
     ;; (ledger . 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.11. 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.12. 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.13. evil-org-mode

Taken from DOOM:

  • nice +org/insert-item-below function
  • 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 a code block
(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-open-at-point
    ;; "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)))

  )

6.14. exporters   org_export

6.14.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)
  :init
  (setq org-html-themify-themes
        '((light . modus-operandi)
          (dark . modus-operandi)))
  :config
  ;; otherwise it complains about invalid face
  (require 'hl-line)
  
  )

(use-package htmlize
  :after org-html-themify)

6.14.2. ox-gfm

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

6.14.3. ox-ipynb

The first block of the org file is used to determine the exported language. To override this we can write:

#+OX-IPYNB-LANGUAGE: ipython

It seems that also jupyter-python should be replaced with ipython for the export to work.

(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.14.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.14.5. weblorg

(use-package weblorg)

(use-package templatel)

(use-package htmlize)

6.14.6. ox-cv

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

6.15. 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.16. automatic latex preview

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

6.17. 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.18. 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.19. org-jupyter-mode   prog_jupyter

(use-package emacs
  :hook
  ((org-jupyter-mode . (lambda () (visual-line-mode -1)
                         (advice-add 'org-cycle :around #'lc/org-cycle-or-py-complete)))
   (org-mode . (lambda () (when (lc/is-jupyter-org-buffer?) (org-jupyter-mode)))))
  :init
  (defun lc/is-jupyter-org-buffer? ()
    (with-current-buffer (buffer-name)
      (goto-char (point-min))
      (re-search-forward "begin_src jupyter-" 10000 t)))
  (defun lc/org-cycle-or-py-complete (orig-fun &rest args)
    "If in a jupyter-python code block, call py-indent-or-complete, otherwise use org-cycle"
    (if (and (org-in-src-block-p)
             (eq (intern (org-element-property :language (org-element-at-point))) 'jupyter-python))
        (lc/py-indent-or-complete)
      (apply orig-fun args)))
  (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)
    )
  )

6.20. org-roam   org_roam

To set up roam:

  • ln -s ~/OneDrive/.../worknotes ~/roam/work
  • ln -s /Users/cambiaghiluca/Library/Mobile\ Documents/iCloud\~com\~appsonthemove\~beorg/Documents/org/roam ~/roam/personal

:CUSTOMID: h:7a406451-6ae8-4d26-a80b-3b7d9e29c023

(use-package org-roam
  :after org
  :init
  (setq org-roam-directory (file-truename "~/roam"))
  (setq org-roam-v2-ack t)
  (setq org-roam-capture-templates
        '(("d" "default" plain "%?" :target
           (file+head "personal/%<%Y%m%d%H%M%S>-${slug}.org" "#+title: ${title}\n") :unnarrowed t)
          ("w" "work" plain "%?" :target
           (file+head "work/%<%Y%m%d%H%M%S>-${slug}.org"  "#+title: ${title}\n") :unnarrowed t)))
  :general
  (lc/leader-keys
    "TAB n" '((lambda () (interactive) (persp-switch "notes")) :wk "notes")
    "n b" 'org-roam-buffer-toggle
    "n f" 'org-roam-node-find
    "n g" 'org-roam-graph
    "n i" 'org-roam-node-insert
    "n c" 'org-roam-capture
    "n t" 'org-roam-tag-add
    "n r" 'org-roam-ref-add
    "n a" 'org-roam-alias-add
    ;; Dailies
    "n j" 'org-roam-dailies-capture-today
    "n J" 'org-roam-dailies-goto-today
    ;; todos
    "o t" '((lambda () (interactive)
              (persp-switch "notes")
              (find-file (concat org-roam-directory "/work/todo.org")))
            :wk "work todos")
    "o n" '((lambda () (interactive)
              (persp-switch "notes")
              (org-roam-node-find))
            :wk "notes")
    )
  :config
  (org-roam-setup)
  ;; If using org-roam-protocol
  ;; (require 'org-roam-protocol)
  (add-to-list 'display-buffer-alist
               '(("*org-roam*"
                  (display-buffer-in-direction)
                  (direction . right)
                  (window-width . 0.33)
                  (window-height . fit-window-to-buffer))))
  
  )

7. UI

7.1. all the icons

(use-package all-the-icons
  :if (not lc/is-ipad)
  :demand
  )

7.2. all the icons completion

(use-package all-the-icons-completion
  :after (marginalia all-the-icons)
  :hook (marginalia-mode . all-the-icons-completion-marginalia-setup)
  :init
  (all-the-icons-completion-mode))

7.3. 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.4. Modus themes + alternate light/dark themes   modus_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-mixed-fonts t
        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 'light)
    ;; (with-eval-after-load 'org (plist-put org-format-latex-options :foreground "whitesmoke"))
    (with-eval-after-load 'org (plist-put org-format-latex-options :background "Transparent"))
    (with-eval-after-load 'org-html-themify
      (setq org-html-themify-themes '((light . modus-vivendi) (dark . modus-vivendi))))
    ;; (modus-themes-load-vivendi)
    (modus-themes-load-operandi)
    (lc/update-centaur-tabs)
    )
  (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 (plist-put org-format-latex-options :background  "Transparent"))
    (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)
    (lc/update-centaur-tabs))
  (defun lc/update-centaur-tabs ()
    (centaur-tabs-display-update)
    (centaur-tabs-headline-match)
    (set-face-attribute 'centaur-tabs-selected nil :overline (face-background 'centaur-tabs-active-bar-face)))
    (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-light-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. 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.6. dashboard   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 3 (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.7. popup management

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

(use-package emacs
  :init
  (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.8. centered cursor mode   ui_extra

(use-package centered-cursor-mode
  :general
  (lc/leader-keys
    "t =" '((lambda () (interactive) (centered-cursor-mode 'toggle)) :wk "center cursor")
    )
  )

7.9. hide mode line   ui_extra

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

7.10. winum   ui_extra

(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")
"4" '(winum-select-window-4 :wk "win 4")
"5" '(winum-select-window-5 :wk "win 5")
"6" '(winum-select-window-6 :wk "win 6")
)
:config
(winum-mode))

7.11. transpose frame   ui_extra

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

7.12. 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
  (python-mode . display-fill-column-indicator-mode)
  :init
  (setq-default fill-column  90)
  ;; (setq display-fill-column-indicator-character "|")
  )

7.13. 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 'bitmap)
  (setq highlight-indent-guides-method 'character)
  (setq highlight-indent-guides-character ?‖)
  (setq highlight-indent-guides-responsive 'top)
  ;; (setq highlight-indent-guides-responsive 'stack)
  ;; (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.14. 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.15. 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)
 '(magit-hash ((t (:background "black" :foreground "white"))) t)
 '(iedit-occurrence ((t (:background "blue" :foreground "white"))) t)
 )

(provide-theme '8colors)
(use-package emacs
  :init
  (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. marginalia

(use-package marginalia
  :after vertico
  :init
  (setq marginalia-annotators '(marginalia-annotators-heavy marginalia-annotators-light nil))
  (marginalia-mode)
  (with-eval-after-load 'projectile
    (add-to-list 'marginalia-command-categories '(projectile-find-file . file)))
  )

8.2. embark

NOTE:

  • You can act on candidates with C-l
  • You can run embark-export on all results (e.g. after a consult-line) with C-l E
    • You can run embark-export-snapshot with C-l S
(use-package embark
  :after vertico
  :general
  (general-nmap "C-l" 'embark-act)
  (vertico-map
   "C-l" #'embark-act
   )
  (:keymaps 'embark-file-map
            ;; "o" 'find-file-other-window
            "x" 'lc/dired-open-externally
            ) 
  :init
  ;; Optionally replace the key help with a completing-read interface
  (setq prefix-help-command #'embark-prefix-help-command)
  :config
  ;; Hide the mode line of the Embark live/completions buffers
  (add-to-list 'display-buffer-alist
               '("\\`\\*Embark Collect \\(Live\\|Completions\\)\\*"
                 nil
                 (window-parameters (mode-line-format . none))))
  ;; (add-hook 'embark-setup-hook 'selectrum-set-selected-candidate)
  )

8.3. wgrep

After running embark-export, we can edit the results with wgrep and commit the edits. This is extremely powerful for refactorings such as changing the name of a class or a function across files in the project.

(use-package wgrep
  :general
  (grep-mode-map "W" 'wgrep-change-to-wgrep-mode)
  :init
  (setq wgrep-auto-save-buffer t)
  (setq wgrep-change-readonly-file t)
  )

8.4. consult

NOTE:

  • After consult-line you can press M-n twice to search for the symbol at point
(use-package consult
  :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.5. embark-consult

(use-package embark-consult
  :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. vertico

(use-package vertico
  ;; :straight (vertico :type git :host github :repo "minad/vertico")
  :straight (vertico :files (:defaults "extensions/*")
                     :includes (vertico-indexed
                                vertico-flat
                                vertico-grid
                                vertico-mouse
                                ;; vertico-quick
                                vertico-buffer
                                vertico-repeat
                                vertico-reverse
                                vertico-directory
                                vertico-multiform
                                vertico-unobtrusive
                                ))
  :demand
  :hook
  ((minibuffer-setup . vertico-repeat-save) ; Make sure vertico state is saved for `vertico-repeat'
   (rfn-eshadow-update-overlay . vertico-directory-tidy) ; Clean up file path when typing
   ) 
  :general
  (:keymaps 'vertico-map
            "C-j" #'vertico-next
            "C-k" #'vertico-previous
            "<escape>" #'minibuffer-keyboard-quit ; Close minibuffer
            ;; "C-;" #'kb/vertico-multiform-flat-toggle
            "M-<backspace>" #'vertico-directory-delete-word
            )
  (:keymaps '(normal insert visual motion)
            "M-." #'vertico-repeat) ; Perfectly return to the state of the last Vertico minibuffer usage
  ;; :bind (:map vertico-map
  ;;             ("C-j" . vertico-next)
  ;;             ("C-k" . vertico-previous)
  ;;             ("<escape>" . minibuffer-keyboard-quit)
  ;;             )
  :init
  ;; (setq vertico-resize t)
  
  ;; multiform extension
  (setq vertico-grid-separator "       ")
  (setq vertico-grid-lookahead 50)
  (setq vertico-buffer-display-action '(display-buffer-reuse-window))
  (setq vertico-multiform-categories
        '((file indexed)
          (consult-grep buffer)
          (consult-location)
          (imenu buffer)
          (library reverse indexed)
          (org-roam-node reverse indexed)
          (t reverse)
          ))
  (setq vertico-multiform-commands
        '(("flyspell-correct-*" grid reverse)
          (org-refile grid reverse indexed)
          (consult-yank-pop indexed)
          (consult-flycheck)
          (consult-lsp-diagnostics)
          ))
  (defun kb/vertico-multiform-flat-toggle ()
    "Toggle between flat and reverse."
    (interactive)
    (vertico-multiform--display-toggle 'vertico-flat-mode)
    (if vertico-flat-mode
        (vertico-multiform--temporary-mode 'vertico-reverse-mode -1)
      (vertico-multiform--temporary-mode 'vertico-reverse-mode 1)))

  ;; Workaround for problem with `tramp' hostname completions. This overrides
  ;; the completion style specifically for remote files! See
  ;; https://github.com/minad/vertico#tramp-hostname-completion
  (defun lc/basic-remote-try-completion (string table pred point)
    (and (vertico--remote-p string)
         (completion-basic-try-completion string table pred point)))
  (defun lc/basic-remote-all-completions (string table pred point)
    (and (vertico--remote-p string)
         (completion-basic-all-completions string table pred point)))
  (add-to-list 'completion-styles-alist
               '(basic-remote           ; Name of `completion-style'
                 lc/basic-remote-try-completion lc/basic-remote-all-completions nil))

  (setq completion-in-region-function
        (lambda (&rest args)
          (apply (if vertico-mode
                     #'consult-completion-in-region
                   #'completion--in-region)
                 args)))

  :config
  ;; (vertico-multiform-mode) 
  (vertico-mode)

  ;; Prefix the current candidate with “» ”. From
  ;; https://github.com/minad/vertico/wiki#prefix-current-candidate-with-arrow
  (advice-add #'vertico--format-candidate :around
              (lambda (orig cand prefix suffix index _start)
                (setq cand (funcall orig cand prefix suffix index _start))
                (concat
                 (if (= vertico--index index)
                     (propertize "» " 'face 'vertico-current)
                   "  ")
                 cand)))

  
  )

(use-package orderless
  :init
  ;; Configure a custom style dispatcher (see the Consult wiki)
  ;; (setq orderless-style-dispatchers '(+orderless-dispatch)
  ;;       orderless-component-separator #'orderless-escapable-split-on-space)
  (setq completion-styles '(orderless)
        completion-category-defaults nil
        completion-category-overrides '((file (styles partial-completion)))))

(use-package savehist
  :init
  (savehist-mode))

;; A few more useful configurations...
(use-package emacs
  :init
  ;; Add prompt indicator to `completing-read-multiple'.
  ;; Alternatively try `consult-completing-read-multiple'.
  (defun crm-indicator (args)
    (cons (concat "[CRM] " (car args)) (cdr args)))
  (advice-add #'completing-read-multiple :filter-args #'crm-indicator)

  ;; Do not allow the cursor in the minibuffer prompt
  (setq minibuffer-prompt-properties
        '(read-only t cursor-intangible t face minibuffer-prompt))
  (add-hook 'minibuffer-setup-hook #'cursor-intangible-mode)

  ;; Emacs 28: Hide commands in M-x which do not work in the current mode.
  ;; Vertico commands are hidden in normal buffers.
  ;; (setq read-extended-command-predicate
  ;;       #'command-completion-default-include-p)

  ;; Enable recursive minibuffers
  (setq enable-recursive-minibuffers t))

8.7. dabbrev

NOTE:

  • When TAB does not work, use S-TAB as backup
(use-package dabbrev
  :general
  (python-mode-map
   :states 'insert
   "<backtab>" 'dabbrev-completion
   ;; ("C-M-/" . dabbrev-expand)
   )
  )

8.8. corfu

;; Configure corfu
(use-package corfu
  :straight (corfu :type git :host github :repo "minad/corfu")
  ;; :hook (after-init . corfu-global-mode)
  :hook ((prog-mode . corfu-mode)
         (org-mode . corfu-mode))
  :bind
  (:map corfu-map
        ("C-j" . corfu-next)
        ("C-k" . corfu-previous))
  :general
  (evil-insert-state-map "C-k" nil)
  :init
  (setq corfu-auto nil)                 ;; Enable auto completion
  (setq corfu-cycle t)                ;; Enable cycling for `corfu-next/previous'
  (setq corfu-min-width 80)
  (setq corfu-max-width corfu-min-width)       ; Always have the same width
   (setq corfu-preselect-first t)   
   
   (defun corfu-enable-always-in-minibuffer ()
  "Enable Corfu in the minibuffer if Vertico/Mct are not active."
  (unless (or (bound-and-true-p mct--active) ; Useful if I ever use MCT
              (bound-and-true-p vertico--input))
    (setq-local corfu-auto nil)       ; Ensure auto completion is disabled
    (corfu-mode 1)))
   (add-hook 'minibuffer-setup-hook #'corfu-enable-always-in-minibuffer 1)
  ;; :custom
  ;; (corfu-commit-predicate nil)   ;; Do not commit selected candidates on next input
  ;; (corfu-quit-at-boundary t)     ;; Automatically quit at word boundary
  ;; (corfu-quit-no-match t)        ;; Automatically quit if there is no match
  ;; (corfu-preview-current nil)    ;; Disable current candidate preview
  ;; (corfu-preselect-first nil)    ;; Disable candidate preselection
  ;; (corfu-echo-documentation nil) ;; Disable documentation in the echo area
  ;; (corfu-scroll-margin 5)        ;; Use scroll margin
  )

8.9. kind-icon

(use-package kind-icon
  :straight (kind-icon :type git :host github :repo "jdtsmith/kind-icon")
  :after corfu :demand
  :init
  (setq kind-icon-default-face 'corfu-default) ; to compute blended backgrounds correctly
  (setq kind-icon-blend-background nil)  ; Use midpoint color between foreground and background colors ("blended")?
  (setq kind-icon-blend-frac 0.08)
  :config
  (add-to-list 'corfu-margin-formatters #'kind-icon-margin-formatter)
  ;; refresh kind icon cache to match theme 
  (with-eval-after-load 'modus-themes
    (add-hook 'modus-themes-after-load-theme-hook #'(lambda () (interactive) (kind-icon-reset-cache))))

)

9. Programming

9.1. project

9.1.1. projectile

NOTE:

  • 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.
  • projectile excludes git ignored files from projectile-find-file. Use lc/projectile-find-file-all when opening data
(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 F" '(lc/projectile-find-file-all :wk "find file (all)")
    "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)))
  (defun lc/projectile-find-file-all ()
    (interactive)
    (let ((projectile-git-command "git ls-files -zco"))
      (projectile-find-file)))
  :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

NOTE:

  • You can use persp-state-load to restore the perspective when emacs was killed
(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 h" 'persp-prev
    "TAB l" 'persp-next
    "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))
  (defun lc/is-persp-empty? ()
    (seq-filter
     ;; filter away buffers which should be hidden
     (lambda (buffer-name) (not (string-prefix-p "*" buffer-name)))
     ;; get list of buffer names in current perspective
     (mapcar (lambda (elm) (buffer-name (car elm)))
             (centaur-tabs-view (centaur-tabs-current-tabset)))
     ))
  :config
  (persp-mode)
  (add-hook 'kill-emacs-hook #'persp-state-save))

9.1.3. persp-projectile

NOTE:

  • Use SPC TAB r to reload a project when something went wrong with SPC p p
(use-package persp-projectile
  :after projectile
  :init
  (defun lc/get-last-folder-from-known-proj (path)
    "/path/to/something/ returns something"
    (car (last (split-string path "\/") 2)))
  (defun lc/find-project-from-persp (persp-name)
    "known-proj returns /path/to/known-proj"
    (car
     (seq-filter
      (lambda (proj) (string= persp-name (lc/get-last-folder-from-known-proj proj)))
      projectile-known-projects-on-file)))
  (defun lc/persp-reload-project ()
    (interactive)
    (let* ((persp (persp-current-name))
           (proj-root (lc/find-project-from-persp persp)))
      (persp-kill persp)
      (projectile-persp-switch-project proj-root)))
  :general
  (lc/leader-keys
    "p p" 'projectile-persp-switch-project
    "TAB r" '(lc/persp-reload-project :wk "reload")
    ;; "TAB o"  '((lambda () (interactive)
    ;;               (let ((projectile-switch-project-action #'projectile-find-file))
    ;;                 (projectile-persp-switch-project "org")))
    ;;             :wk "org")
    )
  )

9.2. 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 them as subtree with i
  • Open files with macos with O
  • View files with go and exit with q

9.2.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" '(lc/dired-open-externally :wk "open external"))
  :init
  (setq dired-omit-files "^\\.[^.]\\|$Rhistory\\|$RData\\|__pycache__")
  (setq dired-listing-switches "-lah")
  (setq ls-lisp-dirs-first t)
  (setq ls-lisp-use-insert-directory-program nil)
  (setq dired-dwim-target t)
  (defun lc/dired-open-externally ()
    "Open marked dired file/folder(s) (or file/folder(s) at point if no marks)
  with external application"
    (interactive)
    (let ((fn (dired-get-file-for-visit)))
              (start-process "open-external" nil "open" fn)))
  )

(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.2.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.3. 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))

9.4. rainbow parenthesis

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

9.5. restart-emacs

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

9.6. term

(use-package term
  :if lc/is-ipad
  :straight (:type built-in)
  :general
  (lc/leader-keys
    "'" (lambda () (interactive) (term "/bin/zsh")))
  )

(use-package term
  :if lc/is-windows
  :straight (:type built-in)
  :general
  (lc/leader-keys
    "'" (lambda () (interactive)
          (let ((explicit-shell-file-name "C:/Program Files/Git/bin/bash"))
            (call-interactively 'shell))))
  ;; (setq explicit-shell-file-name "C:/Program Files/Git/bin/bash")
  ;; (setq explicit-bash.exe-args '("--login" "-i"))
  )

9.7. tramp

  • Call e.g. dired and input /ssh:user@hostname:/path/to/file
  • In .ssh/config you can set ControlMaster Yes for a host, then ssh with the terminal
(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)
  (setq remote-file-name-inhibit-cache 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.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. git   git

9.9.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

On iPad, we may need to (require 'sendmail) before calling magit-status

(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))
  ;; (when lc/is-ipad (require 'sendmail))
  :config
  (setq magit-buffer-name-format (concat "*" magit-buffer-name-format "*"))
  (evil-define-key* '(normal visual) magit-mode-map
    "zz" #'evil-scroll-line-to-center)
  )

9.9.2. 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.9.3. 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)
  )

:CUSTOMID: h:6C6EA9DD-42EE-49CD-A7FC-5BE9FAB42F7F

9.9.4. smerge + hydra-smerge

(use-package hydra
  :after evil
  :demand
  :general
  (lc/leader-keys "w w" 'evil-windows-hydra/body)
  :init
  (defhydra evil-windows-hydra (:hint nil
                                      ;; :pre (smerge-mode 1)
                                      ;; :post (smerge-auto-leave)
                                      )
    "
 [_h_] ⇢⇠ decrease width [_l_] ⇠⇢ increase width
 [_j_] decrease height [_k_] increase height
│ [_q_] quit"
    ("h" evil-window-decrease-width)
    ("l" evil-window-increase-width)
    ("j" evil-window-decrease-height)
    ("k" evil-window-increase-height)
    ("q" nil :color blue)
    )
  )

(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.10. emacs tree-sitter   prog_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)

9.11. envrc   direnv

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.12. snippets   yasnippet

9.12.1. 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.12.2. LaTeX yasnippets

(use-package yasnippet
  :config
  (setq lc/greek-alphabet
        '(("a" . "\\alpha")
          ("b" . "\\beta" )
          ("g" . "\\gamma")
          ("d" . "\\delta")
          ("e" . "\\epsilon")
          ("z" . "\\zeta")
          ("h" . "\\eta")
          ("t" . "\\theta")
          ("i" . "\\iota")
          ("k" . "\\kappa")
          ("l" . "\\lambda")
          ("m" . "\\mu")
          ("n" . "\\nu")
          ("x" . "\\xi")
          ("p" . "\\pi")
          ("r" . "\\rho")
          ("s" . "\\sigma")
          ("t" . "\\tau")
          ("u" . "\\upsilon")
          ("f" . "\\phi")
          ("c" . "\\chi")
          ("v" . "\\psi")
          ("g" . "\\omega")))

  (setq lc/latex-greek-prefix "'")

  ;; The same for capitalized letters
  (dolist (elem lc/greek-alphabet)
    (let ((key (car elem))
          (value (cdr elem)))
      (when (string-equal key (downcase key))
        (add-to-list 'lc/greek-alphabet
                     (cons
                      (capitalize (car elem))
                      (concat
                       (substring value 0 1)
                       (capitalize (substring value 1 2))
                       (substring value 2)))))))

  (yas-define-snippets
   'latex-mode
   (mapcar
    (lambda (elem)
      (list (concat lc/latex-greek-prefix (car elem)) (cdr elem) (concat "Greek letter " (car elem))))
    lc/greek-alphabet))
  
  (setq lc/english-alphabet
        '("a" "b" "c" "d" "e" "f" "g" "h" "i" "j" "k" "l" "m" "n" "o" "p" "q" "r" "s" "t" "u" "v" "w" "x" "y" "z"))

  (dolist (elem lc/english-alphabet)
    (when (string-equal elem (downcase elem))
      (add-to-list 'lc/english-alphabet (upcase elem))))

  (setq lc/latex-mathbb-prefix "`")

  (yas-define-snippets
   'latex-mode
   (mapcar
    (lambda (elem)
      (list (concat lc/latex-mathbb-prefix elem) (concat "\\mathbb{" elem "}") (concat "Mathbb letter " elem)))
    lc/english-alphabet))

  (setq lc/latex-math-symbols
        '(("x" . "\\times")
          ("." . "\\cdot")
          ("v" . "\\forall")
          ("s" . "\\sum_{$1}^{$2}$0")
          ("p" . "\\prod_{$1}^{$2}$0")
          ("e" . "\\exists")
          ("i" . "\\int_{$1}^{$2}$0")
          ("c" . "\\cap")
          ("u" . "\\cup")
          ("0" . "\\emptyset")))

  (setq lc/latex-math-prefix "''")

  (yas-define-snippets
   'latex-mode
   (mapcar
    (lambda (elem)
      (let ((key (car elem))
            (value (cdr elem)))
        (list (concat lc/latex-math-prefix key) value (concat "Math symbol " value))))
    lc/latex-math-symbols))
  )

9.13. vterm and friends   prog_vterm

9.13.1. vterm

(use-package vterm
  :if (not lc/is-ipad)
  :general
  (general-imap
    :keymaps 'vterm-mode-map
    "M-l" 'vterm-send-right
    "M-h" 'vterm-send-left)
  :config
  (setq vterm-shell (executable-find "fish")
        vterm-max-scrollback 10000))

9.13.2. vterm toggle

NOTE:

  • You can use universal argument to create a new vterm buffer (SPC U SPC ')
(use-package vterm-toggle
  :if (not lc/is-ipad)
  :general
  (lc/leader-keys
    "'" 'vterm-toggle)
  :init
  (setq vterm-toggle-scope 'project)
  )

9.14. search google   prog_extra

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

9.15. search github   prog_extra

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

9.16. transient help commands   prog_extra

(use-package transient
  :general
  (lc/leader-keys
    "h h" 'lc/help-transient)
  :config
  (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)
     ;;  ]
     ]
    )
  )

9.17. transient increase/decrease font size   prog_extra

(use-package transient
  :general
  (lc/leader-keys
    "t f" 'lc/font-size-transient)
  :config
  (transient-define-prefix lc/font-size-transient ()
    "Change font size"
    ["Font size"
     ("+" "Increase" (lambda () (interactive) (progn (text-scale-increase) (lc/font-size-transient))))
     ("-" "Decrease" (lambda () (interactive) (progn (text-scale-decrease) (lc/font-size-transient))))
     ])
  )

9.18. isearch-mb   prog_extra

You can use consult-history to browse past searches

(use-package isearch-mb
  :straight (isearch-mb :type git :host github :repo "astoff/isearch-mb")
  :demand
  :init
  (setq-default
   ;; Match count next to minibuffer prompt
   isearch-lazy-count t
   ;; Don't be stingy with history; default is to keep just 16 entries
   search-ring-max 200
   regexp-search-ring-max 200
   ;; fuzzy match with space
   isearch-regexp-lax-whitespace t
   search-whitespace-regexp ".*?"
   )
  :config
  (add-to-list 'isearch-mb--with-buffer #'loccur-isearch)
  (define-key isearch-mb-minibuffer-map (kbd "C-o") #'loccur-isearch)
  )

9.19. olivetti mode   extra_focus

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

9.20. darkroom   extra_focus

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

10. Programming languages

10.1. lsp mode and friends   prog_lsp

10.1.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")
    "a" '(lsp-execute-code-action :wk "code action")  
    "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-headerline-breadcrumb-enable nil)
  (setq lsp-diagnostics-provider :none)
  )

10.1.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.1.3. dap-mode

(use-package dap-mode
  :hook
  ((dap-mode . corfu-mode)
   (dap-terminated . lc/hide-debug-windows)
   (dap-session-created . (lambda (_arg) (projectile-save-project-buffers)))
   (dap-ui-repl-mode . (lambda () (setq-local truncate-lines t))))
  :general
  (lc/local-leader-keys
    :states '(normal)
    :keymaps '(python-mode-map dap-ui-repl-mode-map)
    "d d" '(dap-debug :wk "debug")
    "d b" '(dap-breakpoint-toggle :wk "breakpoint toggle")
    "d B" '(dap-ui-breakpoints-list :wk "breakpoint list")
    "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 l" '(dap-debug-last :wk "step in")
    "d q" '(dap-disconnect :wk "quit")
    "d r" '(dap-ui-repl :wk "repl")
    "d h" '(dap-hydra :wk "hydra")
    "d i" '(lc/dap-inspect-df :wk "view df")
    "d I" '(lc/dap-inspect-df2 :wk "view df2")
    ;; "d t" '(lc/dap-dtale-df :wk "dtale df")
    )
  (:keymaps 'dap-ui-repl-mode-map
            "<backtab>" 'dabbrev-completion
            "TAB" 'lc/py-indent-or-complete)
  :init
  ;; (defun lc/dap-dtale-df (dataframe)
  ;;   "Show df in tale in default browser"
  ;;   (interactive (list (read-from-minibuffer "DataFrame: " (evil-find-symbol nil))))
  ;;   (dap-eval (concat "import dtale; dtale.show(" dataframe ", open_browser=True)")))
  (setq lc/dap-temp-dataframe-buffer  "*inspect-df*")
  (setq lc/dap-temp-dataframe-path "~/tmp-inspect-df.csv")
  (defun lc/dap-inspect-df (dataframe)
    "Save the df to csv and open the file with csv-mode"
    (interactive (list (read-from-minibuffer "DataFrame: " (evil-find-symbol nil))))
    (dap-eval (format  "%s.to_csv('%s', index=False)" dataframe lc/dap-temp-dataframe-path))
    (sleep-for 1)
    (find-file-other-window lc/dap-temp-dataframe-path)
    )
  (defun lc/dap-inspect-df2 (dataframe)
    "Save the df to csv and open the file with csv-mode"
    (interactive (list (read-from-minibuffer "DataFrame: " (evil-find-symbol nil))))
    (dap-eval (concat dataframe ".to_csv('~/tmp-inspect-df2.csv', index=False)"))
    (sleep-for 1)
    (with-current-buffer 
        (display-buffer
         (with-current-buffer (find-file-noselect "~/tmp-inspect-df2.csv")
           (rename-buffer "*inspect-df2*"))
         '((;display-buffer-in-side-window
            display-buffer-reuse-window)
           (side . right)
           (window-width . 0.5)
           )))
    )
  ;; prevent minibuffer prompt about reloading from disk
  (setq revert-without-query '("~/tmp-inspect-df.csv"))
  ;; (setq dap-auto-configure-features '(locals repl))
  (setq dap-auto-configure-features '(sessions repl))
  (setq dap-python-debugger 'debugpy)
  ;; show stdout
  (setq dap-auto-show-output t)
  (setq dap-output-window-min-height 10)
  (setq dap-output-window-max-height 200)
  (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))
      ;; delete output buffer
      (when-let (window (get-buffer-window (dap--debug-session-output-buffer (dap--cur-session-or-die))))
        (delete-window window))
      ;; delete dataframe inspector window
      (when-let
          (window (get-buffer-window (get-file-buffer lc/dap-temp-dataframe-path)))
        (delete-window window)))
    )
  (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-sessions*"
           (side . bottom)
           (slot . 1)
           (window-height . 0.33))
          ("*debug-window*"
           (side . bottom)
           (slot . 2)
           (window-height . 0.33))
          ("*dap-ui-repl*"
           (side . bottom)
           (slot . 3)
           (window-height . 0.33))))
  (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 flight-tower-mill (list
                             :name "mill"
                             :type "python"
                             :request "launch"
                             :program (expand-file-name "~/git/Sodra.Common.FlightTower/flight_tower/__main__.py")
                             ;; :env '(("NO_JSON_LOG" . "true"))
                             :args ["-m" "mill" "--config" "user_luca"]))
  (defvar flight-tower-calibration (list
                                    :name "mill"
                                    :type "python"
                                    :request "launch"
                                    :program (expand-file-name "~/git/Sodra.Common.FlightTower/flight_tower/__main__.py")
                                    ;; :env '(("NO_JSON_LOG" . "true"))
                                    :args ["-m" "mill"
                                           ;; "--config" "user_luca"
                                           ;; "--config" "calibration_g292imp_41x185"
                                           ;; "--config" "calibration_41x185_38x89"
                                           "--config" "calibration_jan22"
                                           ]
                                    ))
  (defvar flight-tower-e2e (list
                            :name "mill"
                            :type "python"
                            :request "launch"
                            :program (expand-file-name "~/git/Sodra.Common.FlightTower/flight_tower/__main__.py")
                            ;; :env '(("NO_JSON_LOG" . "true"))
                            :args ["-m" "wood_processing_e2e"
                                   "--config" "user_luca"]
                            ))
  (dap-register-debug-template "dap-debug-script" dap-script-args)
  (dap-register-debug-template "dap-debug-test-at-point" dap-test-args)
  (dap-register-debug-template "flight-tower-mill" flight-tower-mill)
  (dap-register-debug-template "flight-tower-e2e" flight-tower-e2e)
  (dap-register-debug-template "flight-tower-calibration" flight-tower-calibration)
  ;; 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.2. Python   prog_python

10.2.1. python mode

NOTE:

  • In a python buffer use run-python to start an inferior python process or ,'
  • Use eval operator so eval lines with grr
(use-package python-mode
  :hook
  ((envrc-mode . (lambda ()
                   (when (executable-find "ipython")
                     (setq python-shell-interpreter (executable-find "ipython"))))))
  :general
  (lc/local-leader-keys
    :keymaps 'python-mode-map
    "'" 'run-python)
  (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-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.2.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.2.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.2.4. flycheck

flycheck-verify-setup is your best friend.

(use-package flycheck
  :hook ((lsp-mode . flycheck-mode)
         (envrc-mode . (lambda ()
                        (setq flycheck-python-flake8-executable (executable-find "python"))
                        (setq flycheck-checker 'python-flake8)
                        (setq flycheck-flake8rc ".flake8")
                        )))
  :init
  (setq flycheck-indication-mode 'right-fringe)
  ;; only check on save
  (setq flycheck-check-syntax-automatically '(mode-enabled save))
)

10.2.5. blacken

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

10.2.6. csv mode

(use-package csv-mode
  :hook (csv-mode . lc/init-csv-mode)
  :general
  (lc/local-leader-keys
    :keymaps 'csv-mode-map
    :states 'normal
    "a" '(csv-align-fields :wk "align fields")
    "A" '(lc/csv-align-visible :wk "align fields, visible")
    "i"  '(lc/init-csv-mode :wk "init csv mode")
    "u" '(csv-unalign-fields :wk "unalign fields")
    "s" '(csv-sort-fields :wk "sort fields")
    ";" '(lc/set-csv-semicolon-separator :wk "set semicolon sep")
    "," '(lc/reset-csv-separators :wk "set comma sep"))
  :init
  (defun lc/csv-align-visible (&optional arg)
    "Align visible fields"
    (interactive "P")
    (csv-align-fields nil (window-start) (window-end)))
  (defun lc/set-csv-semicolon-separator ()
    (interactive)
    (customize-set-variable 'csv-separators '(";")))
  (defun lc/reset-csv-separators ()
    (interactive)
    (customize-set-variable 'csv-separators lc/default-csv-separators))
  (defun lc/init-csv-mode ()
    (interactive)
    (lc/set-csv-separators)
    (lc/csv-highlight)
    (call-interactively 'csv-align-fields))
  :config
  (require 'cl)
  (require 'color)
  (defun lc/set-csv-separators ()
    (interactive)
    (let* ((n-commas (count-matches "," (point-at-bol) (point-at-eol)))
           (n-semicolons (count-matches ";" (point-at-bol) (point-at-eol))))
      (if ( ; <
           > n-commas n-semicolons)
          (customize-set-variable 'csv-separators '("," " "))   
        (customize-set-variable 'csv-separators '(";" " ")))))
  (defun lc/csv-highlight ()
    (interactive)
    (font-lock-mode 1)
    (let* ((separator (string-to-char (car csv-separators)))
           (n (count-matches (string separator) (point-at-bol) (point-at-eol)))
           (colors (loop for i from 0 to 1.0 by (/ 2.0 n)
                         collect (apply #'color-rgb-to-hex 
                                        (color-hsl-to-rgb i 0.3 0.5)))))
      (loop for i from 2 to n by 2 
            for c in colors
            for r = (format "^\\([^%c\n]+%c\\)\\{%d\\}" separator separator i)
            do (font-lock-add-keywords nil `((,r (1 '(face (:foreground ,c)))))))))
  )

10.3. Jupyter   prog_jupyter

10.3.1. 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"))
  ;; (lc/local-leader-keys
  ;;   :keymaps 'python-mode-map
  ;;   :states 'visual
  ;;   "e" '(jupyter-eval-region :wk "eval"))
  (lc/local-leader-keys
    :keymaps 'jupyter-org-interaction-mode-map
    :states 'normal
    "k i" '(jupyter-org-interrupt-kernel :wk "interrupt")
    "k r" '(jupyter-repl-restart-kernel :wk "restart"))
  :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.3.2. 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

When exporting we can add this to the header:

When exporting .org file to HTML, we can add this header:

#+HTML_HEAD: <link rel="stylesheet" type="text/css" href="https://gongzhitaao.org/orgcss/org.css"/>

We can avoid evaluation of code with:

(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. R   prog_R

10.4.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.4.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.4.3. enable lsp-mode

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

10.5. emacs-lisp   prog_elisp

10.5.1. emacs-lisp-mode

(use-package emacs
  :straight (:type built-in)
  :general
  (general-nmap
    :keymaps 'emacs-lisp-mode-map
    :states 'normal
   "gr" nil) ;; interferes with eval-operator
  )

10.5.2. evil-lisp state

NOTE:

  • Wrap with SPC l w
  • Raise with SPC l r
  • 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.5.3. 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)
    ;;          end))
    ;;       :wk "region")
    "e" '(eros-eval-region :wk "region")
    )
  :init
  (defun eros-eval-region (start end)
    (interactive "r")
    (eros--eval-overlay
     (string-trim
      (with-output-to-string
        (eval-region start end standard-output)))
     (max (point) (mark))))
  )

10.6. Nix   prog_nix

10.6.1. nix mode

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

10.7. Clojure   prog_clojure

10.7.1. Clojure mode

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

10.7.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.7.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.7.4. ob-clojure

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

10.7.5. aggressive-indent

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

10.8. markdown   prog_markdown

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

10.9. yaml mode   prog_markdown

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

10.10. toml mode   prog_markdown

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

11. Gimmicks

11.1. web browser   extra_web

Use SPC x x 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 x 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"))
  ;; :custom
  ;; (setq xwwp-follow-link-completion-system 'ivy)
  ;; :bind (:map xwidget-webkit-mode-map
  ;;             ("v" . xwwp-follow-link))
  )

11.2. elfeed   extra_rss

NOTE:

  • 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))))

12. Provide modules

12.1. init-core

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

12.2. init-ui-extras

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

12.3. init-org-export

(provide 'init-org-export)
;;; init-org-export.el ends here

12.4. init-prog-tree-sitter

(provide 'init-prog-tree-sitter)
;;; init-prog-tree-sitter.el ends here

12.5. init-prog-nix

(provide 'init-prog-nix)
;;; init-prog-nix.el ends here

12.6. init-prog-lsp

(provide 'init-prog-lsp)
;;; init-prog-lsp.el ends here

12.7. init-prog-python

(provide 'init-prog-python)
;;; init-prog-python.el ends here

12.8. init-prog-jupyter

(provide 'init-prog-jupyter)
;;; init-prog-jupyter.el ends here

12.9. init-org-roam

(provide 'init-org-roam)
;;; init-org-roam.el ends here

12.10. init-prog-elisp

(provide 'init-prog-elisp)
;;; init-org-prog-elisp.el ends here

12.11. init-prog-r

(provide 'init-prog-r)
;;; init-org-prog-r.el ends here

12.12. init-prog-clojure

(provide 'init-prog-clojure)
;;; init-org-prog-clojure.el ends here

12.13. init-prog-vterm

(provide 'init-prog-vterm)
;;; init-prog-vterm.el ends here

12.14. init-prog-markdown

(provide 'init-prog-markdown)
;;; init-prog-markdown.el ends here

12.15. init-extra-focus

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

12.16. init-extra-web

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

12.17. init-extra-rss

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

12.18. init-extras

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