All Projects → lccambiaghi → vanilla-emacs

lccambiaghi / vanilla-emacs

Licence: other
No description, website, or topics provided.

Programming Languages

emacs lisp
2029 projects
YASnippet
69 projects

Luca’s literate Emacs config

Introduction

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.

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.

Why literate?

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

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

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

Let’s consider some of the benefits:

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

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.

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.

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:

early-init.el and init.el

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

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

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

Package manager

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)

straight lockfile

We can run M-x straight-freeze-versions to write the file straight/versions/default.el. The content of the file can then be kept in a code block, under version control. The code block can then be tangle again to straight/versions/default.el. We can then restore package versions using M-x straight-thaw-versions.
(("ESS" . "a9e9367976658391126c907b6a5dfc8ad3033ebd")
 ("Emacs-wgrep" . "f9687c28bbc2e84f87a479b6ce04407bb97cfb23")
 ("a.el" . "3d341eb7813ee02b00ab28e11c915295bfd4b5a7")
 ("ace-window" . "c7cb315c14e36fded5ac4096e158497ae974bec9")
 ("aggressive-indent-mode" . "b0ec0047aaae071ad1647159613166a253410a63")
 ("alert" . "7046393272686c7a1a9b3e7f7b1d825d2e5250a6")
 ("all-the-icons-dired" . "fc2dfa1e9eb8bf1c402a675e7089638d702a27a5")
 ("all-the-icons.el" . "6917b08f64dd8487e23769433d6cb9ba11f4152f")
 ("annalist.el" . "134fa3f0fb91a636a1c005c483516d4b64905a6d")
 ("avy" . "e92cb37457b43336b765630dbfbea8ba4be601fa")
 ("blacken" . "784da60033fe3743336d1da0f33239f1bf514266")
 ("bui.el" . "f3a137628e112a91910fd33c0cff0948fa58d470")
 ("centaur-tabs" . "9c7c936e4e1de6f4f4095d70e43c9ae738d05086")
 ("centered-cursor-mode.el" . "4093821cc9759ca5a3c6e527d4cc915fc3a5ad74")
 ("cfrs" . "7c42f2c82c7ae689f3ef291b066688c58ab96298")
 ("cider" . "9c137c52cf5b769fcc52b4e8108acda10638f766")
 ("clojure-mode" . "53ef8ac076ae7811627fbdd408e519ab7fca9a0b")
 ("consult" . "9dcecad2d448e2da0ae9fa17222358176ef58dd3")
 ("csv-mode" . "8da54e8b4ef9e5fe8a0afa147c625ced603dc0aa")
 ("dap-mode" . "88eda699e77e86410cdf3eb6afb790303dd27728")
 ("darkroom" . "27b928a6e91c3207742180f7e209bae754c9c1fe")
 ("dash.el" . "0e975782086020aa12863fdb658d6a3cc748a10c")
 ("diff-hl" . "89aeb2fc8b24b6c4de4394f85041c5dd5fa60dad")
 ("dired-hacks" . "7c0ef09d57a80068a11edc74c3568e5ead5cc15a")
 ("dired-hide-dotfiles" . "964b860a40ac0d585c71760ed2ecdfa6ce7c4184")
 ("dired-single" . "98c2102429fcac6fbfdba9198c126eb1b3dcc4e5")
 ("docker-tramp.el" . "8e2b671eff7a81af43b76d9dfcf94ddaa8333a23")
 ("doom-modeline" . "49816da1a6c05e6215ec3c8aac6c5eabeb47e74c")
 ("el-get" . "d76ac84ae9670de7bf7725b362cafe86688771f9")
 ("eldoc" . "b906386cf04029e01228fde239c3a2e3e5b53603")
 ("elfeed" . "e29c8b91450bd42d90041231f769c4e5fe5070da")
 ("elisp-refs" . "b3634a4567c655a1cda51b217629849cba0ac6a7")
 ("emacs-async" . "14f48de586b0977e3470f053b810d77b07ea427a")
 ("emacs-bind-map" . "bf4181e3a41463684adfffc6c5c305b30480e30f")
 ("emacs-dashboard" . "2b1ef13392be2f07d2a52636edf578b89512d501")
 ("emacs-hide-mode-line" . "88888825b5b27b300683e662fa3be88d954b1cea")
 ("emacs-htmlize" . "49205105898ba8993b5253beec55d8bddd820a70")
 ("emacs-jupyter" . "1f0612eb936d36abab0f27b09cca691e81fc6e74")
 ("emacs-libvterm" . "6f95a1b2949f60539fd92e3a63011801a7e765fd")
 ("emacs-memoize" . "51b075935ca7070f62fae1d69fe0ff7d8fa56fdd")
 ("emacs-python-pytest" . "4a1c4c8915c12e540d41aae1d4e326a2362da541")
 ("emacs-tree-sitter" . "7f5d0938002092ec08830a73f64961021303e1e9")
 ("emacs-undo-fu" . "c0806c1903c5a0e4c69b6615cdc3366470a9b8ca")
 ("emacs-web-server" . "22ce66ea43e0eadb9ec1d691a35d9695fc29cee6")
 ("emacs-websocket" . "34e11124fdd9d73e431499ba8a6b6a8023519664")
 ("emacs-which-key" . "428aedfce0157920814fbb2ae5d00b4aea89df88")
 ("emacs-winum" . "c5455e866e8a5f7eab6a7263e2057aff5f1118b9")
 ("emacs-zmq" . "790033363cf0e78c616cfe117a2f681381e96f29")
 ("emacsmirror-mirror" . "71c9ae8e353f18f5ff6039b2e78f7ffad24fd4e1")
 ("embark" . "c1450983ba272993094e3048eb8b134c162bcb7d")
 ("envrc" . "a7c6ca84a2b0617c94594a23a0c05246f14fa4ee")
 ("epl" . "78ab7a85c08222cd15582a298a364774e3282ce6")
 ("eros" . "dd8910279226259e100dab798b073a52f9b4233a")
 ("ess-view-data" . "283251e8ac19ac0c0f89a4b0f0eb38482167e52b")
 ("evil" . "6316dae58e95fe019fcb41d2d1ca5847227a16e6")
 ("evil-cleverparens" . "8c45879d49bfa6d4e414b6c1df700a4a51cbb869")
 ("evil-collection" . "f53ef08224f709c732740d45b373ef3617f6d759")
 ("evil-goggles" . "08a22058fd6a167f9f1b684c649008caef571459")
 ("evil-iedit-state" . "30fcfa96ceebed0191337c493f5c2efc8ae090ad")
 ("evil-indent-plus" . "0c7501e6efed661242c3a20e0a6c79a6455c2c40")
 ("evil-lisp-state" . "3c65fecd9917a41eaf6460f22187e2323821f3ce")
 ("evil-mc" . "7dfb2ca5ac00c249cb2f55cd6fa91fb2bfb1117e")
 ("evil-nerd-commenter" . "563cdc154b1f29d181b883563dd37be7eafafdee")
 ("evil-org-mode" . "a9706da260c45b98601bcd72b1d2c0a24a017700")
 ("evil-snipe" . "6dcac7f2516c6137a2de532fc2c052f242559ee3")
 ("evil-surround" . "346d4d85fcf1f9517e9c4991c1efe68b4130f93a")
 ("exec-path-from-shell" . "d14d6d2966efe5a1409f84a6b9d998268f74761d")
 ("f.el" . "c4dbf8c8e83df834f5d6f72cd5649b9d8a8812ec")
 ("gcmh" . "0089f9c3a6d4e9a310d0791cf6fa8f35642ecfd9")
 ("general.el" . "a0b17d207badf462311b2eef7c065b884462cb7c")
 ("git-timemachine" . "8d675750e921a047707fcdc36d84f8439b19a907")
 ("git.el" . "a3396a7027a7d986598c6a2d6d5599bac918f3da")
 ("gntp.el" . "767571135e2c0985944017dc59b0be79af222ef5")
 ("gnu-elpa-mirror" . "3f69bd0d6dcc9060acfb4b9bd2456b360eb2fe51")
 ("goto-chg" . "2af612153bc9f5bed135d25abe62f46ddaa9027f")
 ("helpful" . "584ecc887bb92133119f93a6716cdf7af0b51dca")
 ("hexrgb" . "90e5f07f14bdb9966648977965094c75072691d4")
 ("highlight-indent-guides" . "cf352c85cd15dd18aa096ba9d9ab9b7ab493e8f6")
 ("hl-todo" . "9661a462d86b22293caaa4c3d94f971a15dbf1d5")
 ("ht.el" . "c4c1be487d6ecb353d07881526db05d7fc90ea87")
 ("hydra" . "2d553787aca1aceb3e6927e426200e9bb9f056f1")
 ("iedit" . "5c792f5fd44797ece83169b7ef6ac6f4b259255d")
 ("inheritenv" . "13c0135ddd96519ddeb993ee21163d6e11b4f464")
 ("isearch-mb" . "781b5373c06131ca6ab45136cdca3cab022a67af")
 ("log4e" . "7df0c1ff4656f8f993b87064b1567618eadb5546")
 ("lsp-mode" . "8166a1fe04891efcb7f1262609e77fc48500537b")
 ("lsp-pyright" . "71ff088ac4c93b0edd012f305a3dfd1602c5d21e")
 ("lsp-treemacs" . "3bae4a91e05d55d5ca92da272ffcd497f370e9df")
 ("lsp-ui" . "62568188b7cbc0758a0c4bfb57647708406ddf51")
 ("magit" . "25f432551347468ce97b8b03987e59092e91f8f0")
 ("marginalia" . "d38a27867bcec0bafa43e8d1bd3fd96a32b15d31")
 ("markdown-mode" . "377ce39ffe69f058994ac4e98bde8cfb58661406")
 ("melpa" . "7e9db27ec78a96e067b87bb49bbe8e37fc43a3b5")
 ("modus-themes" . "1903cf653055482714c0f4cfa79e53c82fba047c")
 ("nix-mode" . "0023fc5b100ec0c939ffe699d1a7d1afcf1f417a")
 ("no-littering" . "6e8950ad296c0f57d80d034eb0b7adf538c02906")
 ("ob-async" . "de1cd6c93242a4cb8773bbe115b7be3d4dd6b97e")
 ("olivetti" . "b76a020aedb57a6a7d0ae61cde13434f5c802a44")
 ("org-appear" . "19ea96e6e2ce01b8583b25a6e5579f1be207a119")
 ("org-cv" . "2f86bab21d35f76c641b6175d33039422b6ed0db")
 ("org-fragtog" . "0151cabc7aa9f244f82e682b87713b344d780c23")
 ("org-html-themify" . "db0bdeedecb3f311ca39f165abdac809404c6e32")
 ("org-re-reveal" . "d404eb13d9e34354c081870ebdd69711937682b3")
 ("org-reverse-datetree" . "be24274dd62cd3c586cbea99c8f73db251bf319d")
 ("org-superstar-mode" . "7f83636db215bf5a10edbfdf11d12a132864a914")
 ("org-tree-slide" . "d6e8e91433dfe4968f1343b483f2680f45a77d52")
 ("ox-gfm" . "99f93011b069e02b37c9660b8fcb45dab086a07f")
 ("ox-ipynb" . "919b694763035c0ea04a3a368418355185f896b8")
 ("page-break-lines" . "69caea070379f3324c530e96e06625c3cd097cb9")
 ("paredit" . "8330a41e8188fe18d3fa805bb9aa529f015318e8")
 ("parseclj" . "eff941126859bc9e949eae5cd6c2592e731629f2")
 ("parseedn" . "90cfe3df51b96f85e346f336c0a0ee6bf7fee508")
 ("persistent-scratch" . "57221e5fdff22985c0ea2f3e7c282ce823ea5932")
 ("persp-projectile" . "533808b3e4f8f95a1e3ed9c55d9aa720277ebd5f")
 ("perspective-el" . "2f2b59e693f08b8d9c81062fca25e6076b6e7f8d")
 ("pfuture" . "d7926de3ba0105a36cfd00811fd6278aea903eef")
 ("pkg-info" . "76ba7415480687d05a4353b27fea2ae02b8d9d61")
 ("posframe" . "3454a4cb9d218c38f9c5b88798dfb2f7f85ad936")
 ("powerline" . "346de84be53cae3663b4e2512222c279933846d4")
 ("prescient.el" . "42adc802d3ba6c747bed7ea1f6e3ffbbdfc7192d")
 ("projectile" . "c31bd41c0b9d6fba8837ebfd3a31dec0b3cd73c6")
 ("pyimport" . "a6f63cf7ed93f0c0f7c207e6595813966f8852b9")
 ("python-mode" . "41b123b4d4906cce7591900a952bb75a38c5296c")
 ("queue" . "52206c0f78afc0dfb9a287cb928c1e725103336d")
 ("rainbow-delimiters" . "f43d48a24602be3ec899345a3326ed0247b960c6")
 ("restart-emacs" . "1607da2bc657fe05ae01f7fdf26f716eafead02c")
 ("s.el" . "43ba8b563bee3426cead0e6d4ddc09398e1a349d")
 ("selectrum" . "015798542b441d993d53b967d49df3dc0162ca37")
 ("sesman" . "edee869c209c016e5f0c5cbb8abb9f3ccd2d1e05")
 ("shrink-path.el" . "c14882c8599aec79a6e8ef2d06454254bb3e1e41")
 ("shut-up" . "081d6b01e3ba0e60326558e545c4019219e046ce")
 ("smartparens" . "63695c64233d215a92bf08e762f643cdb595bdd9")
 ("spinner" . "61f59fab44d22cd5add61a1baf3f0b88a5d829d7")
 ("straight.el" . "2d407bccd9378f1d5218f8ba2ae85c6be73fbaf1")
 ("templatel" . "a3458234b8e0e83c46c6aca11a757c1134752c09")
 ("toml-mode.el" . "f6c61817b00f9c4a3cab1bae9c309e0fc45cdd06")
 ("transient" . "90e640fe8fa3f309c7cf347501e86ca5cd0bd85e")
 ("transpose-frame" . "12e523d70ff78cc8868097b56120848befab5dbc")
 ("treemacs" . "332d4e0f1f606c472dd083c9cdd4f143ee23020a")
 ("use-package" . "caa92f1d64fc25480551757d854b4b49981dfa6b")
 ("vertico" . "cc23578c9143d2e320c9ed0283ad2e82c37ab65d")
 ("vterm-toggle" . "61cb072af997aa961e2aebe0125b883ff3bd6f43")
 ("weblorg" . "ca7136629fc3f3f1646fa9bd5ad51766062edb39")
 ("with-editor" . "6735180e73e787b79535c245b162249b70dbf841")
 ("xwwp" . "f67e070a6e1b233e60274deb717274b000923231")
 ("yaml-mode" . "fc5e1c58f94472944c4aa838f00f6adcac6fa992")
 ("yasnippet" . "5cbdbf0d2015540c59ed8ee0fcf4788effdf75b6"))
