3900 lines
151 KiB
Org Mode
3900 lines
151 KiB
Org Mode
#+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.
|
||
|
||
I use emacs on Mac OSX as my primary development environment. I install the latest available pre-compiled Emacs (with native compilation), as provided by Jimeh here: [[https://github.com/jimeh/emacs-builds?tab=readme-ov-file][jimeh/emacs-builds]].
|
||
|
||
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) 'large)))))
|
||
: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 "Iosevka"
|
||
: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 ~show-font~
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: h:60a005be-77bd-49f1-a865-78d7cf75bd2a
|
||
:END:
|
||
|
||
#+begin_quote
|
||
This is yet another package of mine. It lets you preview a font inside
|
||
of Emacs. It does so in three ways:
|
||
|
||
- Prompt for a font on the system and display it in a buffer.
|
||
- List all known fonts in a buffer, with a short preview for each.
|
||
- Provide a major mode to preview a font whose file is among the
|
||
installed ones.
|
||
#+end_quote
|
||
|
||
Prot is the developer and maintainer of this package.
|
||
|
||
+ Package name (GNU ELPA): ~show-font~
|
||
+ Official manual: <https://protesilaos.com/emacs/show-font>
|
||
+ Change log: <https://protesilaos.com/emacs/show-font-changelog>
|
||
+ Git repository: <https://github.com/protesilaos/show-font>
|
||
|
||
To actually set fonts, use the ~fontaine~ package ([[#h:cb41fef0-41a5-4a85-9552-496d96290258][The =unravel-theme.el= section about ~fontaine~]]).
|
||
|
||
#+begin_src emacs-lisp :tangle "unravel-modules/unravel-theme.el"
|
||
;;;; Show Font (preview fonts)
|
||
;; Read the manual: <https://protesilaos.com/emacs/show-font>
|
||
(use-package show-font
|
||
:ensure t
|
||
:commands (show-font-select-preview show-font-list)
|
||
:config
|
||
;; These are the defaults, but I keep them here for easier access.
|
||
(setq show-font-pangram 'prot)
|
||
(setq show-font-character-sample
|
||
"
|
||
ABCDEFGHIJKLMNOPQRSTUVWXYZ
|
||
abcdefghijklmnopqrstuvwxyz
|
||
0123456789 !@#$¢%^&*~|
|
||
`'\"‘’“”.,;: ()[]{}—-_+=<>
|
||
|
||
()[]{}<>«»‹› 6bB8&0ODdoa 1tiIlL|\/
|
||
!ij c¢ 5$Ss 7Z2z 9gqp nmMNNMW uvvwWuuw
|
||
x×X .,·°;:¡!¿?`'‘’ ÄAÃÀ TODO
|
||
"))
|
||
#+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 =unravel-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 =unravel-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
|
||
|
||
** The =unravel-essentials.el= section for OSX changes
|
||
|
||
These are modifications to basic configuration I use on my Mac OSX machine.
|
||
|
||
#+begin_src emacs-lisp :tangle "unravel-modules/unravel-essentials.el"
|
||
;;;; Configuration on Mac OS X machine
|
||
(when (eq system-type 'darwin)
|
||
(use-package ns-win
|
||
:ensure nil
|
||
:config
|
||
(defun copy-from-osx ()
|
||
"Make cut and paste work with the OS X clipboard"
|
||
(shell-command-to-string "pbpaste"))
|
||
|
||
(defun paste-to-osx (text &optional push)
|
||
"Make cut and paste work with the OS X clipboard"
|
||
(let ((process-connection-type nil))
|
||
(let ((proc (start-process "pbcopy" "*Messages*" "pbcopy")))
|
||
(process-send-string proc text)
|
||
(process-send-eof proc))))
|
||
|
||
(setq mac-command-modifier 'meta)
|
||
(setq mac-option-modifier 'alt)
|
||
(setq interprogram-cut-function #'paste-to-osx)
|
||
(setq interprogram-paste-function #'copy-from-osx)
|
||
;; Work around a bug on OS X where system-name is a fully qualified
|
||
;; domain name
|
||
(setq system-name (car (split-string system-name "\\.")))
|
||
;;; Binaries
|
||
(setq vc-git-program (or (executable-find "git") "/usr/local/bin/git"))
|
||
(setq epg-gpg-program (or (executable-find "gpg") "/usr/local/bin/gpg"))
|
||
;;; Source dirs
|
||
;; Note: These are hard-coded to my machine.
|
||
(setq source-directory (expand-file-name "~/src/emacs/src/"))
|
||
(setq find-function-C-source-directory (expand-file-name "~/src/emacs/src/"))))
|
||
#+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 =unravel-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 =unravel-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 =unravel-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
|
||
;; Prot's bindings
|
||
("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-fd)
|
||
("M-s M-g" . consult-ripgrep)
|
||
("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)
|
||
;; Overriding defaults: C-x bindings in `ctl-x-map'
|
||
("C-x M-:" . consult-complex-command) ;; orig. repeat-complex-command
|
||
("C-x b" . consult-buffer) ;; orig. switch-to-buffer
|
||
("C-x 4 b" . consult-buffer-other-window) ;; orig. switch-to-buffer-other-window
|
||
("C-x 5 b" . consult-buffer-other-frame) ;; orig. switch-to-buffer-other-frame
|
||
("C-x t b" . consult-buffer-other-tab) ;; orig. switch-to-buffer-other-tab
|
||
("C-x r b" . consult-bookmark) ;; orig. bookmark-jump
|
||
("C-x p b" . consult-project-buffer) ;; orig. project-switch-to-buffer
|
||
;; Custom M-# bindings for fast register access
|
||
("M-#" . consult-register-load)
|
||
("M-'" . consult-register-store) ;; orig. abbrev-prefix-mark (unrelated)
|
||
("C-M-#" . consult-register)
|
||
;; Other custom bindings
|
||
("M-y" . consult-yank-pop) ;; orig. yank-pop
|
||
;; M-g bindings in `goto-map'
|
||
("M-g e" . consult-compile-error)
|
||
("M-g f" . consult-flymake) ;; Alternative: consult-flycheck
|
||
("M-g g" . consult-goto-line) ;; orig. goto-line
|
||
("M-g M-g" . consult-goto-line) ;; orig. goto-line
|
||
("M-g o" . consult-outline) ;; Alternative: consult-org-heading
|
||
;; My bindings from my Helm workflow
|
||
("C-x c i" . consult-imenu)
|
||
("C-c s" . consult-ripgrep)
|
||
: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
|
||
|
||
I use ~vertico-repeat~ to mimic the functionality that ~helm-resume~ would provide. The configuration for that is also part of this section.
|
||
|
||
#+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)))
|
||
|
||
(use-package vertico-repeat
|
||
:after vertico
|
||
:bind ( :map global-map
|
||
("M-R" . vertico-repeat)
|
||
:map vertico-map
|
||
("M-N" . vertico-repeat-next)
|
||
("M-P" . vertico-repeat-previous))
|
||
:hook (minibuffer-setup . vertico-repeat-save))
|
||
|
||
(use-package vertico-suspend
|
||
:after vertico
|
||
:init
|
||
(setq enable-recursive-minibuffers t)
|
||
:bind ( :map global-map
|
||
("M-S" . vertico-suspend)
|
||
("C-x c b" . vertico-suspend)))
|
||
#+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-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
|
||
#+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 "~/Tresors/Documents/diary"))
|
||
(setq org-default-notes-file (expand-file-name "brain/daily.org" org-directory))
|
||
(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-use-speed-commands t)
|
||
(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!)")))
|
||
|
||
(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:
|
||
|
||
#+begin_quote
|
||
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]]
|
||
#+end_quote
|
||
|
||
#+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:
|
||
|
||
#+begin_quote
|
||
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).
|
||
#+end_quote
|
||
|
||
#+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 nil)
|
||
(setq org-src-tab-acts-natively t)
|
||
(setq org-edit-src-content-indentation 2))
|
||
#+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 Prot's talk: [[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
|
||
|
||
* 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 =unravel-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 =unravel-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 d n" . denote-create-note)
|
||
("C-c d N" . denote-silo-extras-select-silo-then-command)
|
||
("C-c d o" . denote-open-or-create)
|
||
("C-c d O" . denote-silo-extras-open-or-create)
|
||
("C-c d l" . denote-link-or-create)
|
||
("C-c d L" . denote-link-after-creating-with-command)
|
||
;; 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 d 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'.
|
||
("C-c d j" . denote-journal-extras-new-entry)
|
||
("C-c d s" . denote-sort-dired)
|
||
;; Bindings to personal functions (defined below)
|
||
("C-c d p m" . vedang/denote-publishing-extras-new-microblog-entry)
|
||
("C-c d p b" . vedang/denote-publishing-extras-new-blog-entry)
|
||
:map text-mode-map
|
||
("C-c d B" . denote-backlinks)
|
||
("C-c d b" . denote-find-backlink)
|
||
;; Also see `denote-rename-file' further above.
|
||
("C-c d R" . denote-rename-file-using-front-matter)
|
||
("C-c d k" . denote-rename-file-keywords)
|
||
:map org-mode-map
|
||
("C-c d h" . denote-org-extras-link-to-heading)
|
||
("C-c d d l" . denote-org-extras-dblock-insert-links)
|
||
("C-c d d b" . denote-org-extras-dblock-insert-backlinks)
|
||
("C-c d d m" . denote-org-extras-dblock-insert-missing-links)
|
||
;; 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-A" . denote-dired-rename-marked-files-add-keywords)
|
||
("C-c C-d C-K" . denote-dired-rename-marked-files-remove-keywords)
|
||
("C-c C-d C-f" . denote-dired-rename-marked-files-using-front-matter))
|
||
:config
|
||
(require 'denote-silo-extras)
|
||
(require 'denote-journal-extras)
|
||
(require 'denote-org-extras)
|
||
|
||
;; Remember to check the doc strings of those variables.
|
||
(setq denote-directory (expand-file-name "~/Tresors/Documents/diary/notes"))
|
||
(setq denote-infer-keywords t)
|
||
(setq denote-sort-keywords t)
|
||
(setq denote-excluded-directories-regexp "data") ; external data related to headings is stored in these directories (web archives)
|
||
(setq denote-date-format nil) ; read its doc string
|
||
(setq denote-date-prompt-use-org-read-date t)
|
||
(setq denote-prompts '(title keywords subdirectory signature))
|
||
|
||
(setq denote-rename-confirmations nil) ; CAREFUL with this if you are not familiar with Denote!
|
||
(setq denote-save-buffers t)
|
||
(setq denote-rename-buffer-format "[D] %s %t%b")
|
||
;; 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)
|
||
|
||
(setq denote-buffer-has-backlinks-string " (<--->)")
|
||
(setq denote-backlinks-show-context t)
|
||
(setq denote-org-store-link-to-heading t)
|
||
|
||
;; Journal settings
|
||
(setq denote-journal-extras-keyword "")
|
||
|
||
;; I use Yasnippet to expand these into a better template.
|
||
(add-to-list 'denote-templates '(reference-note . "reference"))
|
||
(add-to-list 'denote-templates '(morning . "morningpage"))
|
||
(add-to-list 'denote-templates '(emotion . "emotion"))
|
||
(add-to-list 'denote-templates '(insight . "insight"))
|
||
(add-to-list 'denote-templates '(weekly_intentions . "weekint"))
|
||
(add-to-list 'denote-templates '(weekly_report . "weekrpt"))
|
||
(add-to-list 'denote-templates '(checkin . "checkin"))
|
||
|
||
;; Front-matter for Org files
|
||
(setq denote-org-front-matter
|
||
":PROPERTIES:
|
||
:ID: %4$s
|
||
:CREATED: %2$s
|
||
:END:
|
||
,#+title: %1$s
|
||
,#+filetags: %3$s
|
||
,#+date: %2$s
|
||
,#+identifier: %4$s
|
||
\n")
|
||
|
||
(defun vedang/denote-publishing-extras-new-microblog-entry (&optional date)
|
||
"Create a new microblog entry.
|
||
Set the title of the new entry according to the value of the user option
|
||
`denote-journal-extras-title-format'.
|
||
|
||
With optional DATE as a prefix argument, prompt for a date. If
|
||
`denote-date-prompt-use-org-read-date' is non-nil, use the Org
|
||
date selection module.
|
||
|
||
When called from Lisp DATE is a string and has the same format as
|
||
that covered in the documentation of the `denote' function. It
|
||
is internally processed by `denote-parse-date'."
|
||
(interactive (list (when current-prefix-arg (denote-date-prompt))))
|
||
(let ((internal-date (denote-parse-date date))
|
||
(denote-directory (file-name-as-directory (expand-file-name "published" denote-directory))))
|
||
(denote
|
||
(denote-journal-extras-daily--title-format internal-date)
|
||
'("draft" "microblog")
|
||
nil nil date
|
||
;; See YASnippet
|
||
"microblog")))
|
||
|
||
(defun vedang/denote-publishing-extras-new-blog-entry (&optional date)
|
||
"Create a new blog entry.
|
||
|
||
With optional DATE as a prefix argument, prompt for a date. If
|
||
`denote-date-prompt-use-org-read-date' is non-nil, use the Org
|
||
date selection module.
|
||
|
||
When called from Lisp DATE is a string and has the same format as
|
||
that covered in the documentation of the `denote' function. It
|
||
is internally processed by `denote-parse-date'."
|
||
(interactive (list (when current-prefix-arg (denote-date-prompt))))
|
||
(let ((internal-date (denote-parse-date date))
|
||
(denote-directory (file-name-as-directory (expand-file-name "published" denote-directory))))
|
||
(denote
|
||
(denote-title-prompt)
|
||
'("draft")
|
||
nil nil date
|
||
;; See YASnippet
|
||
"fullblog")))
|
||
|
||
(defun vedang/denote-link-ol-get-id ()
|
||
"Get the CUSTOM_ID of the current entry.
|
||
If the entry already has a CUSTOM_ID, return it as-is, else
|
||
create a new one."
|
||
(interactive)
|
||
(let* ((pos (point))
|
||
(id (org-entry-get pos "CUSTOM_ID")))
|
||
(if (and (stringp id) (string-match-p "\\S-" id))
|
||
id
|
||
(setq id (org-id-new "h"))
|
||
(org-entry-put pos "CUSTOM_ID" id)
|
||
id)))
|
||
|
||
(defun vedang/denote--split-luhman-sig (signature)
|
||
"Split numbers and letters in Luhmann-style SIGNATURE string."
|
||
(replace-regexp-in-string
|
||
"\\([a-zA-Z]+?\\)\\([0-9]\\)" "\\1=\\2"
|
||
(replace-regexp-in-string
|
||
"\\([0-9]+?\\)\\([a-zA-Z]\\)" "\\1=\\2"
|
||
signature)))
|
||
|
||
(defun vedang/denote--pad-sig (signature)
|
||
"Create a new signature with padded spaces for all components"
|
||
(combine-and-quote-strings
|
||
(mapcar
|
||
(lambda (x)
|
||
(string-pad x 5 32 t))
|
||
(split-string (vedang/denote--split-luhman-sig signature) "=" t))
|
||
"="))
|
||
|
||
(defun vedang/denote-sort-for-signatures (sig1 sig2)
|
||
"Return non-nil if SIG1 is smaller that SIG2.
|
||
Perform the comparison with `string<'."
|
||
(string< (vedang/denote--pad-sig sig1) (vedang/denote--pad-sig sig2)))
|
||
|
||
(setq denote-sort-signature-comparison-function
|
||
#'vedang/denote-sort-for-signatures))
|
||
#+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 d f" . consult-denote-find)
|
||
("C-c d 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
|
||
|