#+title: GNU Emacs configuration #+author: Vedang Manerikar #+email: vedang@unravel.tech #+language: en #+options: ':t toc:nil num:t author:t email:t This configuration is inspired from the work of [[https://github.com/protesilaos/dotfiles/blob/master/emacs/.emacs.d/prot-emacs.org][my hero Prot]]. I've copied straight from his config file, because I think the explanations he has created are worthwhile and should be read by everyone. Prot's files are all prefixed with =prot-emacs-*=. I have changed this to =unravel-*=. The reason for this is that I have picked up only what I need and changed it where needed. As such, any issues you face with this configuration are likely introduced by me. Any quote without attribution is directly taken from [[https://github.com/protesilaos/dotfiles/blob/master/emacs/.emacs.d/prot-emacs.org][Prot's org file]]. You should read through it if you want detailed explanations of things you find here. #+begin_quote What you are now reading is not a common literate configuration of Emacs. In most such cases, you have a generic =init.el= with a call to the ~org-babel-load-file~ function that gets an Org document as its value. That method works but is very slow, because we have to load Org before starting Emacs (and Org loads a bunch of other things we do not need at such an early stage). Whereas this Org document serves as (i) a single point of entry to my Emacs setup and (ii) the origin of all of my Emacs configurations. While I am defining everything in a single Org file, I am not actually starting Emacs by reading this file. Rather, I am instructing Org to put the code blocks defined herein in standalone files, organised by scope. The end result is something where you cannot tell whether a literate program was executed or not. This is the beauty of it. I can keep editing a single file as the "source of truth", though I can still handle each of the files individually (e.g. someone wants to see how I do a specific thing, so I share only that file as an email attachment---no need to send over this massive document). When I want to modify my Emacs setup, I edit this file and then evaluate the following code block or do =C-c C-v C-t=. All files will be updated accordingly. #+end_quote #+src emacs-lisp :tangle no :results none (org-babel-tangle) #+end_src #+toc: headlines 2 Here is what the generated directory structure should look like: #+begin_src sh :dir ~/src/prototypes/emacs-up :results raw fd -e el -e org -E elpa | tree --fromfile #+end_src #+RESULTS: . ├── early-init.el ├── init.el ├── unravel-emacs.org └── unravel-modules ├── unravel-completion.el ├── unravel-essentials.el ├── unravel-langs.el └── unravel-theme.el 2 directories, 7 files To make a change to this Emacs configuration, edit this file and then type =C-c C-v C-t= (=M-x org-babel-tangle=) to republish all the relevant files. * The =early-init.el= file #+begin_quote This is the first file that Emacs reads when starting up. It should contain code that does not depend on any package or the proportions of the Emacs frame. In general, this early initialisation file is meant to set up a few basic things before Emacs produces the initial frame by delegating to the =init.el #+end_quote ** The =early-init.el= basic frame settings :PROPERTIES: :CUSTOM_ID: h:a1288a07-93f6-4e14-894e-707d5ad8b6dc :END: #+begin_src emacs-lisp :tangle "early-init.el" ;;;; No GUI ;; I do not use those graphical elements by default, but I do enable ;; them from time-to-time for testing purposes or to demonstrate ;; something. NEVER tell a beginner to disable any of these. They ;; are helpful. (menu-bar-mode -1) (scroll-bar-mode -1) (tool-bar-mode -1) #+end_src ** The =early-init.el= tweaks to startup time and garbage collection :PROPERTIES: :CUSTOM_ID: h:50d28f3c-3ada-4db5-b830-bbbbee7fec4e :END: #+begin_src emacs-lisp :tangle "early-init.el" ;; A big contributor to startup times is garbage collection. ;; We up the gc threshold to temporarily prevent it from running, then ;; reset it later by enabling `gcmh-mode'. Not resetting it will cause ;; stuttering/freezes. (setq gc-cons-threshold most-positive-fixnum) #+end_src ** If you have both .el and .elc files, load the newer one :PROPERTIES: :CUSTOM_ID: h:F8987E20-3E36-4E27-9EAE-D0680303A95B :END: #+begin_src emacs-lisp :tangle "early-init.el" ;; When both .el and .elc / .eln files are available, ;; load the latest one. (setq load-prefer-newer t) #+end_src ** The =early-init.el= initialises the package cache :PROPERTIES: :CUSTOM_ID: h:7a037504-8a2f-4df0-8482-ce6476354440 :END: #+begin_src emacs-lisp :tangle "early-init.el" ;; Ensure that `describe-package' does not require a ;; `package-refresh-contents'. (setq package-enable-at-startup t) #+end_src ** The =early-init.el= gives a name to the default frame :PROPERTIES: :CUSTOM_ID: h:ad227f7e-b0a7-43f8-91d6-b50db82da9ad :END: Naming frames allows you to select them using completion (=M-x select-frame-by-name=). #+begin_src emacs-lisp :tangle "early-init.el" ;; Name the default frame ;; You can select a frame with M-x select-frame-by-name (add-hook 'after-init-hook (lambda () (set-frame-name "unravel"))) #+end_src * The =init.el= file :PROPERTIES: :CUSTOM_ID: h:dae63bd9-93a8-41c4-af1b-d0f39ba50974 :END: This is the main initialisation file of Emacs. Everything loads from here, even if it has been split into multiple files for convenience. ** The =init.el= tweaks to make native compilation silent :PROPERTIES: :CUSTOM_ID: h:3563ceb5-b70c-4191-9c81-f2f5a202c4da :END: These warnings are unnecessarily scary. #+begin_quote The =--with-native-compilation=yes= build option of Emacs is very nice: it enables the "native compilation" of Emacs Lisp, translating it down to machine code. However, the default setting for reporting errors is set to a verbose value which, in my coaching experience, confuses users: it produces warnings for compilation issues that only the developer of the given package needs to deal with. These include innocuous facts like docstrings being wider than a certain character count. To make things even worse, the buffer that shows these warnings uses the stop sign character, resulting in a long list of lines with red spots everywhere, as if we have totally broken Emacs. #+end_quote #+begin_src emacs-lisp :tangle "init.el" ;; Make native compilation silent and prune its cache. (when (native-comp-available-p) (setq native-comp-async-report-warnings-errors 'silent) ; Emacs 28 with native compilation (setq native-compile-prune-cache t)) ; Emacs 29 #+end_src ** The =init.el= setting to send ~custom-file~ to oblivion :PROPERTIES: :CUSTOM_ID: h:f2ffe0e9-a58d-4bba-9831-cc35940ea83f :END: There is no need to use the =M-x customize= infrastructure. It's easier to just rely on the init file instead. #+begin_quote I would prefer to just have an option to avoid the Custom infrastructure altogether, but this is not possible. So here we are... #+end_quote #+begin_src emacs-lisp :tangle "init.el" ;; Disable custom.el by making it disposable. (setq custom-file (make-temp-file "emacs-custom-")) #+end_src ** The =init.el= settings to enable commands disabled by default :PROPERTIES: :CUSTOM_ID: h:4ed6593f-6f55-4258-a1c2-ddb50e9e2465 :END: These commands are actually useful, especially in org-mode. #+begin_src emacs-lisp :tangle "init.el" ;; Enable these commands which have been disabled by default (mapc (lambda (command) (put command 'disabled nil)) '(list-timers narrow-to-region narrow-to-page upcase-region downcase-region)) #+end_src ** The =init.el= settings to disable unnecessary commands enabled by default These commands are "unsafe", in that we should be using the alternatives (like ~vterm~ and ~org~) #+begin_src emacs-lisp :tangle "init.el" ;; Disable these commands which have been enabled by default (mapc (lambda (command) (put command 'disabled t)) '(eshell project-eshell overwrite-mode iconify-frame diary)) #+end_src ** Add the modules folder to the load-path :PROPERTIES: :CUSTOM_ID: h:e289a614-4f17-4d6c-a028-42fe45aebe66 :END: This is where all the custom configuration sits for all the packages we use. We write configuration on a per-file basis instead of in a giant file, because these smaller files are more readable, approachable and shareable. #+begin_src emacs-lisp :tangle "init.el" (mapc (lambda (string) (add-to-list 'load-path (locate-user-emacs-file string))) '("unravel-modules" "custom-lisp")) #+end_src ** The =init.el= settings for packages (=package.el=) :PROPERTIES: :CUSTOM_ID: h:424340cc-f3d7-4083-93c9-d852d40dfd40 :END: #+begin_quote The =package.el= is built into Emacs and is perfectly fine for my use-case. We do not need to load it explicitly, as it will be called by ~use-package~ when it needs it. Since the introduction of the =early-init.el= file, we also do not need to initialise the packages at this point: we activate the cache instead ([[#h:7a037504-8a2f-4df0-8482-ce6476354440][The =early-init.el= initialises the package cache]]). With regard to the settings here, make sure to read my article about package archives, pinning packages, and setting priorities: <https://protesilaos.com/codelog/2022-05-13-emacs-elpa-devel/>. #+end_quote #+begin_src emacs-lisp :tangle "init.el" ;;;; Packages (setq package-vc-register-as-project nil) ; Emacs 30 (add-hook 'package-menu-mode-hook #'hl-line-mode) ;; Also read: <https://protesilaos.com/codelog/2022-05-13-emacs-elpa-devel/> (setq package-archives '(("gnu-elpa" . "https://elpa.gnu.org/packages/") ("gnu-elpa-devel" . "https://elpa.gnu.org/devel/") ("nongnu" . "https://elpa.nongnu.org/nongnu/") ("melpa" . "https://melpa.org/packages/"))) ;; Highest number gets priority (what is not mentioned has priority 0) (setq package-archive-priorities '(("gnu-elpa" . 3) ("melpa" . 2) ("nongnu" . 1))) ;; Let `package-install' suggest upgrades for built-in packages too. (setq package-install-upgrade-built-in t) #+end_src ** The =init.el= macro to do nothing with Elisp code (~prot-emacs-comment~) :PROPERTIES: :CUSTOM_ID: h:3b14faa6-83fd-4d5f-b3bc-85f72fd572d4 :END: #+begin_quote This is something I learnt while studying Clojure: a ~comment~ macro that wraps some code, effectively commenting it out, while keeping indentation and syntax highlighting intact. What I have here is technically not commenting out the code, because the expansion of the macro is nil, not the actual code with comments around it. #+end_quote #+begin_example emacs-lisp (defmacro prot-emacs-comment (&rest body) "Do nothing with BODY and return nil, with no side effects." (declare (indent defun)) nil) #+end_example #+begin_quote The above is an example. What I actually use is the following. It behaves the same as above, except when it reads a plist of the form =(:eval t)=. The idea is for me to quickly activate something I want to test by passing that to the macro. So here we have it: #+end_quote #+begin_src emacs-lisp :tangle "init.el" (defmacro prot-emacs-comment (&rest body) "Determine what to do with BODY. If BODY contains an unquoted plist of the form (:eval t) then return BODY inside a `progn'. Otherwise, do nothing with BODY and return nil, with no side effects." (declare (indent defun)) (let ((eval)) (dolist (element body) (when-let* (((plistp element)) (key (car element)) ((eq key :eval)) (val (cadr element))) (setq eval val body (delq element body)))) (when eval `(progn ,@body)))) #+end_src ** The =init.el= macro to define abbreviations (~prot-emacs-abbrev~) :PROPERTIES: :CUSTOM_ID: h:e7a12825-7848-42bd-b99b-b87903012814 :END: [ Watch Prot's video: [[https://protesilaos.com/codelog/2024-02-03-emacs-abbrev-mode/][abbreviations with abbrev-mode (quick text expansion)]] (2024-02-03). ] #+begin_src emacs-lisp :tangle "init.el" (defmacro prot-emacs-abbrev (table &rest definitions) "Expand abbrev DEFINITIONS for the given TABLE. DEFINITIONS is a sequence of (i) string pairs mapping the abbreviation to its expansion or (ii) a string and symbol pair making an abbreviation to a function." (declare (indent 1)) (unless (zerop (% (length definitions) 2)) (error "Uneven number of key+command pairs")) `(if (abbrev-table-p ,table) (progn ,@(mapcar (lambda (pair) (let ((abbrev (nth 0 pair)) (expansion (nth 1 pair))) (if (stringp expansion) `(define-abbrev ,table ,abbrev ,expansion) `(define-abbrev ,table ,abbrev "" ,expansion)))) (seq-split definitions 2))) (error "%s is not an abbrev table" ,table))) #+end_src ** The =init.el= final part to load the individual modules :PROPERTIES: :CUSTOM_ID: h:e6c4acf5-5b51-4b38-a86a-bf3f698ac872 :END: Now we are ready to load our per-module configuration files: #+begin_src emacs-lisp :tangle "init.el" (require 'unravel-theme) (require 'unravel-essentials) ;; (require 'unravel-modeline) (require 'unravel-completion) ;; (require 'unravel-search) ;; (require 'unravel-dired) ;; (require 'unravel-window) (require 'unravel-git) (require 'unravel-org) (require 'unravel-langs) #+end_src * The =unravel-theme.el= module :PROPERTIES: :CUSTOM_ID: h:8cf67c82-1ebb-4be8-b0e7-161bbf5419ce :END: This module defines everything related to the aesthetics of Emacs. #+begin_src emacs-lisp :tangle "unravel-modules/unravel-theme.el" :mkdirp yes ;;; Everything related to the look of Emacs #+end_src ** The =unravel-theme.el= section for cool, modern themes (~ef-themes~) :PROPERTIES: :CUSTOM_ID: h:2b2a27a1-6d2e-4b59-bf60-94682e173f2f :END: I use themes from the ~ef-themes~ package exclusively. Prot is the lead developer and maintainer of this package. + Package name (GNU ELPA): ~ef-themes~ + Official manual: <https://protesilaos.com/emacs/ef-themes> + Change log: <https://protesilaos.com/emacs/ef-themes-changelog> + Git repositories: - GitHub: <https://github.com/protesilaos/ef-themes> - GitLab: <https://gitlab.com/protesilaos/ef-themes> #+begin_src emacs-lisp :tangle "unravel-modules/unravel-theme.el" ;;; The Ef (εὖ) themes ;; The themes are customisable. Read the manual: ;; <https://protesilaos.com/emacs/ef-themes>. (use-package ef-themes :ensure t :demand t :bind (("<f5>" . ef-themes-rotate) ("C-<f5>" . ef-themes-select)) :config (setq ef-themes-to-toggle '(ef-elea-light ef-elea-dark) ef-themes-variable-pitch-ui t ef-themes-mixed-fonts t ef-themes-headings ; read the manual's entry of the doc string '((0 . (variable-pitch light 1.9)) (1 . (variable-pitch light 1.8)) (2 . (variable-pitch regular 1.7)) (3 . (variable-pitch regular 1.6)) (4 . (variable-pitch regular 1.5)) (5 . (variable-pitch 1.4)) ; absence of weight means `bold' (6 . (variable-pitch 1.3)) (7 . (variable-pitch 1.2)) (agenda-date . (semilight 1.5)) (agenda-structure . (variable-pitch light 1.9)) (t . (variable-pitch 1.1)))) (ef-themes-select 'ef-elea-light)) #+end_src ** The =unravel-theme.el= section for ~lin~ :PROPERTIES: :CUSTOM_ID: h:bf5b4d08-8f33-4a8c-8ecd-fca19bf2497a :END: ~lin~ is an improvement on ~hl-line-mode~. Prot is the lead developer and maintainer of this package. + Package name (GNU ELPA): ~lin~ + Official manual: <https://protesilaos.com/emacs/lin> + Change log: <https://protesilaos.com/emacs/lin-changelog> + Git repositories: - GitHub: <https://github.com/protesilaos/lin> - GitLab: <https://gitlab.com/protesilaos/lin> #+begin_src emacs-lisp :tangle "unravel-modules/unravel-theme.el" ;;;; Lin ;; Read the lin manual: <https://protesilaos.com/emacs/lin>. (use-package lin :ensure t :hook (after-init . lin-global-mode) ; applies to all `lin-mode-hooks' :config (setopt lin-face 'lin-cyan)) #+end_src ** The =unravel-theme.el= section for ~spacious-padding~ :PROPERTIES: :CUSTOM_ID: h:6c118185-fcb1-4c9a-93af-71814cb84279 :END: ~spacious-padding~ gives us a comfortable reading experience. Prot is the lead developer and maintainer of this package. #+begin_quote Inspiration for this package comes from [[https://github.com/rougier][Nicolas Rougier's impressive designs]] and [[https://github.com/minad/org-modern][Daniel Mendler's ~org-modern~ package]]. #+end_quote + Package name (GNU ELPA): ~spacious-padding~ + Official manual: <https://protesilaos.com/emacs/spacious-padding> + Git repositories: - GitHub: <https://github.com/protesilaos/spacious-padding> - GitLab: <https://gitlab.com/protesilaos/spacious-padding> #+begin_src emacs-lisp :tangle "unravel-modules/unravel-theme.el" ;;;; Increase padding of windows/frames ;; <https://protesilaos.com/codelog/2023-06-03-emacs-spacious-padding/>. (use-package spacious-padding :ensure t :if (display-graphic-p) :hook (after-init . spacious-padding-mode) :init ;; These are the defaults, but I keep it here for visiibility. (setq spacious-padding-widths '(:internal-border-width 30 :header-line-width 4 :mode-line-width 6 :tab-width 4 :right-divider-width 30 :scroll-bar-width 8 :left-fringe-width 20 :right-fringe-width 20)) ;; Read the doc string of `spacious-padding-subtle-mode-line' as ;; it is very flexible. (setq spacious-padding-subtle-mode-line t)) #+end_src ** The =unravel-theme.el= section for ~rainbow-mode~ :PROPERTIES: :CUSTOM_ID: h:9438236e-a8a4-45e0-8c61-8268c634d50b :END: #+begin_quote This package produces an in-buffer preview of a colour value. I use those while developing my themes, hence the ~prot/rainbow-mode-in-themes~ to activate ~rainbow-mode~ if I am editing a theme file. #+end_quote #+begin_src emacs-lisp :tangle "unravel-modules/unravel-theme.el" ;;;; Rainbow mode for colour previewing (rainbow-mode.el) (use-package rainbow-mode :ensure t :init (setq rainbow-ansi-colors nil) (setq rainbow-x-colors nil) (defun prot/rainbow-mode-in-themes () (when-let* ((file (buffer-file-name)) ((derived-mode-p 'emacs-lisp-mode)) ((string-match-p "-theme" file))) (rainbow-mode 1))) :bind ( :map ctl-x-x-map ("c" . rainbow-mode)) ; C-x x c :hook (emacs-lisp-mode . prot/rainbow-mode-in-themes)) #+end_src ** The =unravel-theme.el= section for ~cursory~ :PROPERTIES: :CUSTOM_ID: h:34ce98fe-0b57-44d9-b5f3-0224632114a5 :END: #+begin_quote My ~cursory~ package provides a thin wrapper around built-in variables that affect the style of the Emacs cursor on graphical terminals. The intent is to allow the user to define preset configurations such as "block with slow blinking" or "bar with fast blinking" and set them on demand. The use-case for such presets is to adapt to evolving interface requirements and concomitant levels of expected comfort, such as in the difference between writing and reading. #+end_quote Prot is the lead developer and maintainer. + Package name (GNU ELPA): ~cursory~ + Official manual: <https://protesilaos.com/emacs/cursory> + Change log: <https://protesilaos.com/emacs/cursory-changelog> + Git repositories: - GitHub: <https://github.com/protesilaos/cursory> - GitLab: <https://gitlab.com/protesilaos/cursory> #+begin_src emacs-lisp :tangle "unravel-modules/unravel-theme.el" ;;; Cursor appearance (cursory) ;; Read the manual: <https://protesilaos.com/emacs/cursory>. (use-package cursory :ensure t :demand t :if (display-graphic-p) :config (setq cursory-presets '((box :blink-cursor-interval 1.2) (box-no-blink :blink-cursor-mode -1) (bar :cursor-type (bar . 2) :blink-cursor-interval 0.8) (bar-no-other-window :inherit bar :cursor-in-non-selected-windows nil) (bar-no-blink :cursor-type (bar . 2) :blink-cursor-mode -1) (underscore :cursor-type (hbar . 3) :blink-cursor-interval 0.3 :blink-cursor-blinks 50) (underscore-no-other-window :inherit underscore :cursor-in-non-selected-windows nil) (underscore-thick :cursor-type (hbar . 8) :blink-cursor-interval 0.3 :blink-cursor-blinks 50 :cursor-in-non-selected-windows (hbar . 3)) (underscore-thick-no-blink :blink-cursor-mode -1 :cursor-type (hbar . 8) :cursor-in-non-selected-windows (hbar . 3)) (t ; the default values :cursor-type box :cursor-in-non-selected-windows hollow :blink-cursor-mode 1 :blink-cursor-blinks 10 :blink-cursor-interval 0.2 :blink-cursor-delay 0.2))) ;; I am using the default values of `cursory-latest-state-file'. ;; Set last preset or fall back to desired style from `cursory-presets'. (cursory-set-preset (or (cursory-restore-latest-preset) 'box)) (cursory-mode 1)) #+end_src ** The =unravel-theme.el= section for ~theme-buffet~ :PROPERTIES: :CUSTOM_ID: h:2af10314-c8c2-4946-bf9c-a5b0f5fe881b :END: The ~theme-buffet~ package automatically changes the theme based on time of day. Bruno Boal is the lead developer and Prot is a co-maintainer. + Package name (GNU ELPA): ~theme-buffet~ + Git repo on SourceHut: <https://git.sr.ht/~bboal/theme-buffet> - Mirrors: + GitHub: <https://github.com/BBoal/theme-buffet> + Codeberg: <https://codeberg.org/BBoal/theme-buffet> + Mailing list: <https://lists.sr.ht/~bboal/general-issues> #+begin_src emacs-lisp :tangle "unravel-modules/unravel-theme.el" ;;;; Theme buffet ;; <https://git.sr.ht/~bboal/theme-buffet> (use-package theme-buffet :ensure t :after (:any modus-themes ef-themes) :defer 1 :config (let ((modus-themes-p (featurep 'modus-themes)) (ef-themes-p (featurep 'ef-themes))) (setq theme-buffet-menu 'end-user) (setq theme-buffet-time-offset 0) (setq theme-buffet-end-user '(:night (ef-dark ef-winter ef-autumn ef-night ef-duo-dark ef-symbiosis ef-owl) :morning (ef-light ef-cyprus ef-spring ef-frost ef-duo-light ef-eagle) :afternoon (ef-arbutus ef-day ef-kassio ef-summer ef-elea-light ef-maris-light ef-melissa-light ef-trio-light ef-reverie) :evening (ef-rosa ef-elea-dark ef-maris-dark ef-melissa-dark ef-trio-dark ef-dream))) (when (or modus-themes-p ef-themes-p) (theme-buffet-timer-hours 2)))) #+end_src ** The =unravel-theme.el= section about ~fontaine~ :PROPERTIES: :CUSTOM_ID: h:cb41fef0-41a5-4a85-9552-496d96290258 :END: [ Watch Prot's video: [[https://protesilaos.com/codelog/2024-01-16-customize-emacs-fonts/][Customise Emacs fonts]] (2024-01-16) ] #+begin_quote My ~fontaine~ package allows the user to define detailed font configurations and set them on demand. For example, one can have a =regular-editing= preset and another for =presentation-mode= (these are arbitrary, user-defined symbols): the former uses small fonts which are optimised for writing, while the latter applies typefaces that are pleasant to read at comfortable point sizes. #+end_quote Prot is the lead developer and maintainer. + Package name (GNU ELPA): ~fontaine~ + Official manual: <https://protesilaos.com/emacs/fontaine> + Change log: <https://protesilaos.com/emacs/fontaine-changelog> + Git repositories: - GitHub: <https://github.com/protesilaos/fontaine> - GitLab: <https://gitlab.com/protesilaos/fontaine> Another section defines some complementary functionality ([[#h:60d6aae2-6e4b-402c-b6a8-411fc49a6857][The =unravel-theme.el= section about ~variable-pitch-mode~ and font resizing]]). #+begin_src emacs-lisp :tangle "unravel-modules/unravel-theme.el" ;;;; Fontaine (font configurations) ;; Read the manual: <https://protesilaos.com/emacs/fontaine> (use-package fontaine :ensure t :if (display-graphic-p) :hook ;; Persist the latest font preset when closing/starting Emacs and ;; while switching between themes. ((after-init . fontaine-mode) (after-init . (lambda () ;; Set last preset or fall back to desired style from `fontaine-presets'. (fontaine-set-preset (or (fontaine-restore-latest-preset) 'regular))))) :config ;; This is defined in Emacs C code: it belongs to font settings. (setq x-underline-at-descent-line nil) ;; And this is for Emacs28. (setq-default text-scale-remap-header-line t) ;; This is the default value. Just including it here for ;; completeness. (setq fontaine-latest-state-file (locate-user-emacs-file "fontaine-latest-state.eld")) (setq fontaine-presets '((small :default-height 80) (regular) ; like this it uses all the fallback values and is named `regular' (medium :default-weight semilight :default-height 115 :bold-weight extrabold) (large :inherit medium :default-height 150) (live-stream :default-family "Iosevka" :default-height 150 :default-weight medium :fixed-pitch-family "Iosevka" :variable-pitch-family "Iosevka" :bold-weight extrabold) (presentation :default-height 180) (jumbo :default-height 260) (t ;; I keep all properties for didactic purposes, but most can be ;; omitted. See the fontaine manual for the technicalities: ;; <https://protesilaos.com/emacs/fontaine>. :default-family "Iosevka" :default-weight regular :default-slant normal :default-width normal :default-height 100 :fixed-pitch-family "Iosevka Fixed" :fixed-pitch-weight nil :fixed-pitch-slant nil :fixed-pitch-width nil :fixed-pitch-height 1.0 :fixed-pitch-serif-family nil :fixed-pitch-serif-weight nil :fixed-pitch-serif-slant nil :fixed-pitch-serif-width nil :fixed-pitch-serif-height 1.0 :variable-pitch-family "Iosevka" :variable-pitch-weight nil :variable-pitch-slant nil :variable-pitch-width nil :variable-pitch-height 1.0 :mode-line-active-family "Iosevka Term" :mode-line-active-weight nil :mode-line-active-slant nil :mode-line-active-width nil :mode-line-active-height 1.0 :mode-line-inactive-family "Iosevka Term" :mode-line-inactive-weight nil :mode-line-inactive-slant nil :mode-line-inactive-width nil :mode-line-inactive-height 1.0 :header-line-family "Iosevka Term" :header-line-weight nil :header-line-slant nil :header-line-width nil :header-line-height 1.0 :line-number-family "Iosevka Term" :line-number-weight nil :line-number-slant nil :line-number-width nil :line-number-height 1.0 :tab-bar-family "Iosevka Term" :tab-bar-weight nil :tab-bar-slant nil :tab-bar-width nil :tab-bar-height 1.0 :tab-line-family "Iosevka Term" :tab-line-weight nil :tab-line-slant nil :tab-line-width nil :tab-line-height 1.0 :bold-family "Iosevka" :bold-slant nil :bold-weight bold :bold-width nil :bold-height 1.0 :italic-family "Iosevka" :italic-weight nil :italic-slant italic :italic-width nil :italic-height 1.0 :line-spacing nil)))) #+end_src ** The =unravel-theme.el= section about ~variable-pitch-mode~ and font resizing :PROPERTIES: :CUSTOM_ID: h:60d6aae2-6e4b-402c-b6a8-411fc49a6857 :END: [ Watch Prot's video: [[https://protesilaos.com/codelog/2024-01-16-customize-emacs-fonts/][Customise Emacs fonts]] (2024-01-16) ] #+begin_quote The built-in ~variable-pitch-mode~ makes the current buffer use a proportionately spaced font. In technical terms, it remaps the ~default~ face to ~variable-pitch~, so whatever applies to the latter takes effect over the former. I take care of their respective font families in my ~fontaine~ setup ([[#h:cb41fef0-41a5-4a85-9552-496d96290258][The =prot-emacs-theme.el= section about ~fontaine~]]). I want to activate ~variable-pitch-mode~ in all buffers where I normally focus on prose. The exact mode hooks are specified in the variable =prot/enable-variable-pitch-in-hooks=. Exceptions to these are major modes that I do not consider related to prose (and which in my opinion should not be derived from ~text-mode~): these are excluded in the function ~prot/enable-variable-pitch~. #+end_quote #+begin_src emacs-lisp :tangle "unravel-modules/unravel-theme.el" ;;;;; `variable-pitch-mode' setup (use-package face-remap :ensure nil :functions prot/enable-variable-pitch :bind ( :map ctl-x-x-map ("v" . variable-pitch-mode)) :hook ((text-mode notmuch-show-mode elfeed-show-mode) . prot/enable-variable-pitch) :config ;; NOTE 2022-11-20: This may not cover every case, though it works ;; fine in my workflow. I am still undecided by EWW. (defun prot/enable-variable-pitch () (unless (derived-mode-p 'mhtml-mode 'nxml-mode 'yaml-mode) (variable-pitch-mode 1))) ;;;;; Resize keys with global effect :bind ;; Emacs 29 introduces commands that resize the font across all ;; buffers (including the minibuffer), which is what I want, as ;; opposed to doing it only in the current buffer. The keys are the ;; same as the defaults. (("C-x C-=" . global-text-scale-adjust) ("C-x C-+" . global-text-scale-adjust) ("C-x C-0" . global-text-scale-adjust))) #+end_src ** Finally, we provide the =unravel-theme.el= module :PROPERTIES: :CUSTOM_ID: h:bac0ce0a-db68-42e7-ba2c-f350f91f80ef :END: #+begin_src emacs-lisp :tangle "unravel-modules/unravel-theme.el" (provide 'unravel-theme) #+end_src * The =unravel-essentials.el= module :PROPERTIES: :CUSTOM_ID: h:0ef52ed9-7b86-4329-ae4e-eff9ab8d07f2 :END: ** The =unravel-essentials.el= block with basic configurations :PROPERTIES: :CUSTOM_ID: h:713ede33-3802-40c6-a8e3-7e1fc0d0a924 :END: #+begin_src emacs-lisp :tangle "unravel-modules/unravel-essentials.el" :mkdirp yes ;;; Essential configurations (use-package emacs :ensure nil :demand t :config ;;;; General settings and common custom functions (setq help-window-select t) (setq next-error-recenter '(4)) ; center of the window (setq find-library-include-other-files nil) ; Emacs 29 (setq tramp-connection-timeout (* 60 10)) ; seconds (setq save-interprogram-paste-before-kill t) (setq mode-require-final-newline t) (setq-default truncate-partial-width-windows nil) (setq eval-expression-print-length nil) (setq kill-do-not-save-duplicates t) (setq scroll-error-top-bottom t) (setq echo-keystrokes-help t) ; Emacs 30 (setq epa-keys-select-method 'minibuffer)) ; Emacs 30 #+end_src ** The =unravel-essentials.el= configuration to track recently visited files (~recentf~) :PROPERTIES: :CUSTOM_ID: h:f9aa7523-d88a-4080-add6-073f36cb8b9a :END: #+begin_src emacs-lisp :tangle "unravel-modules/unravel-essentials.el" (use-package recentf :ensure nil :hook (after-init . recentf-mode) :config (setq recentf-max-saved-items 100) (setq recentf-max-menu-items 25) ; I don't use the `menu-bar-mode', but this is good to know (setq recentf-save-file-modes nil) (setq recentf-keep nil) (setq recentf-auto-cleanup nil) (setq recentf-initialize-file-name-history nil) (setq recentf-filename-handlers nil) (setq recentf-show-file-shortcuts-flag nil)) #+end_src ** The =unravel-essentials.el= settings for bookmarks :PROPERTIES: :CUSTOM_ID: h:581aa0ff-b136-4099-a321-3b86edbfbccb :END: #+begin_quote Bookmarks are compartments that store arbitrary information about a file or buffer. The records are used to recreate that file/buffer inside of Emacs. Put differently, we can easily jump back to a file or directory (or anything that has a bookmark recorder+handler, really). Use the ~bookmark-set~ command (=C-x r m= by default) to record a bookmark and then visit one of your bookmarks with ~bookmark-jump~ (=C-x r b= by default). Also see [[#h:5685df62-4484-42ad-a062-d55ab19022e3][the =unravel-essentials.el= settings for registers]]. #+end_quote #+begin_src emacs-lisp :tangle "unravel-modules/unravel-essentials.el" ;;;; Built-in bookmarking framework (bookmark.el) (use-package bookmark :ensure nil :commands (bookmark-set bookmark-jump bookmark-bmenu-list) :hook (bookmark-bmenu-mode . hl-line-mode) :config (setq bookmark-use-annotations nil) (setq bookmark-automatically-show-annotations nil) (setq bookmark-fringe-mark nil) ; Emacs 29 to hide bookmark fringe icon ;; Write changes to the bookmark file as soon as 1 modification is ;; made (addition or deletion). Otherwise Emacs will only save the ;; bookmarks when it closes, which may never happen properly ;; (e.g. power failure). (setq bookmark-save-flag 1)) #+end_src ** The =unravel-essentials.el= settings for registers :PROPERTIES: :CUSTOM_ID: h:5685df62-4484-42ad-a062-d55ab19022e3 :END: [ Watch Prot's video: [[https://protesilaos.com/codelog/2023-06-28-emacs-mark-register-basics/][Mark and register basics]] (2023-06-28). ] #+begin_quote Much like bookmarks, registers store data that we can reinstate quickly ([[#h:581aa0ff-b136-4099-a321-3b86edbfbccb][The =unravel-essentials.el= settings for bookmarks]]). A common use-case is to write some text to a register and then insert that text by calling the given register. This is much better than relying on the ~kill-ring~, because registers are meant to be overwritten by the user, whereas the ~kill-ring~ accumulates lots of text that we do not necessarily need. To me, registers are essential for keyboard macros. By default, registers do not persist between Emacs sessions, though I do need to re-use them from time to time, hence the arrangement to record them with ~savehist-mode~ ([[#h:25765797-27a5-431e-8aa4-cc890a6a913a][The =unravel-completion.el= settings for saving the history (~savehist-mode~)]]). #+end_quote #+begin_src emacs-lisp :tangle "unravel-modules/unravel-essentials.el" ;;;; Registers (register.el) (use-package register :ensure nil :defer t ; its commands are autoloaded, so this will be loaded then :config (setq register-preview-delay 0.8 register-preview-function #'register-preview-default) (with-eval-after-load 'savehist (add-to-list 'savehist-additional-variables 'register-alist))) #+end_src ** The =unravel-essentials.el= section for ~delete-selection-mode~ :PROPERTIES: :CUSTOM_ID: h:d551b90d-d730-4eb5-976a-24b010fd4db3 :END: #+begin_src emacs-lisp :tangle "unravel-modules/unravel-essentials.el" ;;;; Delete selection (use-package delsel :ensure nil :hook (after-init . delete-selection-mode)) #+end_src ** The =unravel-essentials.el= settings for tooltips :PROPERTIES: :CUSTOM_ID: h:26afeb95-7920-45ed-8ff6-3648256c280b :END: #+begin_quote With these settings in place, Emacs will use its own faces and frame infrastructure to display tooltips. I prefer it this way because then we can benefit from the text properties that can be added to these messages (e.g. a different colour or a slant). #+end_quote #+begin_src emacs-lisp :tangle "unravel-modules/unravel-essentials.el" ;;;; Tooltips (tooltip-mode) (use-package tooltip :ensure nil :hook (after-init . tooltip-mode) :config (setq tooltip-delay 0.5 tooltip-short-delay 0.5 x-gtk-use-system-tooltips t tooltip-frame-parameters '((name . "tooltip") (internal-border-width . 10) (border-width . 0) (no-special-glyphs . t)))) #+end_src ** The =unravel-essentials.el= arrangement to run Emacs as a server :PROPERTIES: :CUSTOM_ID: h:7709b7e9-844f-49f3-badf-784aacec4bca :END: #+begin_quote The "server" is functionally like the daemon, except it is run by the first Emacs frame we launch. With a running server, we can connect to it through a new ~emacsclient~ call. This is useful if we want to launch new frames that share resources with the existing running process. #+end_quote #+begin_src emacs-lisp :tangle "unravel-modules/unravel-essentials.el" ;;;; Emacs server (allow emacsclient to connect to running session) (use-package server :ensure nil :defer 1 :config (setq server-client-instructions nil) (unless (server-running-p) (server-start))) #+end_src ** The =prot-emacs-essentials.el= section about ~expreg~ (tree-sitter mark syntactically) :PROPERTIES: :CUSTOM_ID: h:ceb193bf-0de3-4c43-8ab7-6daa50817754 :END: #+begin_quote The ~expreg~ package by Yuan Fu (aka casouri) uses the tree-sitter framework to incrementally expand the region from the smallest to the largest syntactic unit in the given context. This is a powerful feature, though it (i) requires Emacs to be built with tree-sitter support and (ii) for the user to be running a major mode that is designed for tree-sitter (Lisp seems to work regardless). The package offers the ~expreg-expand~ and ~expreg-contract~ commands. #+end_quote #+begin_src emacs-lisp :tangle "unravel-modules/unravel-essentials.el" ;;; Mark syntactic constructs efficiently if tree-sitter is available (expreg) (when (treesit-available-p) (use-package expreg :ensure t :functions (prot/expreg-expand prot/expreg-expand-dwim) :bind ("C-M-SPC" . prot/expreg-expand-dwim) ; overrides `mark-sexp' :config (defun prot/expreg-expand (n) "Expand to N syntactic units, defaulting to 1 if none is provided interactively." (interactive "p") (dotimes (_ n) (expreg-expand))) (defun prot/expreg-expand-dwim () "Do-What-I-Mean `expreg-expand' to start with symbol or word. If over a real symbol, mark that directly, else start with a word. Fall back to regular `expreg-expand'." (interactive) (let ((symbol (bounds-of-thing-at-point 'symbol))) (cond ((equal (bounds-of-thing-at-point 'word) symbol) (prot/expreg-expand 1)) (symbol (prot/expreg-expand 2)) (t (expreg-expand))))))) #+end_src ** The =unravel-essentials.el= section for Battery display :PROPERTIES: :CUSTOM_ID: h:080aa291-95b4-4d54-8783-d156b13190e9 :END: #+begin_src emacs-lisp :tangle "unravel-modules/unravel-essentials.el" ;;;; Show battery status on the mode line (battery.el) (use-package battery :ensure nil :hook (after-init . display-battery-mode) :config (setq battery-mode-line-format (cond ((eq battery-status-function #'battery-linux-proc-acpi) "⏻ %b%p%%,%d°C ") (battery-status-function "⏻ %b%p%% ")))) #+end_src ** Finally, we provide the =unravel-essentials.el= module :PROPERTIES: :CUSTOM_ID: h:c8b2f021-fe5a-4f6b-944c-20340f764fb2 :END: #+begin_src emacs-lisp :tangle "unravel-modules/unravel-essentials.el" (provide 'unravel-essentials) #+end_src * The =unravel-completion.el= module :PROPERTIES: :CUSTOM_ID: h:15edf2c3-4419-4101-928a-6e224958a741 :END: ** The =unravel-completion.el= settings for completion styles :PROPERTIES: :CUSTOM_ID: h:14b09958-279e-4069-81e3-5a16c9b69892 :END: #+begin_quote The ~completion-styles~ are pattern matching algorithms. They interpret user input and match candidates accordingly. - emacs22 :: Prefix completion that only operates on the text before point. If we are in =prefix|suffix=, with =|= representing the cursor, it will consider everything that expands =prefix= and then add back to it the =suffix=. - basic :: Prefix completion that also accounts for the text after point. Using the above example, this one will consider patterns that match all of ~emacs22~ as well as anything that completes =suffix=. - partial-completion :: This is used for file navigation. Instead of typing out a full path like =~/.local/share/fonts=, we do =~/.l/s/f= or variants thereof to make the matches unique such as =~/.l/sh/fon=. It is a joy to navigate the file system in this way. - substring :: Matches the given sequence of characters literally regardless of where it is in a word. So =pro= will match =professional= as well as =reproduce=. - flex :: Completion of an in-order subset of characters. It does not matter where the charactes are in the word, so long as they are encountered in the given order. The input =lad= will thus match ~list-faces-display~ as well as ~pulsar-highlight-dwim~. - initials :: Completion of acronyms and initialisms. Typing =lfd= will thus match ~list-faces-display~. This completion style can also be used for file system navigation, though I prefer to only have ~partial-completion~ handle that task. - orderless :: This is the only completion style I use which is not built into Emacs and which I tweak further in a separate section ([[#h:7cc77fd0-8f98-4fc0-80be-48a758fcb6e2][The =prot-emacs-completion.el= for the ~orderless~ completion style]]). It matches patterns out-of-order. Patterns are typically words separated by spaces, though they can also be regular expressions, and even styles that are the same as the aforementioned ~flex~ and ~initials~. Now that you know about the completion styles I use, take a look at the value of my ~completion-styles~. You will notice that ~orderless~, which is the most powerful/flexible is placed last. I do this because Emacs tries the styles in the given order from left to right, moving the next one until it finds a match. As such, I usually want to start with tight matches (e.g. =li-fa-di= for ~list-faces-display~) and only widen the scope of the search as I need to. This is easy to do because none of the built-in completion styles parses the empty space, so as soon as I type a space after some characters I am using ~orderless~. #+end_quote (There are more details in Prot's file, for the interested reader) #+begin_src emacs-lisp :tangle "unravel-modules/unravel-completion.el" :mkdirp yes ;;; General minibuffer settings (use-package minibuffer :ensure nil :config ;;;; Completion styles (setq completion-styles '(basic substring initials flex orderless)) ; also see `completion-category-overrides' (setq completion-pcm-leading-wildcard t) ; Emacs 31: make `partial-completion' behave like `substring' ;; Reset all the per-category defaults so that (i) we use the ;; standard `completion-styles' and (ii) can specify our own styles ;; in the `completion-category-overrides' without having to ;; explicitly override everything. (setq completion-category-defaults nil) ;; A non-exhaustve list of known completion categories: ;; ;; - `bookmark' ;; - `buffer' ;; - `charset' ;; - `coding-system' ;; - `color' ;; - `command' (e.g. `M-x') ;; - `customize-group' ;; - `environment-variable' ;; - `expression' ;; - `face' ;; - `file' ;; - `function' (the `describe-function' command bound to `C-h f') ;; - `info-menu' ;; - `imenu' ;; - `input-method' ;; - `kill-ring' ;; - `library' ;; - `minor-mode' ;; - `multi-category' ;; - `package' ;; - `project-file' ;; - `symbol' (the `describe-symbol' command bound to `C-h o') ;; - `theme' ;; - `unicode-name' (the `insert-char' command bound to `C-x 8 RET') ;; - `variable' (the `describe-variable' command bound to `C-h v') ;; - `consult-grep' ;; - `consult-isearch' ;; - `consult-kmacro' ;; - `consult-location' ;; - `embark-keybinding' ;; (setq completion-category-overrides ;; NOTE 2021-10-25: I am adding `basic' because it works better as a ;; default for some contexts. Read: ;; <https://debbugs.gnu.org/cgi/bugreport.cgi?bug=50387>. ;; ;; `partial-completion' is a killer app for files, because it ;; can expand ~/.l/s/fo to ~/.local/share/fonts. ;; ;; If `basic' cannot match my current input, Emacs tries the ;; next completion style in the given order. In other words, ;; `orderless' kicks in as soon as I input a space or one of its ;; style dispatcher characters. '((file (styles . (basic partial-completion orderless))) (bookmark (styles . (basic substring))) (library (styles . (basic substring))) (embark-keybinding (styles . (basic substring))) (imenu (styles . (basic substring orderless))) (consult-location (styles . (basic substring orderless))) (kill-ring (styles . (emacs22 orderless))) (eglot (styles . (emacs22 substring orderless)))))) #+end_src ** The =unravel-completion.el= for the ~orderless~ completion style :PROPERTIES: :CUSTOM_ID: h:7cc77fd0-8f98-4fc0-80be-48a758fcb6e2 :END: #+begin_quote The ~orderless~ package by Omar Antolín Camarena provides one of the completion styles that I use ([[#h:14b09958-279e-4069-81e3-5a16c9b69892][The =prot-emacs-completion.el= settings for completion styles]]). It is a powerful pattern matching algorithm that parses user input and interprets it out-of-order, so that =in pa= will cover ~insert-pair~ as well as ~package-install~. Components of the search are space-separated, by default, though we can modify the user option ~orderless-component-separator~ to have something else (but I cannot think of a better value). In the section about completion styles, I explain how I use ~orderless~ and why its power does not result in lots of false positives. #+end_quote #+begin_src emacs-lisp :tangle "unravel-modules/unravel-completion.el" ;;; Orderless completion style (use-package orderless :ensure t :demand t :after minibuffer :config ;; Remember to check my `completion-styles' and the ;; `completion-category-overrides'. (setq orderless-matching-styles '(orderless-prefixes orderless-regexp)) ;; SPC should never complete: use it for `orderless' groups. ;; The `?' is a regexp construct. :bind ( :map minibuffer-local-completion-map ("SPC" . nil) ("?" . nil))) #+end_src ** The =unravel-completion.el= settings to ignore letter casing :PROPERTIES: :CUSTOM_ID: h:7fe1787d-dba3-46fe-82a9-5dc5f8ea6217 :END: #+begin_quote I never really need to match letters case-sensitively in the minibuffer. Let's have everything ignore casing by default. #+end_quote #+begin_src emacs-lisp :tangle "unravel-modules/unravel-completion.el" (setq completion-ignore-case t) (setq read-buffer-completion-ignore-case t) (setq-default case-fold-search t) ; For general regexp (setq read-file-name-completion-ignore-case t) #+end_src ** The =unravel-completion.el= settings for common interactions :PROPERTIES: :CUSTOM_ID: h:b640f032-ad11-413e-ad8f-63408671d500 :END: #+begin_quote Here I combine several small tweaks to improve the overall minibuffer experience. - The need to ~resize-mini-windows~ arises on some occasions where Emacs has to show text spanning multiple lines in the "mini windows". A common scenario for me is in Org mode buffers where I set the =TODO= keyword of a task with =C-c C-t= (=M-x org-todo=) and have this as my setting: ~(setq org-use-fast-todo-selection 'expert)~ Otherwise, this is not an issue anyway and I may also like other options for ~org-use-fast-todo-selection~. - The ~read-answer-short~ is complementary to ~use-short-answers~. This is about providing the shorter version to some confirmation prompt, such as =y= instead of =yes=. - The ~echo-keystrokes~ is set to a low value to show in the echo area the incomplete key sequence I have just typed. This is especially helpful for demonstration purposes but also to double check that I did not mistype something (I cannot touch-type, so this happens a lot). - The ~minibuffer-prompt-properties~ and advice to ~completing-read-multiple~ make it so that (i) the minibuffer prompt is not accessible with regular motions to avoid mistakes and (ii) prompts that complete multiple targets show an indicator about this fact. With regard to the latter in particular, we have prompts like that of Org to set tags for a heading (with =C-c C-q= else =M-x org-set-tags-command=) where more than one candidate can be provided using completion, provided each candidate is separated by the ~crm-separator~ (a comma by default, though Org uses =:= in that scenario). Remember that when using completion in the minibuffer, you can hit =TAB= to expand the selected choice without exiting with it. For cases when multiple candidates can be selected, you select the candidate, =TAB=, then input the ~crm-separator~, and repeat until you are done selecting at which point you type =RET=. - Finally the ~file-name-shadow-mode~ is a neat little feature to remove the "shadowed" part of a file prompt while using something like =C-x C-f= (=M-x find-file=). File name shadowing happens when we invoke ~find-file~ and instead of first deleting the contents of the minibuffer, we start typing out the file system path we wish to visit. For example, I am in =~/Git/Projects/= and type directly after it something like =~/.local/share/fonts/=, so Emacs displays =~/Git/Projects/~/.local/share/fonts/= with the original part greyed out. With ~file-name-shadow-mode~ the "shadowed" part is removed altogether. This is especially nice when combined with the completion style called ~partial-completion~ ([[#h:14b09958-279e-4069-81e3-5a16c9b69892][The =unravel-completion.el= settings for completion styles]]). #+end_quote #+begin_src emacs-lisp :tangle "unravel-modules/unravel-completion.el" (use-package rfn-eshadow :ensure nil :hook (minibuffer-setup . cursor-intangible-mode) :config ;; Not everything here comes from rfn-eshadow.el, but this is fine. (setq resize-mini-windows t) (setq read-answer-short t) ; also check `use-short-answers' for Emacs28 (setq echo-keystrokes 0.25) (setq kill-ring-max 60) ; Keep it small ;; Do not allow the cursor to move inside the minibuffer prompt. I ;; got this from the documentation of Daniel Mendler's Vertico ;; package: <https://github.com/minad/vertico>. (setq minibuffer-prompt-properties '(read-only t cursor-intangible t face minibuffer-prompt)) ;; Add prompt indicator to `completing-read-multiple'. We display ;; [`completing-read-multiple': <separator>], e.g., ;; [`completing-read-multiple': ,] if the separator is a comma. This ;; is adapted from the README of the `vertico' package by Daniel ;; Mendler. I made some small tweaks to propertize the segments of ;; the prompt. (defun crm-indicator (args) (cons (format "[`completing-read-multiple': %s] %s" (propertize (replace-regexp-in-string "\\`\\[.*?]\\*\\|\\[.*?]\\*\\'" "" crm-separator) 'face 'error) (car args)) (cdr args))) (advice-add #'completing-read-multiple :filter-args #'crm-indicator) (file-name-shadow-mode 1)) #+end_src ** The =unravel-completion.el= generic minibuffer UI settings :PROPERTIES: :CUSTOM_ID: h:de61a607-0bdf-462b-94cd-c0898319590e :END: These are some settings for the default completion user interface. #+begin_src emacs-lisp :tangle "unravel-modules/unravel-completion.el" (use-package minibuffer :ensure nil :demand t :config (setq completions-format 'one-column) (setq completion-show-help nil) (setq completion-auto-help 'always) (setq completion-auto-select nil) (setq completions-detailed t) (setq completion-show-inline-help nil) (setq completions-max-height 6) (setq completions-header-format (propertize "%s candidates:\n" 'face 'bold-italic)) (setq completions-highlight-face 'completions-highlight) (setq minibuffer-completion-auto-choose t) (setq minibuffer-visible-completions t) ; Emacs 30 (setq completions-sort 'historical)) #+end_src ** The =unravel-completion.el= settings for saving the history (~savehist-mode~) :PROPERTIES: :CUSTOM_ID: h:25765797-27a5-431e-8aa4-cc890a6a913a :END: #+begin_quote Minibuffer prompts can have their own history. When they do not, they share a common history of user inputs. Emacs keeps track of that history in the current session, but loses it as soon as we close it. With ~savehist-mode~ enabled, all minibuffer histories are written to a file and are restored when we start Emacs again. #+end_quote #+begin_quote Since we are already recording minibuffer histories, we can instruct ~savehist-mode~ to also keep track of additional variables and restore them next time we use Emacs. Hence ~savehist-additional-variables~. I do this in a few of places: - [[#h:804b858f-7913-47ef-aaf4-8eef5b59ecb4][The =unravel-completion.el= for in-buffer completion popup and preview (~corfu~)]] - [[#h:5685df62-4484-42ad-a062-d55ab19022e3][The =unravel-essentials.el= settings for registers]] Note that the user option ~history-length~ applies to each individual history variable: it is not about all histories combined. Overall, I am happy with this feature and benefit from it on a daily basis. #+end_quote #+begin_src emacs-lisp :tangle "unravel-modules/unravel-completion.el" ;;;; `savehist' (minibuffer and related histories) (use-package savehist :ensure nil :hook (after-init . savehist-mode) :config (setq savehist-file (locate-user-emacs-file "savehist")) (setq history-length 100) (setq history-delete-duplicates t) (setq savehist-save-minibuffer-history t) (add-to-list 'savehist-additional-variables 'kill-ring)) #+end_src ** The =unravel-completion.el= settings for dynamic text expansion (~dabbrev~) :PROPERTIES: :CUSTOM_ID: h:567bb00f-1d82-4746-93e5-e0f60721728a :END: #+begin_quote The built-in ~dabbrev~ package provides a text completion method that reads the contents of a buffer and expands the text before the cursor to match possible candidates. This is done with =M-/= (~dabbrev-expand~) which is what I use most of the time to perform in-buffer completions. The term "dabbrev" stands for "dynamic abbreviation". Emacs also has static, user-defined abbreviations ([[#h:fd84b79a-351e-40f0-b383-bf520d77834b][Settings for static text expansion (~abbrev~)]]). #+end_quote #+begin_src emacs-lisp :tangle "unravel-modules/unravel-completion.el" (use-package dabbrev :ensure nil :commands (dabbrev-expand dabbrev-completion) :config ;;;; `dabbrev' (dynamic word completion (dynamic abbreviations)) (setq dabbrev-abbrev-char-regexp "\\sw\\|\\s_") (setq dabbrev-abbrev-skip-leading-regexp "[$*/=~']") (setq dabbrev-backward-only nil) (setq dabbrev-case-distinction 'case-replace) (setq dabbrev-case-fold-search nil) (setq dabbrev-case-replace 'case-replace) (setq dabbrev-check-other-buffers t) (setq dabbrev-eliminate-newlines t) (setq dabbrev-upcase-means-case-search t) (setq dabbrev-ignored-buffer-modes '(archive-mode image-mode docview-mode pdf-view-mode))) #+end_src ** The =unravel-completion.el= for in-buffer completion popup (~corfu~) :PROPERTIES: :CUSTOM_ID: h:804b858f-7913-47ef-aaf4-8eef5b59ecb4 :END: #+begin_quote I generally do not rely on in-buffer text completion. I feel it slows me down and distracts me. When I do, however, need to rely on it, I have the ~corfu~ package by Daniel Mendler: it handles the task splendidly as it works with Emacs' underlying infrastructure for ~completion-at-point-functions~. Completion is triggered with the =TAB= key, which produces a popup where the cursor is. The companion ~corfu-popupinfo-mode~ will show a secondary documentation popup if we move over a candidate but do not do anything with it. Also see [[#h:567bb00f-1d82-4746-93e5-e0f60721728a][the =prot-emacs-completion.el= settings for dynamic text expansion (~dabbrev~)]]. #+end_quote #+begin_src emacs-lisp :tangle "unravel-modules/unravel-completion.el" ;;; Corfu (in-buffer completion popup) (use-package corfu :ensure t :hook (after-init . global-corfu-mode) ;; I also have (setq tab-always-indent 'complete) for TAB to complete ;; when it does not need to perform an indentation change. :bind (:map corfu-map ("<tab>" . corfu-complete)) :config (setq corfu-preview-current nil) (setq corfu-min-width 20) (setq corfu-popupinfo-delay '(1.25 . 0.5)) (corfu-popupinfo-mode 1) ; shows documentation after `corfu-popupinfo-delay' ;; Sort by input history (no need to modify `corfu-sort-function'). (with-eval-after-load 'savehist (corfu-history-mode 1) (add-to-list 'savehist-additional-variables 'corfu-history))) #+end_src ** The =unravel-completion.el= settings for ~consult~ :PROPERTIES: :CUSTOM_ID: h:22e97b4c-d88d-4deb-9ab3-f80631f9ff1d :END: #+begin_quote consult~ is another wonderful package by Daniel Mendler. It provides a number of commands that turbocharge the minibuffer with advanced capabilities for filtering, asynchronous input, and previewing of the current candidate's context. - A case where filtering is in use is the ~consult-buffer~ command, which many users have as a drop-in replacement to the generic =C-x b= (=M-x switch-to-buffer=). It is a one-stop-shop for buffers, recently visited files (if ~recentf-mode~ is used---I don't), bookmarks ([[#h:581aa0ff-b136-4099-a321-3b86edbfbccb][The =unravel-essentials.el= settings for bookmarks]]), and, in principle, anything else that defines a source for this interface. To filter those source, we can type at the empty minibuffer =b SPC=, which will insert a filter specific to buffers. Delete back to remove the =[Buffer]= filter and insert another filter. Available filters are displayed by typing =?= at the prompt (I define it this way to call the command ~consult-narrow-help~). Every multi-source command from ~consult~ relies on this paradigm. - Asynchronous input pertains to the intersection between Emacs and external search programs. A case in point is ~consult-grep~, which calls the system's ~grep~ program. The prompt distinguishes between what is sent to the external program and what is only shown to Emacs by wrapping the former inside of =#=. So the input =#prot-#completion= will send =prot-= to the ~grep~ program and then use =completion= inside of the minibuffer to perform the subsequent pattern-matching (e.g. with help from ~orderless~ ([[#h:7cc77fd0-8f98-4fc0-80be-48a758fcb6e2][The =unravel-completion.el= for the ~orderless~ completion style]]). The part that is sent to the external program does not block Emacs. It is handled asynchronously, so everything stays responsive. - As for previewing, ~consult~ commands show the context of the current match and update the window as we move between completion candidates in the minibuffer. For example, the ~consult-line~ command performs an in-buffer search and lets us move between matches in the minibuffer while seeing in the window above what the surrounding text looks like. This is an excellent feature when we are trying to find something and do not quite remember all the search terms to narrow down to it simply by typing at the minibuffer prompt. Also check: [[#h:e0f9c30e-3a98-4479-b709-7008277749e4][The =unravel-search.el= module]]. #+end_quote #+begin_src emacs-lisp :tangle "unravel-modules/unravel-completion.el" ;;; Enhanced minibuffer commands (consult.el) (use-package consult :ensure t :hook (completion-list-mode . consult-preview-at-point-mode) :bind (:map global-map ("M-g M-g" . consult-goto-line) ("M-K" . consult-keep-lines) ; M-S-k is similar to M-S-5 (M-%) ("M-F" . consult-focus-lines) ; same principle ("M-s M-b" . consult-buffer) ("M-s M-f" . consult-find) ("M-s M-g" . consult-grep) ("M-s M-h" . consult-history) ("M-s M-i" . consult-imenu) ("M-s M-l" . consult-line) ("M-s M-m" . consult-mark) ("M-s M-y" . consult-yank-pop) ("M-s M-s" . consult-outline) :map consult-narrow-map ("?" . consult-narrow-help)) :config (setq consult-line-numbers-widen t) ;; (setq completion-in-region-function #'consult-completion-in-region) (setq consult-async-min-input 3) (setq consult-async-input-debounce 0.5) (setq consult-async-input-throttle 0.8) (setq consult-narrow-key nil) (setq consult-find-args (concat "find . -not ( " "-path */.git* -prune " "-or -path */.cache* -prune )")) (setq consult-preview-key 'any) (setq consult-project-function nil) ; always work from the current directory (use `cd' to switch directory) (add-to-list 'consult-mode-histories '(vc-git-log-edit-mode . log-edit-comment-ring)) ;; the `imenu' extension is in its own file (require 'consult-imenu)) #+end_src ** The =unravel-completion.el= section about ~embark~ :PROPERTIES: :CUSTOM_ID: h:61863da4-8739-42ae-a30f-6e9d686e1995 :END: #+begin_quote The ~embark~ package by Omar Antolín Camarena provides a mechanism to perform relevant actions in the given context. What constitutes "the given context" depends on where the cursor is, such as if it is at the end of a symbolic expression in Lisp code or inside the minibuffer. The single point of entry is the ~embark-act~ command or variants like ~embark-dwim~. With ~embark-act~ we gain access to a customisable list of commands for the given context. If we are over a Lisp symbol, one possible action is to describe it (i.e. produce documentation about it). If we are browsing files in the minibuffer, possible actions include file operations such as to delete or rename the file. And so on for everything. The ~embark-dwim~ command always performs the default action for the given context. It is like invoking ~embark-act~ and then typing the =RET= key. A killer feature of ~embark~ is the concepts of "collect" and "export". These are used in the minibuffer to produce a dedicated buffer that contains all the completion candidates. For example, if we are reading documentation about =embark-= and have 10 items there, we can "collect" the results in their own buffer and then navigate it as if it were the minibuffer: =RET= will perform the action that the actual minibuffer would have carried out (to show documentation, in this case). Similarly, the export mechanism takes the completion candidates out of the minibuffer, though it also puts them in a major mode that is appropriate for them. Files, for instance, will be placed in a Dired buffer ([[#h:f8b08a77-f3a8-42fa-b1a9-f940348889c3][The =unravel-dired.el= module]]). Depending on the configurations about the "indicator", the ~embark-act~ command will display an informative buffer with keys and their corresponding commands. One downside of ~embark~ is that it is hard to know what the context is. I have had this experience myself several times, where I though I was targeting the URL at point while the actions were about Org source blocks, headings, and whatnot. Embark is probably correct in such a case, though I cannot make my brain think the way it expects. Another downside, which is also true for ~which-key~, is the sheer number of options for each context. I feel that the defaults should be more conservative, to have 3-4 actions per context to make it easier to find stuff. Those who need more, can add them. Documentation can also be provided to that end. Adding commands to such a list is not a trivial task, because the user must modify keymaps and thus understand the relevant concepts. Sure, we can all learn, but this is not your usual ~setq~ tweak. All things considered, I do not recommend ~embark~ to new users as I know for a fact that people have trouble using it effectively. Power users can benefit from it, though you will notice in the following code block and in =prot-embark.el= how even power users need to put in some work ([[#h:fb034be5-c316-4c4f-a46f-cebcab332a47][The =prot-embark.el= library]]). Whether it is worth it or not depends on one's use-case. Karthik Chikmagalur has an excellently written and presented essay on [[https://karthinks.com/software/fifteen-ways-to-use-embark/][Fifteen ways to use Embark]]. If you plan on becoming an ~embark~ power user, this will help you. #+end_quote #+begin_src emacs-lisp :tangle "unravel-modules/unravel-completion.el" ;;; Extended minibuffer actions and more (embark.el and prot-embark.el) (use-package embark :ensure t :defer 1 :config (setq embark-confirm-act-all nil) (setq embark-mixed-indicator-both nil) (setq embark-mixed-indicator-delay 1.0) (setq embark-indicators '(embark-mixed-indicator embark-highlight-indicator)) (setq embark-verbose-indicator-nested nil) ; I think I don't have them, but I do not want them either (setq embark-verbose-indicator-buffer-sections '(bindings)) (setq embark-verbose-indicator-excluded-actions '(embark-cycle embark-act-all embark-collect embark-export embark-insert)) ;; I never cycle and want to disable the damn thing. Normally, a ;; nil value disables a key binding but here that value is ;; interpreted as the binding for `embark-act'. So I just add ;; some obscure key that I do not have. I absolutely do not want ;; to cycle! (setq embark-cycle-key "<XF86Travel>") ;; I do not want `embark-org' and am not sure what is loading it. ;; So I just unsert all the keymaps... This is the nuclear option ;; but here we are. (with-eval-after-load 'embark-org (defvar prot/embark-org-keymaps '(embark-org-table-cell-map embark-org-table-map embark-org-link-copy-map embark-org-link-map embark-org-src-block-map embark-org-item-map embark-org-plain-list-map embark-org-export-in-place-map) "List of Embark keymaps for Org.") ;; Reset `prot/embark-org-keymaps'. (seq-do (lambda (keymap) (set keymap (make-sparse-keymap))) prot/embark-org-keymaps))) ;; I define my own keymaps because I only use a few functions in a ;; limited number of contexts. (use-package prot-embark :ensure nil :after embark :bind ( :map global-map ("C-," . prot-embark-act-no-quit) ("C-." . prot-embark-act-quit) :map embark-collect-mode-map ("C-," . prot-embark-act-no-quit) ("C-." . prot-embark-act-quit) :map minibuffer-local-filename-completion-map ("C-," . prot-embark-act-no-quit) ("C-." . prot-embark-act-quit)) :config (setq embark-keymap-alist '((buffer prot-embark-buffer-map) (command prot-embark-command-map) (expression prot-embark-expression-map) (file prot-embark-file-map) (function prot-embark-function-map) (identifier prot-embark-identifier-map) (package prot-embark-package-map) (region prot-embark-region-map) (symbol prot-embark-symbol-map) (url prot-embark-url-map) (variable prot-embark-variable-map) (t embark-general-map)))) ;; Needed for correct exporting while using Embark with Consult ;; commands. (use-package embark-consult :ensure t :after (embark consult)) #+end_src ** The =unravel-completion.el= section to configure completion annotations (~marginalia~) :PROPERTIES: :CUSTOM_ID: h:bd3f7a1d-a53d-4d3e-860e-25c5b35d8e7e :END: #+begin_quote The ~marginalia~ package, co-authored by Daniel Mendler and Omar Antolín Camarena, provides helpful annotations to the side of completion candidates. We see its effect, for example, when we call =M-x=: each command has a brief description next to it (taken from its doc string) as well as a key binding, if it has one. #+end_quote #+begin_src emacs-lisp :tangle "unravel-modules/unravel-completion.el" ;;; Detailed completion annotations (marginalia.el) (use-package marginalia :ensure t :hook (after-init . marginalia-mode) :config (setq marginalia-max-relative-age 0)) ; absolute time #+end_src ** The =unravel-completion.el= section for ~vertico~ :PROPERTIES: :CUSTOM_ID: h:cff33514-d3ac-4c16-a889-ea39d7346dc5 :END: #+begin_quote The ~vertico~ package by Daniel Mendler displays the minibuffer in a vertical layout. Under the hood, it takes care to be responsive and to handle even massive completion tables gracefully. #+end_quote #+begin_src emacs-lisp :tangle "unravel-modules/unravel-completion.el" ;;; Vertical completion layout (vertico) (use-package vertico :ensure t :hook (after-init . vertico-mode) :config (setq vertico-scroll-margin 0) (setq vertico-count 5) (setq vertico-resize t) (setq vertico-cycle t) (with-eval-after-load 'rfn-eshadow ;; This works with `file-name-shadow-mode' enabled. When you are in ;; a sub-directory and use, say, `find-file' to go to your home '~/' ;; or root '/' directory, Vertico will clear the old path to keep ;; only your current input. (add-hook 'rfn-eshadow-update-overlay-hook #'vertico-directory-tidy))) #+end_src ** Finally, we provide the ~unravel-completion.el~ module #+begin_src emacs-lisp :tangle "unravel-modules/unravel-completion.el" (provide 'unravel-completion) #+end_src * The =unravel-langs.el= module :PROPERTIES: :CUSTOM_ID: h:f44afb76-a1d7-4591-934d-b698cc79a792 :END: ** The =unravel-langs.el= settings for TAB :PROPERTIES: :CUSTOM_ID: h:559713c8-0e1e-44aa-bca8-0caae01cc8bb :END: #+begin_src emacs-lisp :tangle "unravel-modules/unravel-langs.el" :mkdirp yes ;;;; Tabs, indentation, and the TAB key (use-package emacs :ensure nil :demand t :config (setq tab-always-indent 'complete) (setq tab-first-completion 'word-or-paren-or-punct) ; Emacs 27 (setq-default tab-width 4 indent-tabs-mode nil)) #+end_src ** The =unravel-langs.el= settings ~show-paren-mode~ :PROPERTIES: :CUSTOM_ID: h:7cd21ea6-c5d8-4258-999d-ad94cac2d8bf :END: #+begin_quote The built-in ~show-paren-mode~ highlights the parenthesis on the opposite end of the current symbolic expression. It also highlights matching terms of control flow in programming languages that are not using parentheses like Lisp: for instance, in a ~bash~ shell script it highlights the ~if~ and ~fi~ keywords. This mode also works for prose and I use it globally. Simple and effective! #+end_quote #+begin_src emacs-lisp :tangle "unravel-modules/unravel-langs.el" ;;;; Parentheses (show-paren-mode) (use-package paren :ensure nil :hook (prog-mode . show-paren-local-mode) :config (setq show-paren-style 'mixed) (setq show-paren-when-point-in-periphery nil) (setq show-paren-when-point-inside-paren nil) (setq show-paren-context-when-offscreen 'overlay)) ; Emacs 29 #+end_src ** The =unravel-langs.el= settings for ~eldoc~ :PROPERTIES: :CUSTOM_ID: h:a5773a39-a78f-43fa-8feb-669492c1d5a9 :END: #+begin_quote The built-in ~eldoc~ feature is especially useful in programming modes. While we are in a function call, it produces an indicator in the echo area (where the minibuffer appears upon invocation) that shows the name of the function, the arguments it takes, if any, and highlights the current argument we are positioned at. This way, we do not have to go back to review the signature of the function just to remember its arity. Same principle for variables, where ~eldoc-mode~ puts the first line of their documentation string in the echo area. #+end_quote #+begin_src emacs-lisp :tangle "unravel-modules/unravel-langs.el" ;;;; Eldoc (Emacs live documentation feedback) (use-package eldoc :ensure nil :hook (prog-mode . eldoc-mode) :config (setq eldoc-message-function #'message)) ; don't use mode line for M-x eval-expression, etc. #+end_src ** The =unravel-langs.el= settings for ~eglot~ (LSP client) :PROPERTIES: :CUSTOM_ID: h:92258aa8-0d8c-4c12-91b4-5f44420435ce :END: #+begin_quote The built-in ~eglot~ feature, developed and maintained by João Távora, is Emacs' own client for the Language Server Protocol (LSP). The LSP technology is all about enhancing the ability of a text editor to work with a given programming language. This works by installing a so-called "language server" on your computer, which the "LSP client" (i.e. ~eglot~) will plug into. A typical language server provides the following capabilities: - Code completion :: This can be visualised for in-buffer automatic expansion of function calls, variables, and the like ([[#h:804b858f-7913-47ef-aaf4-8eef5b59ecb4][The =unravel-completion.el= for in-buffer completion popup (~corfu~)]]). - Code linting :: To display suggestions, warnings, or errors. These are highlighted in the buffer, usually with an underline, and can also be displayed in a standalone buffer with the commands ~flymake-show-buffer-diagnostics~, ~flymake-show-project-diagnostics~ ([[#h:df6d1b52-0306-4ace-9099-17dded11fbed][The =unravel-langs.el= settings for code linting (~flymake~)]]). - Code navigation and cross-referencing :: While over a symbol, use a command to jump directly to its definition. The default key bindings for going forth and then back are =M-.= (~xref-find-definitions~) and =M-,= (~xref-go-back~). ... Assuming the language server is installed, to start using the LSP client in a given file, do =M-x eglot=. To make this happen automatically for every newly visited file, add a hook like this: (add-hook 'SOME-MAJOR-mode #'eglot-ensure) #+end_quote #+begin_src emacs-lisp :tangle "unravel-modules/unravel-langs.el" ;;;; Eglot (built-in client for the language server protocol) (use-package eglot :ensure nil :functions (eglot-ensure) :commands (eglot) :config (setq eglot-sync-connect nil) (setq eglot-autoshutdown t)) #+end_src ** The =unravel-langs.el= settings for ~markdown-mode~ :PROPERTIES: :CUSTOM_ID: h:c9063898-07ae-4635-8853-bb5f4bbab421 :END: #+begin_quote The ~markdown-mode~ lets us edit Markdown files. We get syntax highlighting and several extras, such as the folding of headings and navigation between them. The mode actually provides lots of added functionality for GitHub-flavoured Markdown and to preview a Markdown file's HTML representation on a web page. Though I only use it for basic text editing. #+end_quote #+begin_src emacs-lisp :tangle "unravel-modules/unravel-langs.el" ;;; Markdown (markdown-mode) (use-package markdown-mode :ensure t :defer t :config (setq markdown-fontify-code-blocks-natively t)) #+end_src ** The =prot-emacs-langs.el= settings for ~csv-mode~ :PROPERTIES: :CUSTOM_ID: h:bae58479-86c1-410f-867e-c548def65b1c :END: #+begin_quote The package ~csv-mode~ provides support for =.csv= files. I do need this on occasion, even though my use-case is pretty basic. For me, the killer feature is the ability to create a virtual tabulated listing with the command ~csv-align-mode~: it hides the field delimiter (comma or space) and shows a tab stop in its stead. #+end_quote #+begin_src emacs-lisp :tangle "unravel-modules/unravel-langs.el" ;;; csv-mode (use-package csv-mode :ensure t :commands (csv-align-mode)) #+end_src ** The =unravel-langs.el= settings for spell checking (~flyspell~) :PROPERTIES: :CUSTOM_ID: h:115806c4-88b0-43c1-8db2-d9d8d20a5c17 :END: #+begin_src emacs-lisp :tangle "unravel-modules/unravel-langs.el" ;;; Flyspell (use-package flyspell :ensure nil :bind ( :map flyspell-mode-map ("C-;" . nil) :map flyspell-mouse-map ("<mouse-3>" . flyspell-correct-word)) :config (setq flyspell-issue-message-flag nil) (setq flyspell-issue-welcome-flag nil) (setq ispell-program-name "aspell") (setq ispell-dictionary "en_GB")) #+end_src ** The =unravel-langs.el= settings for code linting (~flymake~) :PROPERTIES: :CUSTOM_ID: h:df6d1b52-0306-4ace-9099-17dded11fbed :END: #+begin_quote The built-in ~flymake~ feature defines an interface for viewing the output of linter programs. A "linter" parses a file and reports possible notes/warnings/errors in it. With ~flymake~ we get these diagnostics in the form of a standalone buffer as well as inline highlights (typically underlines combined with fringe indicators) for the portion of text in question. The linter report is displayed with the command ~flymake-show-buffer-diagnostics~, or ~flymake-show-project-diagnostics~. Highlights are shown in the context of the file. The built-in ~eglot~ feature uses ~flymake~ internally to handle the LSP linter output ([[#h:92258aa8-0d8c-4c12-91b4-5f44420435ce][The =unravel-langs.el= settings for ~eglot~]]). As for what I have in this configuration block, the essentials for me are the user options ~flymake-start-on-save-buffer~ and ~flymake-start-on-flymake-mode~ as they make the linter update its report when the buffer is saved and when ~flymake-mode~ is started, respectively. Otherwise, we have to run it manually, which is cumbersome. The ~package-lint-flymake~ package by Steve Purcell adds the glue code to make ~flymake~ report issues with Emacs Lisp files for the purposes of packaging. I use it whenever I work on my numerous Emacs packages. #+end_quote #+begin_src emacs-lisp :tangle "unravel-modules/unravel-langs.el" ;;; Flymake (use-package flymake :ensure nil :bind (:map flymake-mode-map ("C-c ! s" . flymake-start) ("C-c ! l" . flymake-show-buffer-diagnostics) ; Emacs28 ("C-c ! L" . flymake-show-project-diagnostics) ; Emacs28 ("C-c ! n" . flymake-goto-next-error) ("C-c ! p" . flymake-goto-prev-error)) :config (setq flymake-fringe-indicator-position 'left-fringe) (setq flymake-suppress-zero-counters t) (setq flymake-no-changes-timeout nil) (setq flymake-start-on-flymake-mode t) (setq flymake-start-on-save-buffer t) (setq flymake-proc-compilation-prevents-syntax-check t) (setq flymake-wrap-around nil) (setq flymake-mode-line-format '("" flymake-mode-line-exception flymake-mode-line-counters)) ;; NOTE 2023-07-03: `prot-modeline.el' actually defines the counters ;; itself and ignores this. (setq flymake-mode-line-counter-format '("" flymake-mode-line-error-counter flymake-mode-line-warning-counter flymake-mode-line-note-counter "")) (setq flymake-show-diagnostics-at-end-of-line nil)) ; Emacs 30 ;;; Elisp packaging requirements (use-package package-lint-flymake :ensure t :after flymake :config (add-hook 'flymake-diagnostic-functions #'package-lint-flymake)) #+end_src ** The =unravel-langs.el= settings for ~outline-minor-mode~ :PROPERTIES: :CUSTOM_ID: h:ffff5f7b-a62b-4d4a-ae29-af75402e5c35 :END: #+begin_src emacs-lisp :tangle "unravel-modules/unravel-langs.el" ;;; General configurations for prose/writing ;;;; `outline' (`outline-mode' and `outline-minor-mode') (use-package outline :ensure nil :bind ("<f10>" . outline-minor-mode) :config (setq outline-minor-mode-highlight nil) ; emacs28 (setq outline-minor-mode-cycle t) ; emacs28 (setq outline-minor-mode-use-buttons nil) ; emacs29---bless you for the nil option! (setq outline-minor-mode-use-margins nil)) ; as above #+end_src ** The =prot-emacs-langs.el= settings for ~dictionary~ :PROPERTIES: :CUSTOM_ID: h:f91563d8-f176-4555-b45b-ece56de03279 :END: Use the entry point ~M-x dictionary-search~ #+begin_src emacs-lisp :tangle "unravel-modules/unravel-langs.el" ;;;; `dictionary' (use-package dictionary :ensure nil :config (setq dictionary-server "dict.org" dictionary-default-popup-strategy "lev" ; read doc string dictionary-create-buttons nil dictionary-use-single-buffer t)) #+end_src ** The =unravel-langs.el= settings for ~denote~ (notes and file-naming) :PROPERTIES: :CUSTOM_ID: h:e86a66dc-7ef9-4f09-ad7e-946de2034e8d :END: #+begin_quote This is another one of my packages and is extended by my ~consult-denote~ package ([[#h:ee82e629-fb05-4c75-9175-48a760a25691][The =unravel-langs.el= integration between Consult and Denote (~consult-denote~)]]). Denote is a simple note-taking tool for Emacs. It is based on the idea that notes should follow a predictable and descriptive file-naming scheme. The file name must offer a clear indication of what the note is about, without reference to any other metadata. Denote basically streamlines the creation of such files while providing facilities to link between them. Denote's file-naming scheme is not limited to "notes". It can be used for all types of file, including those that are not editable in Emacs, such as videos. Naming files in a consistent way makes their filtering and retrieval considerably easier. Denote provides relevant facilities to rename files, regardless of file type. #+end_quote Prot is the developer and maintainer of this package. + Package name (GNU ELPA): ~denote~ + Official manual: <https://protesilaos.com/emacs/denote> + Change log: <https://protesilaos.com/emacs/denote-changelog> + Git repositories: - GitHub: <https://github.com/protesilaos/denote> - GitLab: <https://gitlab.com/protesilaos/denote> + Video demo: <https://protesilaos.com/codelog/2022-06-18-denote-demo/> + Backronyms: Denote Everything Neatly; Omit The Excesses. Don't Ever Note Only The Epiphenomenal. #+begin_src emacs-lisp :tangle "unravel-modules/unravel-langs.el" ;;; Denote (simple note-taking and file-naming) ;; Read the manual: <https://protesilaos.com/emacs/denote>. This does ;; not include all the useful features of Denote. I have a separate ;; private setup for those, as I need to test everything is in order. (use-package denote :ensure t :hook ;; If you use Markdown or plain text files you want to fontify links ;; upon visiting the file (Org renders links as buttons right away). ((text-mode . denote-fontify-links-mode-maybe) ;; Highlight Denote file names in Dired buffers. Below is the ;; generic approach, which is great if you rename files Denote-style ;; in lots of places as I do. ;; ;; If you only want the `denote-dired-mode' in select directories, ;; then modify the variable `denote-dired-directories' and use the ;; following instead: ;; ;; (dired-mode . denote-dired-mode-in-directories) (dired-mode . denote-dired-mode)) :bind ;; Denote DOES NOT define any key bindings. This is for the user to ;; decide. Here I only have a subset of what Denote offers. ( :map global-map ("C-c n n" . denote) ("C-c n N" . denote-type) ("C-c n o" . denote-sort-dired) ; "order" mnemonic ;; Note that `denote-rename-file' can work from any context, not ;; just Dired buffers. That is why we bind it here to the ;; `global-map'. ;; ;; Also see `denote-rename-file-using-front-matter' further below. ("C-c n r" . denote-rename-file) ;; If you intend to use Denote with a variety of file types, it is ;; easier to bind the link-related commands to the `global-map', as ;; shown here. Otherwise follow the same pattern for ;; `org-mode-map', `markdown-mode-map', and/or `text-mode-map'. :map text-mode-map ("C-c n i" . denote-link) ; "insert" mnemonic ("C-c n I" . denote-add-links) ("C-c n b" . denote-backlinks) ;; Also see `denote-rename-file' further above. ("C-c n R" . denote-rename-file-using-front-matter) :map org-mode-map ("C-c n d l" . denote-org-extras-dblock-insert-links) ("C-c n d b" . denote-org-extras-dblock-insert-backlinks) ;; Key bindings specifically for Dired. :map dired-mode-map ("C-c C-d C-i" . denote-dired-link-marked-notes) ("C-c C-d C-r" . denote-dired-rename-marked-files) ("C-c C-d C-k" . denote-dired-rename-marked-files-with-keywords) ("C-c C-d C-f" . denote-dired-rename-marked-files-using-front-matter)) :config ;; Remember to check the doc strings of those variables. (setq denote-directory (expand-file-name "~/Documents/notes/")) (setq denote-file-type 'text) ; Org is the default file type ;; If you want to have a "controlled vocabulary" of keywords, ;; meaning that you only use a predefined set of them, then you want ;; `denote-infer-keywords' to be nil and `denote-known-keywords' to ;; have the keywords you need. (setq denote-known-keywords '("emacs" "philosophy" "politics" "economics")) (setq denote-infer-keywords t) (setq denote-sort-keywords t) (setq denote-excluded-directories-regexp nil) (setq denote-date-format nil) ; read its doc string (setq denote-rename-confirmations nil) ; CAREFUL with this if you are not familiar with Denote! (setq denote-backlinks-show-context nil) (setq denote-rename-buffer-format "[D] %t%b") (setq denote-buffer-has-backlinks-string " (<--->)") ;; Automatically rename Denote buffers when opening them so that ;; instead of their long file name they have a literal "[D]" ;; followed by the file's title. Read the doc string of ;; `denote-rename-buffer-format' for how to modify this. (denote-rename-buffer-mode 1) ;; ----- PERSONAL TWEAKS FOR EXPERIMENTS ----- (setq denote-text-front-matter "title: %s\n\n") (defun prot/denote-add-text-front-matter-separator () "Add separator equal to the length of the title. Do this when the `denote-file-type' is `text'." (when (and (eq denote-file-type 'text) ;; Not `string=' because there may be a .gpg extension as well. (string-match-p (file-name-extension buffer-file-name) "txt")) (save-excursion (goto-char (point-min)) (when (re-search-forward "title:" nil t) (let ((text (buffer-substring-no-properties (line-beginning-position) (line-end-position)))) (if (re-search-forward "^$" nil t) (insert (make-string (length text) ?-)) (error "Could not find an empty line after the front matter"))))))) (add-hook 'denote-after-new-note-hook #'prot/denote-add-text-front-matter-separator)) #+end_src *** The =unravel-langs.el= integration between Consult and Denote (~consult-denote~) :PROPERTIES: :CUSTOM_ID: h:ee82e629-fb05-4c75-9175-48a760a25691 :END: #+begin_quote This is another package of mine which extends my ~denote~ package ([[#h:e86a66dc-7ef9-4f09-ad7e-946de2034e8d][The =unravel-langs.el= settings for ~denote~ (notes and file-naming)]]). This is glue code to integrate ~denote~ with Daniel Mendler's ~consult~ ([[#h:22e97b4c-d88d-4deb-9ab3-f80631f9ff1d][The =unravel-completion.el= settings for ~consult~]]). The idea is to enhance minibuffer interactions, such as by providing a preview of the file-to-linked/opened and by adding more sources to the ~consult-buffer~ command. #+end_quote Prot is the developer of this package. + Package name (GNU ELPA): ~consult-denote~ + Official manual: not available yet. + Git repositories: + GitHub: <https://github.com/protesilaos/consult-denote> #+begin_src emacs-lisp :tangle "unravel-modules/unravel-langs.el" (use-package consult-denote :ensure t :bind (("C-c n f" . consult-denote-find) ("C-c n g" . consult-denote-grep)) :config (consult-denote-mode 1)) #+end_src ** Finally, we provide the ~unravel-langs.el~ module #+begin_src emacs-lisp :tangle "unravel-modules/unravel-langs.el" (provide 'unravel-langs) #+end_src * Custom libraries ** The =prot-embark.el= library :PROPERTIES: :CUSTOM_ID: h:fb034be5-c316-4c4f-a46f-cebcab332a47 :END: #+begin_src emacs-lisp :tangle "custom-lisp/prot-embark.el" :mkdirp yes ;;; prot-embark.el --- Custom Embark keymaps -*- lexical-binding: t -*- ;; Copyright (C) 2023-2024 Protesilaos Stavrou ;; Author: Protesilaos Stavrou <info@protesilaos.com> ;; URL: https://protesilaos.com/emacs/dotemacs ;; Version: 0.1.0 ;; Package-Requires: ((emacs "30.1") (embark "0.23")) ;; This file is NOT part of GNU Emacs. ;; This program is free software; you can redistribute it and/or modify ;; it under the terms of the GNU General Public License as published by ;; the Free Software Foundation, either version 3 of the License, or (at ;; your option) any later version. ;; ;; This program is distributed in the hope that it will be useful, ;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;; GNU General Public License for more details. ;; ;; You should have received a copy of the GNU General Public License ;; along with this program. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; ;; Remember that every piece of Elisp that I write is for my own ;; educational and recreational purposes. I am not a programmer and I ;; do not recommend that you copy any of this if you are not certain of ;; what it does. ;;; Code: (require 'embark) (defvar-keymap prot-embark-general-map :parent embark-general-map "i" #'embark-insert "w" #'embark-copy-as-kill "E" #'embark-export "S" #'embark-collect "A" #'embark-act-all "DEL" #'delete-region) (defvar-keymap prot-embark-url-map :parent embark-general-map "b" #'browse-url "d" #'embark-download-url "e" #'eww) (defvar-keymap prot-embark-buffer-map :parent embark-general-map "k" #'prot-simple-kill-buffer "o" #'switch-to-buffer-other-window "e" #'ediff-buffers) (add-to-list 'embark-post-action-hooks (list 'prot-simple-kill-buffer 'embark--restart)) (defvar-keymap prot-embark-file-map :parent embark-general-map "f" #'find-file "j" #'embark-dired-jump "c" #'copy-file "e" #'ediff-files) (defvar-keymap prot-embark-identifier-map :parent embark-general-map "h" #'display-local-help "." #'xref-find-definitions "o" #'occur) (defvar-keymap prot-embark-command-map :parent embark-general-map "h" #'describe-command "." #'embark-find-definition) (defvar-keymap prot-embark-expression-map :parent embark-general-map "e" #'pp-eval-expression "m" #'pp-macroexpand-expression) (defvar-keymap prot-embark-function-map :parent embark-general-map "h" #'describe-function "." #'embark-find-definition) (defvar-keymap prot-embark-package-map :parent embark-general-map "h" #'describe-package "i" #'package-install "d" #'package-delete "r" #'package-reinstall "u" #'embark-browse-package-url "w" #'embark-save-package-url) (defvar-keymap prot-embark-symbol-map :parent embark-general-map "h" #'describe-symbol "." #'embark-find-definition) (defvar-keymap prot-embark-variable-map :parent embark-general-map "h" #'describe-variable "." #'embark-find-definition) (defvar-keymap prot-embark-region-map :parent embark-general-map "a" #'align-regexp "D" #'delete-duplicate-lines "f" #'flush-lines "i" #'epa-import-keys-region "d" #'epa-decrypt-armor-in-region "r" #'repunctuate-sentences "s" #'sort-lines "u" #'untabify) ;; The minimal indicator shows cycling options, but I have no use ;; for those. I want it to be silent. (defun prot-embark-no-minimal-indicator ()) (advice-add #'embark-minimal-indicator :override #'prot-embark-no-minimal-indicator) (defun prot-embark-act-no-quit () "Call `embark-act' but do not quit after the action." (interactive) (let ((embark-quit-after-action nil)) (call-interactively #'embark-act))) (defun prot-embark-act-quit () "Call `embark-act' and quit after the action." (interactive) (let ((embark-quit-after-action t)) (call-interactively #'embark-act)) (when (and (> (minibuffer-depth) 0) (derived-mode-p 'completion-list-mode)) (abort-recursive-edit))) (provide 'prot-embark) ;;; prot-embark.el ends here #+end_src ** The =unravel-git.el= module :PROPERTIES: :CUSTOM_ID: h:65e3eff5-0bff-4e1f-b6c5-0d3aa1a0d232 :END: [ Watch Prot's talk: [[https://protesilaos.com/codelog/2023-08-03-contribute-core-emacs/][Contribute to GNU Emacs core]] (2023-08-03). ] #+begin_quote This section covers my settings for version control per se, but more widely for tools related to checking different versions of files and working with so-called "projects". #+end_quote *** The =unravel-git.el= section about ediff :PROPERTIES: :CUSTOM_ID: h:89edea05-4d94-4ea1-b2a8-5ad01422618c :END: [ Watch Prot's talk: [[https://protesilaos.com/codelog/2023-11-17-emacs-ediff-basics/][Emacs: ediff basics]] (2023-12-30) ] #+begin_quote The built-in ~ediff~ feature provides several commands that let us compare files or buffers side-by-side. The defaults of ~ediff~ are bad, in my opinion: it puts buffers one on top of the other and places the "control panel" in a separate Emacs frame. The first time I tried to use it, I thought I broke my setup because it is unlike anything we normally interact with. As such, the settings I have for ~ediff-split-window-function~ and ~ediff-window-setup-function~ are what I would expect Emacs maintainers to adopt as the new default. I strongly encourage everyone to start with them. In my workflow, the points of entry to the ~ediff~ feature are the commands ~ediff-files~, ~ediff-buffers~. Sometimes I use the 3-way variants with ~ediff-files3~ and ~ediff-buffers3~, though this is rare. Do watch the video I link to in the beginning of this section, as it covers the main functionality of this neat tool. I also show how it integrates with ~magit~ ([[#h:b08af527-9ebf-4425-ac3a-24b4f371a4fd][The =unravel-git.el= section about ~magit~ (great Git client)]]). #+end_quote #+begin_src emacs-lisp :tangle "unravel-modules/unravel-git.el" :mkdirp yes ;;;; `ediff' (use-package ediff :ensure nil :commands (ediff-buffers ediff-files ediff-buffers3 ediff-files3) :init (setq ediff-split-window-function 'split-window-horizontally) (setq ediff-window-setup-function 'ediff-setup-windows-plain) :config (setq ediff-keep-variants nil) (setq ediff-make-buffers-readonly-at-startup nil) (setq ediff-merge-revisions-with-ancestor t) (setq ediff-show-clashes-only t)) #+end_src *** The =unravel-git.el= section about =project.el= :PROPERTIES: :CUSTOM_ID: h:7dcbcadf-8af6-487d-b864-e4ce56d69530 :END: #+begin_quote In Emacs parlance, a "project" is a collection of files and/or directories that share the same root. The root of a project is identified by a special file or directory, with =.git/= being one of the defaults as it is a version control system supported by the built-in =vc.el= ([[#h:50add1d8-f0f4-49be-9e57-ab280a4aa300][The =unravel-git.el= section about =vc.el= and related]]). We can specify more project roots as a list of strings in the user option ~project-vc-extra-root-markers~. I work exclusively with Git repositories, so I just add there a =.project= file in case I ever need to register a project without it being controlled by ~git~. In that case, the =.project= file is just an empty file in a directory that I want to treat as the root of this project. The common way to switch to a project is to type =C-x p p=, which calls the command ~project-switch-project~. It lists all registered projects and also includes a =... (choose a dir)= option. By choosing a new directory, we register it in our project list if it has a recognisable root. Once we select a project, we are presented with a list of common actions to start working on the project. These are defined in the user option ~project-switch-commands~ and are activated by the final key that accesses them from the =C-x p= prefix. As such, do =M-x describe-keymap= and check the ~project-prefix-map~. For example, I bind ~project-dired~ to =C-x p RET=, so =RET= accesses this command after =C-x p p= as well. If any of the =project.el= commands is called from outside a project, it first prompts for a project and then carries out its action. For example, ~project-find-file~ will ask for a project to use, then switch to it, and then prompt for a file inside of the specified project. While inside a project, we have many commands that operate on the project level. For example, =C-x p f= (~project-find-file~) searches for a file across the project, while =C-x p b= (~project-switch-to-buffer~) switches to a buffer that is specific to the project. Again, check the ~project-prefix-map~ for available commands. If not inside a project, the project-related commands will first prompt to select a project (same as typing =C-x p p=) and then carry out their action. I combine projects with my ~beframe~ package, so that when I switch to a project I get a new frame that limits the buffers I visit there limited to that frame ([[#h:77e4f174-0c86-460d-8a54-47545f922ae9][The =unravel-window.el= section about ~beframe~]]). #+end_quote #+begin_src emacs-lisp :tangle "unravel-modules/unravel-git.el" ;;;; `project' (use-package project :ensure nil :bind (("C-x p ." . project-dired) ("C-x p C-g" . keyboard-quit) ("C-x p <return>" . project-dired) ("C-x p <delete>" . project-forget-project)) :config (setopt project-switch-commands '((project-find-file "Find file") (project-find-regexp "Find regexp") (project-find-dir "Find directory") (project-dired "Root dired") (project-vc-dir "VC-Dir") (project-shell "Shell") (keyboard-quit "Quit"))) (setq project-vc-extra-root-markers '(".project")) ; Emacs 29 (setq project-key-prompt-style t) ; Emacs 30 (advice-add #'project-switch-project :after #'prot-common-clear-minibuffer-message)) #+end_src *** The =unravel-git.el= section about ~diff-mode~ :PROPERTIES: :CUSTOM_ID: h:8b426a69-e3cd-42ac-8788-f41f6629f879 :END: #+begin_src emacs-lisp :tangle "unravel-modules/unravel-git.el" ;;;; `diff-mode' (use-package diff-mode :ensure nil :defer t :config (setq diff-default-read-only t) (setq diff-advance-after-apply-hunk t) (setq diff-update-on-the-fly t) ;; The following are from Emacs 27.1 (setq diff-refine nil) ; I do it on demand, with my `agitate' package (more below) (setq diff-font-lock-prettify t) ; I think nil is better for patches, but let me try this for a while (setq diff-font-lock-syntax 'hunk-also)) #+end_src *** The =unravel-git.el= section about ~magit~ (great Git client) :PROPERTIES: :CUSTOM_ID: h:b08af527-9ebf-4425-ac3a-24b4f371a4fd :END: #+begin_quote The ~magit~ package, maintained by Jonas Bernoulli, is the best front-end to ~git~ I have ever used. Not only is it excellent at getting the job done, it also helps you learn more about what ~git~ has to offer. At the core of its interface is ~transient~. This is a library that was originally developed as Magit-specific code that was then abstracted away and ultimately incorporated into Emacs version 29. With ~transient~, we get a window pop up with keys and commands corresponding to them. The window is interactive, as the user can set a value or toggle an option and have it take effect when the relevant command is eventually invoked. For ~git~, in particular, this interface is a genious way to surface the plethora of options. To start, call the command ~magit-status~. It brings up a buffer that shows information about the state of the repository. Sections include an overview of the current =HEAD=, untracked files, unstaged changes, staged changes, and recent commits. Each section's visibility state can be cycled by pressing =TAB= (variations of this are available---remember to do =C-h m= (~describe-mode~) in an unfamiliar major mode to get information about its key bindings). From the status buffer, we can perform all the usual version control operations. By typing =?= (~magit-dispatch~), we bring up the main ~transient~ menu, with keys that then bring up their own submenus, such as for viewing commit logs, setting the remotes, switching branches, etc. #+end_quote #+begin_src emacs-lisp :tangle "unravel-modules/unravel-git.el" ;;; Interactive and powerful git front-end (Magit) (use-package transient :defer t :config (setq transient-show-popup 0.5)) (use-package magit :ensure t :bind ("C-x g" . magit-status) :init (setq magit-define-global-key-bindings nil) ;; (setq magit-section-visibility-indicator '("⮧")) :config (setq git-commit-summary-max-length 50) ;; NOTE 2023-01-24: I used to also include `overlong-summary-line' ;; in this list, but I realised I do not need it. My summaries are ;; always in check. When I exceed the limit, it is for a good ;; reason. ;; (setq git-commit-style-convention-checks '(non-empty-second-line)) (setq magit-diff-refine-hunk t)) (use-package magit-repos :ensure nil ; part of `magit' :commands (magit-list-repositories) :init (setq magit-repository-directories '(("~/src/prototypes" . 1)))) #+end_src *** The =unravel-git.el= call to ~provide~ :PROPERTIES: :CUSTOM_ID: h:4e7035c5-9350-4c51-be85-85f2539ed295 :END: Finally, we ~provide~ the module. #+begin_src emacs-lisp :tangle "unravel-modules/unravel-git.el" (provide 'unravel-git) #+end_src ** The =unravel-org.el= module :PROPERTIES: :CUSTOM_ID: h:d799c3c0-bd6a-40bb-bd1a-ba4ea5367840 :END: Watch these talks by Prot: - [[https://protesilaos.com/codelog/2023-12-18-emacs-org-advanced-literate-conf/][Advanced literate configuration with Org]] (2023-12-18) - [[https://protesilaos.com/codelog/2023-05-23-emacs-org-basics/][Basics of Org mode]] (2023-05-23) - [[https://protesilaos.com/codelog/2021-12-09-emacs-org-block-agenda/][Demo of my custom Org block agenda]] (2021-12-09) - [[https://protesilaos.com/codelog/2020-02-04-emacs-org-capture-intro/][Primer on "org-capture"]] (2020-02-04) #+begin_quote At its core, Org is a plain text markup language. By "markup language", we refer to the use of common characters to apply styling, such as how a word wrapped in asterisks acquires strong emphasis. Check the video I link to above on the basics of Org mode. Though what makes Org powerful is not the markup per se, but the fact that it has a rich corpus of Emacs Lisp code that does a lot with this otherwise plain text notation. Some of the headline features: - Cycle the visibility of any heading and its subheadings. This lets you quickly fold a section you do not need to see (or reveal the one you care about). - Mix prose with code in a single document to either make the whole thing an actual program or to evaluate/demonstrate some snippets. - Convert ("export") an Org file to a variety of formats, including HTML and PDF. - Use LaTeX inside of Org files to produce a scientific paper without all the markup of LaTeX. - Manage TODO lists and implement a concomitant methodology of labelling task states. - Quickly shift a "thing" (heading, list item, paragraph, ...) further up or down in the file. - Use tables with formulas as a lightweight alternative to spreadsheet software. - Capture data or fleeting thoughts efficiently using templates. - Maintain an agenda for all your date-bound activities. - Clock in and out of tasks, to eventually track how you are spending your time. - Link to files regardless of file type. This includes special links such as to an Info manual or an email, if you also have that running locally and integrated with Emacs ([[#h:755e195b-9471-48c7-963b-33055969b4e2][The =unravel-email.el= module]]). In other words, Org is highly capable and widely considered one of the killer apps of Emacs. This section covers the relevant configurations. You will notice that it is not limited to Org, as some other built-in features are also relevant here. #+end_quote *** The =unravel-org.el= section on the ~calendar~ :PROPERTIES: :CUSTOM_ID: h:94d48381-1711-4d6b-8449-918bc1e3836c :END: #+begin_quote The ~calendar~ is technically independent of Org, though it tightly integrates with it. We witness this when we are setting timestamps, such as while setting a =SCHEDULED= or =DEADLINE= entry for a given heading. All I do here is set some stylistic preferences. Note that Emacs also has a ~diary~ command. I used it for a while, but Org is far more capable, so I switched to it completely. #+end_quote #+begin_src emacs-lisp :tangle "unravel-modules/unravel-org.el" :mkdirp yes ;;; Calendar (use-package calendar :ensure nil :commands (calendar) :config (setq calendar-mark-diary-entries-flag nil) (setq calendar-mark-holidays-flag t) (setq calendar-mode-line-format nil) (setq calendar-time-display-form '( 24-hours ":" minutes (when time-zone (format "(%s)" time-zone)))) (setq calendar-week-start-day 1) ; Monday (setq calendar-date-style 'iso) (setq calendar-time-zone-style 'numeric) ; Emacs 28.1 (require 'solar) (setq calendar-latitude 35.17 ; Not my actual coordinates calendar-longitude 33.36) (require 'cal-dst) (setq calendar-standard-time-zone-name "+0200") (setq calendar-daylight-time-zone-name "+0300")) #+end_src *** The =unravel-org.el= section about appointment reminders (=appt.el=) :PROPERTIES: :CUSTOM_ID: h:bd4b0dcb-a925-4bd7-90db-6379a7ca6f5e :END: #+begin_quote The built in =appt.el= defines functionality for handling notifications about appointments. It is originally designed to work with the generic diary feature (the =M-x diary= one, I mean), which I do not use anymore, but also integrates nicely with the Org agenda ([[#h:7fe87b83-2815-4617-a5f9-d3417dd9d248][The =unravel-org.el= Org agenda settings]]). I deepen this integration further, such that after adding a task or changing its state, the appointments mechanism re-reads my data to register new notifications. This is done via a series of hooks and with the use of the advice feature of Emacs Lisp. Here I am setting some simple settings to keep appointment notifations minimal. I do not need them to inform me about the contents of my next entry on the agenda: just show text on the mode line telling me how many minutes are left until the event. In Org files, every heading can have an =APPT_WARNTIME= property: it takes a numeric value representing minutes for a forewarning from =appt.el=. I use this in tandem with ~org-capture~ for tasks that need to be done at a specific time, such as coaching sessions ([[#h:f8f06938-0dfe-45c3-b4cf-996d36cba82d][The =unravel-org.el= Org capture templates (~org-capture~)]]). #+end_quote #+begin_src emacs-lisp :tangle "unravel-modules/unravel-org.el" ;;; Appt (appointment reminders which also integrate with Org agenda) (use-package appt :ensure nil :commands (appt-activate) :config (setq appt-display-diary nil appt-display-format nil appt-display-mode-line t appt-display-interval 3 appt-audible nil ; TODO 2023-01-25: t does nothing because I disable `ring-bell-function'? appt-warning-time-regexp "appt \\([0-9]+\\)" ; This is for the diary appt-message-warning-time 6) (with-eval-after-load 'org-agenda (appt-activate 1) ;; NOTE 2021-12-07: In my `prot-org.el' (see further below), I add ;; `org-agenda-to-appt' to various relevant hooks. ;; ;; Create reminders for tasks with a due date when this file is read. (org-agenda-to-appt))) #+end_src *** The =unravel-org.el= section with basic Org settings :PROPERTIES: :CUSTOM_ID: h:e03df1e0-b43e-49b5-978e-6a511165617c :END: #+begin_quote Org, also known as "Org mode", is one of the potentially most useful feature sets available to every Emacs user. At its core, Org is a lightweight markup language: you can have headings and paragraphs, mark a portion of text with emphasis, produce bullet lists, include code blocks, and the like. Though what really sets Org apart from other markup languages is the rich corpus of Emacs Lisp written around it to do all sorts of tasks with this otherwise plain text format. With Org you can write technical documents (e.g. the manuals of all my Emacs packages), maintain a simple or highly sophisticated system for task management, organise your life using the agenda, write tables that can evaluate formulas to have spreadsheet functionality, have embedded LaTeX, evaluate code blocks in a wide range of programming languages and reuse their results for literate programming, include the contents of other files into a singular file, use one file to generate other files/directories with all their contents, and export the Org document to a variety of formats like =.pdf= and =.odt=. Furthermore, Org can be used as a lightweight, plain text database, as each heading can have its own metadata. This has practical applications in most of the aforementioned. In short, if something can be done with plain text, Org probably does it already or has all the elements for piecing it together. This document, among many of my published works, is testament to Org's sheer power, which I explained at greater length in a video demonstration: [[https://protesilaos.com/codelog/2023-12-18-emacs-org-advanced-literate-conf/][Advanced literate configuration with Org]] (2023-12-18). This being Emacs, everything is customisable and Org is a good example of this. There are a lot of user options for us to tweak things to our liking. I do as much, though know that Org is perfectly usable without any configuration. The following sections contain further commentary on how I use Org. #+end_quote #+begin_src emacs-lisp :tangle "unravel-modules/unravel-org.el" ;;; Org-mode (personal information manager) (use-package org :ensure nil :init (setq org-directory (expand-file-name "~/Documents/org/")) (setq org-imenu-depth 7) (add-to-list 'safe-local-variable-values '(org-hide-leading-stars . t)) (add-to-list 'safe-local-variable-values '(org-hide-macro-markers . t)) :bind ( :map global-map ("C-c l" . org-store-link) ("C-c o" . org-open-at-point-global) :map org-mode-map ;; I don't like that Org binds one zillion keys, so if I want one ;; for something more important, I disable it from here. ("C-'" . nil) ("C-," . nil) ("M-;" . nil) ("C-c M-l" . org-insert-last-stored-link) ("C-c C-M-l" . org-toggle-link-display) ("M-." . org-edit-special) ; alias for C-c ' (mnenomic is global M-. that goes to source) :map org-src-mode-map ("M-," . org-edit-src-exit) ; see M-. above :map narrow-map ("b" . org-narrow-to-block) ("e" . org-narrow-to-element) ("s" . org-narrow-to-subtree) :map ctl-x-x-map ("i" . prot-org-id-headlines) ("h" . prot-org-ox-html)) :config ;; My custom extras, which I use for the agenda and a few other Org features. (require 'prot-org) ;;;; general settings (setq org-ellipsis "⮧") (setq org-adapt-indentation nil) ; No, non, nein, όχι! (setq org-special-ctrl-a/e nil) (setq org-special-ctrl-k nil) (setq org-M-RET-may-split-line '((default . nil))) (setq org-hide-emphasis-markers nil) (setq org-hide-macro-markers nil) (setq org-hide-leading-stars nil) (setq org-cycle-separator-lines 0) (setq org-structure-template-alist '(("s" . "src") ("e" . "src emacs-lisp") ("E" . "src emacs-lisp :results value code :lexical t") ("t" . "src emacs-lisp :tangle FILENAME") ("T" . "src emacs-lisp :tangle FILENAME :mkdirp yes") ("x" . "example") ("X" . "export") ("q" . "quote"))) (setq org-fold-catch-invisible-edits 'show) (setq org-return-follows-link nil) (setq org-loop-over-headlines-in-active-region 'start-level) (setq org-modules '(ol-info ol-eww)) (setq org-use-sub-superscripts '{}) (setq org-insert-heading-respect-content t) (setq org-read-date-prefer-future 'time) (setq org-highlight-latex-and-related nil) ; other options affect elisp regexp in src blocks (setq org-fontify-quote-and-verse-blocks t) (setq org-fontify-whole-block-delimiter-line t) (setq org-track-ordered-property-with-tag t) (setq org-highest-priority ?A) (setq org-lowest-priority ?C) (setq org-default-priority ?A) (setq org-priority-faces nil)) #+end_src *** The =unravel-org.el= Org to-do and refile settings :PROPERTIES: :CUSTOM_ID: h:024dd541-0061-4a10-b10b-b17dcd4794b9 :END: #+begin_quote One of the many use-cases for Org is to maintain a plain text to-do list. A heading that starts with a to-do keyword, such as =TODO= is treated as a task and its state is considered not completed. We can switch between the task states with shift and the left or right arrow keys. Or we can select a keyword directly with =C-c C-t=, which calls ~org-todo~ by default. I personally prefer the latter approach, as it is more precise. Whenever a task state changes, we can log that event in a special =LOGBOOK= drawer. This is automatically placed right below the heading, before any paragraph text. Logging data is an opt-in feature, which I consider helpful ([[#h:0884658e-9eb5-47e3-9338-66e09004a1a0][The =unravel-org.el= Org time/state logging]]). Tasks can be associated with timestamps, typically a scheduled date+time or a deadline+time. This can be helpful when we are reviewing the source Org file, though it really shines in tandem with the agenda. Any heading that has a timestamp and which belongs to a file in the ~org-agenda-files~ will show up on the agenda in the given date ([[#h:7fe87b83-2815-4617-a5f9-d3417dd9d248][The =unravel-org.el= Org agenda settings]]). By default, the ~org-todo-keywords~ are =TODO= and =DONE=. We can write more keywords if we wish to implement a descriptive workflow. For example, we can have a =WAIT= keyword for something that is to be done but is not actionable yet. While the number of keywords is not limited, the binary model is the same: we have words that represent the incomplete state and those that count as the completion of the task. For instance, both =CANCEL= and =DONE= mean that the task is not actionable anymore and we move on to other things. As such, the extra keywords are a way for the user to make tasks more descriptive and easy to find. In the value of the ~org-todo-keywords~, we use the bar character to separate the incomplete state to the left from the completed one to the right. One of the agenda's headiline features is the ability to produce a view that lists headings with the given keyword. So having the right terms can make search and retrieval of data more easy. On the flip-side, too many keywords add cognitive load and require more explicit search terms to yield the desired results. I used to work with a more descriptive set of keywords, but ultimately decided to keep things simple. The refile mechanism is how we can reparent a heading, by moving it from one place to another. We do this with the command ~org-refile~, bound to =C-c C-w= by default. A common workflow where refiling is essential is to have an "inbox" file or heading, where unprocessed information is stored at, and periodically process its contents to move the data where it belongs. Though it can also work fine without any such inbox, in those cases where a heading should be stored someplace else. The ~org-refile-targets~ specifies the files that are available when we try to refile the current heading. With how I set it up, all the agenda files plus the current file's headings up to level 2 are included as possible targets. In terms of workflow, I have not done a refile in a very long time, because my entries always stay in the same place as I had envisaged at the capture phase ([[#h:f8f06938-0dfe-45c3-b4cf-996d36cba82d][The =unravel-org.el= Org capture templates (~org-capture~)]]). #+end_quote #+begin_src emacs-lisp :tangle "unravel-modules/unravel-org.el" ;;;; refile, todo (use-package org :ensure nil :config (setq org-refile-targets '((org-agenda-files . (:maxlevel . 2)) (nil . (:maxlevel . 2)))) (setq org-refile-use-outline-path t) (setq org-refile-allow-creating-parent-nodes 'confirm) (setq org-refile-use-cache t) (setq org-reverse-note-order nil) ;; ;; NOTE 2023-04-07: Leaving this here for demo purposes. ;; (setq org-todo-keywords ;; '((sequence "TODO(t)" "MAYBE(m)" "WAIT(w@/!)" "|" "CANCEL(c@)" "DONE(d!)") ;; (sequence "COACH(k)" "|" "COACHED(K!)"))) (setq org-todo-keywords '((sequence "TODO(t)" "|" "CANCEL(c@)" "DONE(d!)") (sequence "COACH(k)" "|" "COACHED(K!)"))) (defface prot/org-bold-done '((t :inherit (bold org-done))) "Face for bold DONE-type Org keywords.") (setq org-todo-keyword-faces '(("CANCEL" . prot/org-bold-done))) (setq org-use-fast-todo-selection 'expert) (setq org-fontify-done-headline nil) (setq org-fontify-todo-headline nil) (setq org-fontify-whole-heading-line nil) (setq org-enforce-todo-dependencies t) (setq org-enforce-todo-checkbox-dependencies t)) #+end_src *** The =unravel-org.el= Org heading tags :PROPERTIES: :CUSTOM_ID: h:81de4e32-a1af-4e1f-9e10-90eb0c90afa2 :END: Each Org heading can have one or more tags associated with it, while all headings inherit any potential =#+FILETAGS=. We can add tags to a heading when the cursor is over it by typing the ever flexible =C-c C-c=. Though the more specific ~org-set-tags-command~ also gets the job done, plus it does not require that the cursor is positioned on the heading text. Tagging is useful for searching and retrieving the data we store. The Org agenda, in particular, provides commands to filter tasks by tag: - [[#h:024dd541-0061-4a10-b10b-b17dcd4794b9][The =unravel-org.el= Org to-do and refile settings]] - [[#h:7fe87b83-2815-4617-a5f9-d3417dd9d248][The =unravel-org.el= Org agenda settings]] The user option ~org-tag-alist~ lets us specify tags we always want to use, though we can write tags per file as well by using the =#+TAGS= keyword. I do the latter as a global list of tags is not useful in my case. For example, when I wan checking my =coach.org= file for the coaching sessions I provide, I do not need to see any of the tags that make sense in my general =tasks.org=. Note that in the settings below I disable the auto-alignment that Org does where it shifts tags to the right of the heading. I do not like it. #+begin_src emacs-lisp :tangle "unravel-modules/unravel-org.el" ;;;; tags (use-package org :ensure nil :config (setq org-tag-alist nil) (setq org-auto-align-tags nil) (setq org-tags-column 0)) #+end_src *** The =unravel-org.el= Org time/state logging :PROPERTIES: :CUSTOM_ID: h:0884658e-9eb5-47e3-9338-66e09004a1a0 :END: Org can keep a record of state changes, such as when we set an entry marked with the =TODO= keyword as =DONE= or when we reschedule an appointment ([[#h:7fe87b83-2815-4617-a5f9-d3417dd9d248][The =unravel-org.el= Org agenda settings]]). This data is stored in a =LOGBOOK= drawer right below the heading. I choose to keep track of this information, as it is sometimes useful to capture mistakes or figure out intent in the absence of further clarification (though I do tend to write why something happened). #+begin_src emacs-lisp :tangle "unravel-modules/unravel-org.el" ;;;; log (use-package org :ensure nil :config (setq org-log-done 'time) (setq org-log-into-drawer t) (setq org-log-note-clock-out nil) (setq org-log-redeadline 'time) (setq org-log-reschedule 'time)) #+end_src *** The =unravel-org.el= Org link settings :PROPERTIES: :CUSTOM_ID: h:da8ce883-7f21-4a6e-a41f-d668ad762b41 :END: One of the nice things about Org is its flexible linking mechanism. It can produce links to a variety of file types or buffers and even navigate to a section therein. At its simplest form, we have the =file= link type, which points to a file system path, with an optional extension for a match inside the file, as documented in the manual. Evaluate this inside of Emacs: #+begin_example emacs-lisp (info "(org) Search Options") #+end_example Links to buffers are also common and valuable. For example, we can have a link to a page produced by the ~man~ command, which gives us quick access to the documentation of some program. When Org follows that link, it opens the buffer in the appropriate major mode. For me, the most common scenario is a link to an email, which I typically associate with a task that shows up in my agenda: - [[#h:f8f06938-0dfe-45c3-b4cf-996d36cba82d][The =unravel-org.el= Org capture templates (~org-capture~)]] - [[#h:49890997-448e-408d-bebe-2003259bb125][The =unravel-notmuch.el= glue code for ~org-capture~ (=ol-notmuch.el=)]] Org supports lots of link types out-of-the-box, though more can be added by packages. My Denote does this: it defines a =denote= link type which behaves the same way as the =file= type except that it uses the identifier of the file instead of its full path (so eve if the file is renamed, the link will work for as long as the identifier remains the same). Links can be generated automatically as part of an ~org-capture~ template. The command ~org-store-link~ produces one manually, storing it to a special data structure from which it can be retrieved later for insertion with the command ~org-insert-link~. The latter command can also create new links, simply by receiving data that is different from what was already stored. I bind ~org-store-link~ in main section of the Org configuration: [[#h:e03df1e0-b43e-49b5-978e-6a511165617c][The =unravel-org.el= section with basic Org settings]]. #+begin_src emacs-lisp :tangle "unravel-modules/unravel-org.el" ;;;; links (use-package org :ensure nil :config (require 'prot-org) ; for the above commands (setq org-link-context-for-files t) (setq org-link-keep-stored-after-insertion nil) (setq org-id-link-to-org-use-id 'create-if-interactive-and-no-custom-id)) #+end_src *** The =unravel-org.el= Org code block settings :PROPERTIES: :CUSTOM_ID: h:1f5a0d46-5202-48dd-8048-b48ce17f3df8 :END: This document benefits from Org's ability to combine prose with code, by placing the latter inside of a block that is delimited by =#+BEGIN_SRC= and =#+END_SRC= lines. Code blocks can use the syntax highlighting ("fontification" in Emacs parlance) of a given major mode. They can also have optional parameters passed to their header, which expand the capabilities of the block. For instance, the following code block with my actual configuration uses the fontification of the ~emacs-lisp-mode~ and has a =:tangle= parameter with a value of a file system path. When I invoke the command ~org-babel-tangle~, the contents of this block will be added to that file, creating the file if necessary. More generally, Org is capable of evaluating code blocks and passing their return value to other code blocks. It is thus possible to write a fully fledged program as an Org document. This paradigm is known as "literate programming". In the case of an Emacs configuration, such as mine, it is called a "literate configuration" or variants thereof. I did a video about my setup: [[https://protesilaos.com/codelog/2023-12-18-emacs-org-advanced-literate-conf/][Advanced literate configuration with Org]] (2023-12-18). Org can evaluate code blocks in many languages. This is known as "Org Babel" and the files which implement support for a given language are typically named =ob-LANG.el= where =LANG= is the name of the language. We can load the requisite code for the languages we care about with something like the following: #+begin_example emacs-lisp (require 'ob-python) ;; OR (use-package ob-python) ;; OR for more control (use-package ob-python :after org :config ;; Settings here ) #+end_example I seldom need to work with Org Babel, so I do not load any language automatically. Note that Emacs Lisp is loaded by default. To evaluate a code block, we type Org's omnipotent =C-c C-c=. The results will be produced below the code block. There is an optional parameter that controls how---or even if---the results are displayed. There are many other types of block apart from =SRC=. Those do different things, such as: - =#+BEGIN_QUOTE= :: Treat the contents as a block quote or equivalent. - =#+BEGIN_VERSE= :: Do not reflow any like breaks (for poetry and such). - =#+BEGIN_EXPORT= :: Evaluate the code for the given export target (like =html= or =latex=), optionally replacing it with its results or keeping both of them ([[#h:bd11d4d8-6e9f-4536-87a4-4018783bf8f5][The =unravel-org.el= Org export settings]]). This is a wonderful world of possibilities! #+begin_src emacs-lisp :tangle "unravel-modules/unravel-org.el" ;;;; code blocks (use-package org :ensure nil :config (setq org-confirm-babel-evaluate nil) (setq org-src-window-setup 'current-window) (setq org-edit-src-persistent-message nil) (setq org-src-fontify-natively t) (setq org-src-preserve-indentation t) (setq org-src-tab-acts-natively t) (setq org-edit-src-content-indentation 0)) #+end_src *** The =unravel-org.el= Org export settings :PROPERTIES: :CUSTOM_ID: h:bd11d4d8-6e9f-4536-87a4-4018783bf8f5 :END: Org is a capable authoring tool in no small part because it can be converted to other file formats. A typical example is to write a technical document in Org and then export it to a PDF. Another use-case is what I commonly do with the Emacs packages I maintain, which I export to an Info manual (texinfo format) and an HTML web page. The default set of export targets is specified in the value of the user option ~org-export-backends~. It is one of those rare cases where it has to be evaluated before the package is loaded. Other than that, we can load an export backend by finding the correspond =ox-FORMAT.el= file and either ~require~ it or load it with ~use-package~, like what I showed for Org Babel ([[#h:1f5a0d46-5202-48dd-8048-b48ce17f3df8][The =unravel-org.el= Org code block settings]]). #+begin_src emacs-lisp :tangle "unravel-modules/unravel-org.el" ;;;; export (use-package org :ensure nil :init ;; NOTE 2023-05-20: Must be evaluated before Org is loaded, ;; otherwise we have to use the Custom UI. No thanks! (setq org-export-backends '(html texinfo md)) :config (setq org-export-with-toc t) (setq org-export-headline-levels 8) (setq org-export-dispatch-use-expert-ui nil) (setq org-html-htmlize-output-type nil) (setq org-html-head-include-default-style nil) (setq org-html-head-include-scripts nil)) #+end_src *** The =unravel-org.el= Org capture templates (~org-capture~) :PROPERTIES: :CUSTOM_ID: h:f8f06938-0dfe-45c3-b4cf-996d36cba82d :END: The ~org-capture~ command allows us to quickly store data in some structured way. This is done with the help of a templating system where we can, for example, record the date the entry was recorded, prompt for user input, automatically use the email's subject as the title of the task, and the like. The documentation string of ~org-capture-templates~ covers the technicalities. I use two Org files for my tasks. The one is =tasks.org=, which contains the bulk of my entries. The other is =coach.org=, which is specific to my coaching work: https://protesilaos.com/coach. The =tasks.org= consists of several top-level headings. Each contains subheadings I need to review. You will notice how most of my entries in ~org-capture-templates~ involve this file. With Org, it is perfectly fine to work in a single file because we can fold headings or narrow to them with ~org-narrow-to-subtree~. Furthermore, we can navigate directly to a heading using minibuffer completion, such as with the general purpose command ~prot-search-outline~ ([[#h:b902e6a3-cdd2-420f-bc99-3d973c37cd20][The =unravel-search.el= extras provided by the =prot-search.el= library]]). Despite the fact that Org copes well with large files, I still choose to keep my coaching work in a separate file as a contingency plan. Because =coach.org= includes information about appointments, I need to be able to read it with ease from anywhere. This includes different types of hardware, but also any kind of generic text editor or terminal pager. I do not want to depend on features like folding, narrowing, and the like, in times when something has gone awry. Granted, this has never happened, though the idea makes sense. Besides, two files are not hard to manage in this case. The =coach.org= has a simple structure: each appointment is stored as a top-level heading. As for my workflow, here is an overview: - When I want to capture data that I am not yet sure about, I add it to the =tasks.org= "Unprocessed" heading. I periodically review those to decide if I want to do something with them or not. If I do not want them, I delete them. Otherwise, I file them under another heading in the same file using the ~org-refile~ command ([[#h:024dd541-0061-4a10-b10b-b17dcd4794b9][The =unravel-org.el= Org to-do and refile settings]]). Not everything goes into the "Unprocessed" headings, as I often known in advance what an item is about. This is just a fallback for those cases when I need more information to decide on the appropriate action. - Tasks that have an inherent time component are given a =SCHEDULED= or =DEADLINE= timestamp (set those on demand with the commands ~org-schedule~ and ~org-deadline~, respectively). These are the only tasks I want to see on my daily agenda ([[#h:7fe87b83-2815-4617-a5f9-d3417dd9d248][The =unravel-org.el= Org agenda settings]]). The difference between =SCHEDULED= and =DEADLINE= is that the former has no strict start or end time and so is flexible, while the latter is more rigid. For example, "visit the vet today" does not have a strict time associated with it because the doctor often deals with emergency situations and thus their agenda is fluid. While a coaching session of mine like "work on Emacs with PERSON" has to start at the agreed upon time. - I do not arbitrarily assign timestamps to tasks. If something does not have a scheduled date or a deadline, then it does not belong on the agenda. Otherwise, those arbitrarily defined "events" accumulate in the agenda and crowd out the actual time-sensitive tasks. As a result, the cognitive load is heavier and things will not be done. So when I want to do something at some point, but have no specific plan for it, I add is to the =tasks.org= "Wishlist". When I have free time, I review my wishlist and pick something to work on from there depending on my available time and mood. This keeps my workflow both focused and stress-free. - Finally, my =coach.org= only has time-sensitive appointments with a =DEADLINE= associated with them. I organise the rest of my activities in the given day based on those. #+begin_src emacs-lisp :tangle "unravel-modules/unravel-org.el" ;;;; capture (use-package org-capture :ensure nil :bind ("C-c c" . org-capture) :config (require 'prot-org) (setq org-capture-templates `(("u" "Unprocessed" entry (file+headline "tasks.org" "Unprocessed") ,(concat "* %^{Title}\n" ":PROPERTIES:\n" ":CAPTURED: %U\n" ":END:\n\n" "%a\n%i%?") :empty-lines-after 1) ;; ("e" "Email note (unprocessed)" entry ; Also see `org-capture-templates-contexts' ;; (file+headline "tasks.org" "Unprocessed") ;; ,(concat "* TODO %:subject :mail:\n" ;; ":PROPERTIES:\n" ;; ":CAPTURED: %U\n" ;; ":END:\n\n" ;; "%a\n%i%?") ;; :empty-lines-after 1) ("w" "Add to the wishlist (may do some day)" entry (file+headline "tasks.org" "Wishlist") ,(concat "* %^{Title}\n" ":PROPERTIES:\n" ":CAPTURED: %U\n" ":END:\n\n" "%a%?") :empty-lines-after 1) ("c" "Clock in and do immediately" entry (file+headline "tasks.org" "Clocked tasks") ,(concat "* TODO %^{Title}\n" ":PROPERTIES:\n" ":EFFORT: %^{Effort estimate in minutes|5|10|15|30|45|60|90|120}\n" ":END:\n\n" "%a\n") :prepend t :clock-in t :clock-keep t :immediate-finish t :empty-lines-after 1) ("t" "Time-sensitive task" entry (file+headline "tasks.org" "Tasks with a date") ,(concat "* TODO %^{Title} %^g\n" "%^{How time sensitive it is||SCHEDULED|DEADLINE}: %^t\n" ":PROPERTIES:\n" ":CAPTURED: %U\n" ":END:\n\n" "%a%?") :empty-lines-after 1) ("p" "Private lesson or service" entry (file "coach.org") #'prot-org-capture-coach :prepend t :empty-lines 1) ("P" "Private service clocked" entry (file+headline "coach.org" "Clocked services") #'prot-org-capture-coach-clock :prepend t :clock-in t :clock-keep t :immediate-finish t :empty-lines 1))) ;; NOTE 2024-11-10: I realised that I was not using this enough, so ;; I decided to simplify my setup. Keeping it here, in case I need ;; it again. ;; (setq org-capture-templates-contexts ;; '(("e" ((in-mode . "notmuch-search-mode") ;; (in-mode . "notmuch-show-mode") ;; (in-mode . "notmuch-tree-mode"))))) ) #+end_src *** The =unravel-org.el= Org agenda settings :PROPERTIES: :CUSTOM_ID: h:7fe87b83-2815-4617-a5f9-d3417dd9d248 :END: [ Watch: [[https://protesilaos.com/codelog/2021-12-09-emacs-org-block-agenda/][Demo of my custom Org block agenda]] (2021-12-09). It has changed a bit since then, but the idea is the same. ] With the Org agenda, we can visualise the tasks we have collected in our Org files or, more specifically, in the list of files specified in the user option ~org-agenda-files~. In my workflow, only the files in the ~org-directory~ can feed data into the agenda. Though Org provides commands to add/remove the current file on demand: ~org-remove-file~, and ~org-agenda-file-to-front~. If I ever need to write a task that is specific to a certain file or buffer, then I use Org's linking mechanism to point to the relevant context, but otherwise store my task in the usual place ([[#h:f8f06938-0dfe-45c3-b4cf-996d36cba82d][The =unravel-org.el= Org capture templates (~org-capture~)]]). By default, Org provides many so-called "views" for the agenda. One of the them is the daily/weekly agenda. Others show only the headings with =TODO= keywords, or some other kind of search criteria. I personally never use those views. I have my own custom agenda view, which consolidates in a single buffer the following blocks on data, in this order ([[#h:9974eac8-2167-45c4-90e0-12dd877403da][The =prot-org.el= library]]).: - Important tasks without a date :: When I add a top priority to something, but there is no inherent deadline to it. - Pending scheduled tasks :: Tasks with a =SCHEDULED= date may sometimes not be done when they ought to. So they need to be closer to the top for me to do them as soon as I can. - Today's agenda :: What I am actually working on. Because I only assign a timestamp to tasks that are indeed time-sensitive, this always reflects the commitments I have for the day. - Next three days :: Like the above, but for the near future. - Upcoming deadlines (+14d) :: These are the deadlines I need to be aware of for the 14 days after the next three days I am only informed about. The Org agenda has lots of other extras, such as to filter the view. Though I never use them. My custom agenda does exactly what I need from it and thus keeps me focused. #+begin_src emacs-lisp :tangle "unravel-modules/unravel-org.el" ;;;; agenda (use-package org-agenda :ensure nil :bind ;; I bind `org-agenda' to C-c A, so this one puts me straight into my ;; custom block agenda. ( :map global-map ("C-c A" . org-agenda) ("C-c a" . (lambda () "Call Org agenda with `prot-org-custom-daily-agenda' configuration." (interactive) (org-agenda nil "A")))) :config ;;;;; Custom agenda blocks (setq org-agenda-format-date #'prot-org-agenda-format-date-aligned) ;; Check the variable `prot-org-custom-daily-agenda' in prot-org.el (setq org-agenda-custom-commands `(("A" "Daily agenda and top priority tasks" ,prot-org-custom-daily-agenda ((org-agenda-fontify-priorities nil) (org-agenda-prefix-format " %t %s") (org-agenda-dim-blocked-tasks nil))) ("P" "Plain text daily agenda and top priorities" ,prot-org-custom-daily-agenda ((org-agenda-with-colors nil) (org-agenda-prefix-format "%t %s") (org-agenda-current-time-string ,(car (last org-agenda-time-grid))) (org-agenda-fontify-priorities nil) (org-agenda-remove-tags t)) ("agenda.txt")))) ;;;;; Basic agenda setup (setq org-default-notes-file (make-temp-file "emacs-org-notes-")) ; send it to oblivion (setq org-agenda-files `(,org-directory)) (setq org-agenda-span 'week) (setq org-agenda-start-on-weekday 1) ; Monday (setq org-agenda-confirm-kill t) (setq org-agenda-show-all-dates t) (setq org-agenda-show-outline-path nil) (setq org-agenda-window-setup 'current-window) (setq org-agenda-skip-comment-trees t) (setq org-agenda-menu-show-matcher t) (setq org-agenda-menu-two-columns nil) (setq org-agenda-sticky nil) (setq org-agenda-custom-commands-contexts nil) (setq org-agenda-max-entries nil) (setq org-agenda-max-todos nil) (setq org-agenda-max-tags nil) (setq org-agenda-max-effort nil) ;;;;; General agenda view options ;; NOTE 2021-12-07: Check further below my `org-agenda-custom-commands' (setq org-agenda-prefix-format '((agenda . " %i %-12:c%?-12t% s") (todo . " %i %-12:c") (tags . " %i %-12:c") (search . " %i %-12:c"))) (setq org-agenda-sorting-strategy '(((agenda habit-down time-up priority-down category-keep) (todo priority-down category-keep) (tags priority-down category-keep) (search category-keep)))) (setq org-agenda-breadcrumbs-separator "->") (setq org-agenda-todo-keyword-format "%-1s") (setq org-agenda-fontify-priorities 'cookies) (setq org-agenda-category-icon-alist nil) (setq org-agenda-remove-times-when-in-prefix nil) (setq org-agenda-remove-timeranges-from-blocks nil) (setq org-agenda-compact-blocks nil) (setq org-agenda-block-separator ?—) ;;;;; Agenda marks (setq org-agenda-bulk-mark-char "#") (setq org-agenda-persistent-marks nil) ;;;;; Agenda diary entries (setq org-agenda-insert-diary-strategy 'date-tree) (setq org-agenda-insert-diary-extract-time nil) (setq org-agenda-include-diary nil) ;; I do not want the diary, but there is no way to disable it ;; altogether. This creates a diary file in the /tmp directory. (setq diary-file (make-temp-file "emacs-diary-")) (setq org-agenda-diary-file 'diary-file) ; TODO 2023-05-20: review Org diary substitute ;;;;; Agenda follow mode (setq org-agenda-start-with-follow-mode nil) (setq org-agenda-follow-indirect t) ;;;;; Agenda multi-item tasks (setq org-agenda-dim-blocked-tasks t) (setq org-agenda-todo-list-sublevels t) ;;;;; Agenda filters and restricted views (setq org-agenda-persistent-filter nil) (setq org-agenda-restriction-lock-highlight-subtree t) ;;;;; Agenda items with deadline and scheduled timestamps (setq org-agenda-include-deadlines t) (setq org-deadline-warning-days 0) (setq org-agenda-skip-scheduled-if-done nil) (setq org-agenda-skip-scheduled-if-deadline-is-shown t) (setq org-agenda-skip-timestamp-if-deadline-is-shown t) (setq org-agenda-skip-deadline-if-done nil) (setq org-agenda-skip-deadline-prewarning-if-scheduled 1) (setq org-agenda-skip-scheduled-delay-if-deadline nil) (setq org-agenda-skip-additional-timestamps-same-entry nil) (setq org-agenda-skip-timestamp-if-done nil) (setq org-agenda-search-headline-for-time nil) (setq org-scheduled-past-days 365) (setq org-deadline-past-days 365) (setq org-agenda-move-date-from-past-immediately-to-today t) (setq org-agenda-show-future-repeats t) (setq org-agenda-prefer-last-repeat nil) (setq org-agenda-timerange-leaders '("" "(%d/%d): ")) (setq org-agenda-scheduled-leaders '("Scheduled: " "Sched.%2dx: ")) (setq org-agenda-inactive-leader "[") (setq org-agenda-deadline-leaders '("Deadline: " "In %3d d.: " "%2d d. ago: ")) ;; Time grid (setq org-agenda-time-leading-zero t) (setq org-agenda-timegrid-use-ampm nil) (setq org-agenda-use-time-grid t) (setq org-agenda-show-current-time-in-grid t) (setq org-agenda-current-time-string (concat "Now " (make-string 70 ?.))) (setq org-agenda-time-grid '((daily today require-timed) ( 0500 0600 0700 0800 0900 1000 1100 1200 1300 1400 1500 1600 1700 1800 1900 2000 2100 2200) "" "")) (setq org-agenda-default-appointment-duration nil) ;;;;; Agenda global to-do list (setq org-agenda-todo-ignore-with-date t) (setq org-agenda-todo-ignore-timestamp t) (setq org-agenda-todo-ignore-scheduled t) (setq org-agenda-todo-ignore-deadlines t) (setq org-agenda-todo-ignore-time-comparison-use-seconds t) (setq org-agenda-tags-todo-honor-ignore-options nil) ;;;;; Agenda tagged items (setq org-agenda-show-inherited-tags t) (setq org-agenda-use-tag-inheritance '(todo search agenda)) (setq org-agenda-hide-tags-regexp nil) (setq org-agenda-remove-tags nil) (setq org-agenda-tags-column -100) ;;;;; Agenda entry ;; NOTE: I do not use this right now. Leaving everything to its ;; default value. (setq org-agenda-start-with-entry-text-mode nil) (setq org-agenda-entry-text-maxlines 5) (setq org-agenda-entry-text-exclude-regexps nil) (setq org-agenda-entry-text-leaders " > ") ;;;;; Agenda logging and clocking ;; NOTE: I do not use these yet, though I plan to. Leaving everything ;; to its default value for the time being. (setq org-agenda-log-mode-items '(closed clock)) (setq org-agenda-clock-consistency-checks '((:max-duration "10:00" :min-duration 0 :max-gap "0:05" :gap-ok-around ("4:00") :default-face ; This should definitely be reviewed ((:background "DarkRed") (:foreground "white")) :overlap-face nil :gap-face nil :no-end-time-face nil :long-face nil :short-face nil))) (setq org-agenda-log-mode-add-notes t) (setq org-agenda-start-with-log-mode nil) (setq org-agenda-start-with-clockreport-mode nil) (setq org-agenda-clockreport-parameter-plist '(:link t :maxlevel 2)) (setq org-agenda-search-view-always-boolean nil) (setq org-agenda-search-view-force-full-words nil) (setq org-agenda-search-view-max-outline-level 0) (setq org-agenda-search-headline-for-time t) (setq org-agenda-use-time-grid t) (setq org-agenda-cmp-user-defined nil) (setq org-agenda-sort-notime-is-late t) ; Org 9.4 (setq org-agenda-sort-noeffort-is-high t) ; Org 9.4 ;;;;; Agenda column view ;; NOTE I do not use these, but may need them in the future. (setq org-agenda-view-columns-initially nil) (setq org-agenda-columns-show-summaries t) (setq org-agenda-columns-compute-summary-properties t) (setq org-agenda-columns-add-appointments-to-effort-sum nil) (setq org-agenda-auto-exclude-function nil) (setq org-agenda-bulk-custom-functions nil) ;; ;;;;; Agenda habits ;; (require 'org-habit) ;; (setq org-habit-graph-column 50) ;; (setq org-habit-preceding-days 9) ;; ;; Always show the habit graph, even if there are no habits for ;; ;; today. ;; (setq org-habit-show-all-today t) ) #+end_src #+begin_src emacs-lisp :tangle "unravel-modules/unravel-org.el" (use-package prot-coach :ensure nil :commands (prot-coach-done-sessions-with-person)) #+end_src *** The =unravel-org.el= call to ~provide~ :PROPERTIES: :CUSTOM_ID: h:62eb7ca3-2f79-45a6-a018-38238b486e98 :END: Finally, we ~provide~ the module. This is the mirror function of ~require~ ([[#h:e6c4acf5-5b51-4b38-a86a-bf3f698ac872][The init.el final part to load the individual modules]]). #+begin_src emacs-lisp :tangle "unravel-modules/unravel-org.el" (provide 'unravel-org) #+end_src