:beta

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

Emacs

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 "[email protected]")

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

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

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

Zoom

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

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

Garbage collector magic hack

Used by DOOM to manage garbage collection
(use-package gcmh
  :demand
  :config
  (gcmh-mode 1))

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

eldoc

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

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

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

server mode

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

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)

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

xref

(use-package xref
  :straight (:type built-in)
  :init
  (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)
  )

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

emacs extra packages

Keybindings

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

evil

evil mode

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

Search tricks:

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

Some interesting vim nouns:

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

Marks:

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

Macros:

qq
record macro q
@q
execute macro q

Registers:

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

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

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

evil-collection

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

eval operator

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

For example:

  • grab evals the form at point
  • grad evals the top-level form (e.g. use-package blocks or functions)
  • grak evals the function in python
  • grr evals the line
(use-package evil
  :config
  (defcustom evil-extra-operator-eval-modes-alist
    '((emacs-lisp-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)
  
  )

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

evil-snipe

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

evil-nerd-commenter

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

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

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

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

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

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

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

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

Org

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")
    "o t" '((lambda () (interactive)
              (persp-switch "main")
              (find-file (concat org-directory "/personal/todo.org")))
            :wk "open todos"))
  (lc/local-leader-keys
    :keymaps 'org-mode-map
    "a" '(org-archive-subtree :wk "archive subtree")
    "E" '(org-export-dispatch :wk "export")
    "i" '(org-insert-structure-template :wk "insert src")
    "l" '(:ignore true :wk "link")
    "l l" '(org-insert-link :wk "insert link")
    "l s" '(org-store-link :wk "store link")
    "L" '((lambda () (interactive) (org-latex-preview)) :wk "latex preview")
    ;; "L" '((lambda () (interactive) (org--latex-preview-region (point-min) (point-max))) :wk "latex")
    "r" '(org-refile :wk "refile")
    "n" '(org-toggle-narrow-to-subtree :wk "narrow subtree")
    "p" '(org-priority :wk "priority")
    "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"))
  (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.5)
  )

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

Hack to get org-version to work

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

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

  ;; (provide 'org-version)
	
	;; (use-package git)
  )

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

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

async tangle

Taken from https://github.com/KaratasFurkan/.emacs.d
(use-package org
	:config
	(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")
    )

  )
)

org reverse datetree

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

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

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

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

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

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

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

  )

exporters

org-html-themify

I use this package to export my config to HTML. I then push it to the gh-pages branch
(use-package org-html-themify
	:after modus-themes
  :straight
  (org-html-themify
   :type git
   :host github
   :repo "DogLooksGood/org-html-themify"
   :files ("*.el" "*.js" "*.css"))
  :hook (org-mode . org-html-themify-mode)
  :config
  ;; otherwise it complains about invalid face
  (require 'hl-line)
  )

ox-gfm

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

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

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

weblorg

(use-package weblorg)

(use-package templatel)

(use-package htmlize)

ox-cv

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

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

automatic latex preview

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

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

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

org-jupyter-mode

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

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
(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
  (general-nmap
    "SPC n b" 'org-roam-buffer-toggle
    "SPC n f" 'org-roam-node-find
    "SPC n g" 'org-roam-graph
    "SPC n i" 'org-roam-node-insert
    "SPC n c" 'org-roam-capture
    "SPC n t" 'org-roam-tag-add
    "SPC n r" 'org-roam-ref-add
    "SPC n a" 'org-roam-alias-add
    ;; Dailies
    "SPC n j" 'org-roam-dailies-capture-today
    "SPC n J" 'org-roam-dailies-goto-today
    )
  :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))))
  
  )

UI

all the icons

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

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

Modus themes + alternate light/dark themes

I use a particular emacs build which has additional feature for macOS: https://github.com/railwaycat/homebrew-emacsmacport This defines a hook that is run when macOS system theme changes from light to dark. We use this hook to switch from light to dark theme.
(use-package modus-themes
  :straight (modus-themes :type git :host gitlab :repo "protesilaos/modus-themes" :branch "main")
  :demand
  :if (display-graphic-p)
  :hook (modus-themes-after-load-theme . lc/fix-fill-column-indicator)
  :general
  (lc/leader-keys
    "t t" '((lambda () (interactive) (modus-themes-toggle)) :wk "toggle theme"))
  :init
  (setq modus-themes-slanted-constructs t
        ;; modus-themes-no-mixed-fonts t
        modus-themes-bold-constructs t
        modus-themes-fringes 'nil ; {nil,'subtle,'intense}
        modus-themes-mode-line '3d ; {nil,'3d,'moody}
        modus-themes-intense-hl-line nil
        modus-themes-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 'dark)
    ;; (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)
    (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-dark-theme)))))
    (defun lc/change-theme-with-timers ()
      (run-at-time "00:00" (* 60 60 24) 'lc/load-dark-theme)
      (run-at-time "08:00" (* 60 60 24) 'lc/load-light-theme)
      (run-at-time "18:00" (* 60 60 24) 'lc/load-dark-theme))
    (defun lc/fix-fill-column-indicator ()
      (when (display-graphic-p)
        (modus-themes-with-colors
          (custom-set-faces
           `(fill-column-indicator ((,class :background ,bg-inactive :foreground ,bg-inactive)))))))
    :config
    (when (display-graphic-p)
      (lc/override-colors))
    (if (and (boundp 'mac-effective-appearance-change-hook)
             (plist-get (mac-application-state) :appearance))
        (progn
          (add-hook 'after-init-hook 'lc/change-theme-with-mac-system)
          (add-hook 'mac-effective-appearance-change-hook 'lc/change-theme-with-mac-system))
      ;; (add-hook 'after-init-hook 'lc/change-theme-with-timers)
      ;; (add-hook 'emacs-startup-hook 'lc/load-light-theme)
      (add-hook 'emacs-startup-hook 'lc/change-theme-with-timers)
      )
    )

dashboard

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

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

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

centered cursor mode

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

hide mode line

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

winum

(use-package winum
:general
(lc/leader-keys
"1" '(winum-select-window-1 :wk "win 1")
"2" '(winum-select-window-2 :wk "win 2")
"3" '(winum-select-window-3 :wk "win 3")
"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))

transpose frame

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

Fill column indicator

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

Highlight indentation guides

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

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

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

Completion framework

marginalia

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

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
  :general
  (general-nmap "C-l" 'embark-act)
  (selectrum-minibuffer-map
   "C-l" #'embark-act
   )
  (:keymaps 'embark-file-map "o" 'find-file-other-window)	
  :init
  ;; Optionally replace the key help with a completing-read interface
  (setq prefix-help-command #'embark-prefix-help-command)
  :config
  ;; For Selectrum users:
  ;; 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))))
  ;; No unnecessary computation delay after injection.
  (add-hook 'embark-setup-hook 'selectrum-set-selected-candidate)
  )

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

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

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

vertico

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

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

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

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'
  ;; :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
  )

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
  :config
  (add-to-list 'corfu-margin-formatters #'kind-icon-margin-formatter))

Programming

project

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

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

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

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

dired

(use-package dired
  :straight (:type built-in)
  :hook
  (dired-mode . dired-hide-details-mode)
  :general
  (lc/leader-keys
    "f d" 'dired
    "f j" 'dired-jump)
  (general-nmap
    :keymaps 'dired-mode-map
    :states 'normal
    "F" '((lambda () (interactive)
            (let ((fn (dired-get-file-for-visit)))
              (start-process "open-directory" nil "open" "-R" fn)))
          :wk "open finder")
    "X" '((lambda () (interactive)
            (let ((fn (dired-get-file-for-visit)))
              (start-process "open-external" nil "open" fn)))
          :wk "open external"))
  :init
  (setq dired-omit-files "^\\.[^.]\\|$Rhistory\\|$RData\\|__pycache__")
  (setq dired-listing-switches "-lah")
	(setq ls-lisp-dirs-first t)
	(setq ls-lisp-use-insert-directory-program nil)
  (setq dired-dwim-target t)
  (defun my/dired-open-externally ()
    "Open marked dired file/folder(s) (or file/folder(s) at point if no marks)
  with external application"
    (interactive)
    (let ((files (dired-get-marked-files)))
      (dired-run-shell-command
       (dired-shell-stuff-it "xdg-open" files t))))
  )

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

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

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

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

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

rainbow parenthesis

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

restart-emacs

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

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

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)

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

git

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

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

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

smerge + hydra-smerge

(use-package hydra
  :demand)

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

emacs tree-sitter

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

(use-package tree-sitter-langs)

envrc

Running direnv is expensive so I only do it when it is necessary. I need it in two situations:
  • python-mode
  • ob-jupyter

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

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

snippets

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

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

vterm and friends

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

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

search google

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

search github

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

transient help commands

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

transient increase/decrease font size

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

isearch-mb

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

olivetti mode

(use-package olivetti
  :general
  (lc/leader-keys
    "t o" '(olivetti-mode :wk "olivetti"))
  :init
  (setq olivetti-body-width 100)
  (setq olivetti-recall-visual-line-mode-entry-state t))

darkroom

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

Programming languages

lsp mode and friends

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

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

dap-mode

(use-package dap-mode
  :hook
  ((dap-terminated . lc/hide-debug-windows)
   (dap-ui-repl-mode . (lambda () (setq-local truncate-lines t))))
  :general
  (lc/local-leader-keys
    :keymaps 'python-mode-map
    "d d" '(dap-debug :wk "debug")
    "d b" '(dap-breakpoint-toggle :wk "breakpoint")
    "d c" '(dap-continue :wk "continue")
    "d n" '(dap-next :wk "next")
    "d e" '(dap-eval-thing-at-point :wk "eval")
    "d i" '(dap-step-in :wk "step in")
    "d q" '(dap-disconnect :wk "quit")
    "d r" '(dap-ui-repl :wk "repl")
    "d h" '(dap-hydra :wk "hydra")
    "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
            "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)")))
  (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 (concat dataframe ".to_csv('~/tmp-inspect-df.csv', index=False)"))
    (sleep-for 1)
    (with-current-buffer 
        (display-buffer
         (with-current-buffer (find-file-noselect "~/tmp-inspect-df.csv")
           (rename-buffer "*inspect-df*"))
         '((;display-buffer-in-side-window
            display-buffer-reuse-window)
           (side . right)
           (window-width . 0.5)
           )))
    )
	(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)
           )))
    )
  ;; (add-to-list 'display-buffer-alist
  ;;              '(("*inspect-df*"
  ;;                 (display-buffer-reuse-window display-buffer-in-side-window)
  ;;                 (side . bottom)
  ;;                 (reusable-frames . visible)
  ;;                 (window-height . 0.33)
  ;;                 )))
  ;; (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."
		;; (when (get-buffer "*inspect-df*")
		;; 	(delete-window (get-buffer-window  "*inspect-df*")))
    (unless (-filter 'dap--session-running (dap--get-sessions))
      (kill-buffer (dap--debug-session-output-buffer (dap--cur-session-or-die))))
    )
  (defun lc/dap-python--executable-find (orig-fun &rest args)
    (executable-find "python"))
  :config
  ;; configure windows
  (require 'dap-ui)
  (setq dap-ui-buffer-configurations
        '(("*dap-ui-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 empties-forecast (list
                            :name "empties forecast"
                            :type "python"
                            :request "launch"
                            :program "./src/empties/forecasting/predict.py"
                            :env '(("NO_JSON_LOG" . "true"))
                            :args ["--source01" "./data/empties-history-sample.parquet"
                                   "--source02" "./data/model_selection.files"
                                   "--source03" "./data/booking-feature-sample.parquet"
                                   "--source04" "./data/holiday-2019-05-24-1558683595"
                                   "--output-data" "./data/predictions.parquet"
                                   "--output-metrics" "./data/metrics.json"]
                            ))
  (dap-register-debug-template "dap-debug-script" dap-script-args)
  (dap-register-debug-template "dap-debug-test-at-point" dap-test-args)
  ;; bind the templates
  (lc/local-leader-keys
    :keymaps 'python-mode-map
    "d t" '((lambda () (interactive) (dap-debug dap-test-args)) :wk "test")
    "d s" '((lambda () (interactive) (dap-debug dap-script-args)) :wk "script")
    )
  )

Python

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

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

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

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

blacken

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

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

Jupyter

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

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 we can add this to the header:

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

R

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

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

enable lsp-mode

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

emacs-lisp

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
	)

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

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

Nix

nix mode

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

Clojure

Clojure mode

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

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

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

ob-clojure

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

aggressive-indent

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

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

yaml mode

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

toml mode

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

Gimmicks

web browser

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

elfeed

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

Provide modules

init-core

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

init-ui-extras

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

init-prog-tree-sitter

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

init-prog-nix

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

init-prog-lsp

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

init-prog-python

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

init-prog-jupyter

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

init-org-roam

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

init-prog-elisp

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

init-prog-r

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

init-prog-clojure

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

init-prog-vterm

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

init-prog-markdown

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

init-extra-focus

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

init-extra-web

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

init-extra-rss

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

init-extras

(provide 'init-extra)
;;; init-extra.el ends here
Note that the project description data, including the texts, logos, images, and/or trademarks, for each open source project belongs to its rightful owner. If you wish to add or remove any projects, please contact us at [email protected].