James Dyer: Git Worktreess Without Leaving Built-in VC

More vc stuff, and this week's rabbit hole was git worktrees (Bozhidar Batsov's Working with Git Worktrees in Magit is what sent me down this particular rabbit hole!). It was an itch that manifested itself while reading this blog and mainly seeing that worktrees seemed to make a good deal of sense to me (especially as I am quite a subversion veteran). I have always felt uncomfortable with the default git in-situ branch switching and prefer to branch develop away from the mainline in a physical separate directory and this is just what worktrees facilitate. So how to do this with built-in vc-mode tools?, well as always this requires a little work and yes, I could use Magit, but I'm seeing how far I can push the built in vc.

20260905090959-emacs--Git-Worktrees-and-Branch-Surgery-in-Built-in-VC.jpg

The funny thing is that vc already understands worktrees perfectly well: a worktree is just a directory holding a .git pointer file instead of a .git folder, vc-git resolves that transparently, and diffs, commits and vc-dir all quietly do the right thing per worktree. What vc cannot do is actually seemingly manage them. There is no command to create one, switch between them, move one, list them, or throw one away, and the branch side is nearly as thin. Magit does all of this beautifully, of course - but I am stubborn about staying inside core Emacs where I can, so I spent an evening teaching vc-dir some new tricks. And yes, I do understand that vc in Emacs is in fact a generic abstraction and works well across all different types of version control, hence the reduction to a core subset of general source code commands, with a few extra added on of course for those obvious missing pieces, but I want to add the less obvious pieces, and certainly for git, worktrees is probably going to be one of them. Right lets get on with this and write some code!

Everything funnels through one small worker that shells out via vc-git-command and then drops you straight into vc-dir in the new tree:

Here is what I implemented:

;;
;; -> vc-git-worktree-core
;;
(defun my/vc-git-branches ()
  "Return a list of local branch names in the current git repository."
  (let ((root (vc-git-root default-directory)))
    (when root
      (with-temp-buffer
        (let ((default-directory root))
          (vc-git-command (current-buffer) 0 nil
                          "branch" "--format=%(refname:short)"))
        (split-string (buffer-string) "\n" t)))))
(defun my/vc-git--worktree-root () "Return the git worktree root for `default-directory'. Signal a `user-error' when not inside a Git repository." (or (vc-git-root default-directory) (user-error "Not in a Git repository")))
(defun my/vc-git-worktree-entries () "Return the worktrees of the current repository as a list of plists. Each plist has keys :path, :head, :branch, :detached, :bare, :locked and :prunable, parsed from `git worktree list --porcelain'." (let ((root (my/vc-git--worktree-root)) entries current) (with-temp-buffer (let ((default-directory root)) (vc-git-command (current-buffer) 0 nil "worktree" "list" "--porcelain")) (goto-char (point-min)) (while (not (eobp)) (let ((line (buffer-substring-no-properties (line-beginning-position) (line-end-position)))) (cond ((string-prefix-p "worktree " line) (when current (push current entries)) (setq current (list :path (substring line 9)))) ((string-prefix-p "HEAD " line) (setq current (plist-put current :head (substring line 5)))) ((string-prefix-p "branch " line) (let ((ref (substring line 7))) (setq current (plist-put current :branch (if (string-prefix-p "refs/heads/" ref) (substring ref 11) ref))))) ((string= line "detached") (setq current (plist-put current :detached t))) ((string= line "bare") (setq current (plist-put current :bare t))) ((string-prefix-p "locked" line) (setq current (plist-put current :locked (if (> (length line) 6) (substring line 7) t)))) ((string-prefix-p "prunable" line) (setq current (plist-put current :prunable (if (> (length line) 8) (substring line 9) t)))))) (forward-line 1))) (when current (push current entries)) (nreverse entries)))
(defun my/vc-git--default-worktree-path (root branch) "Return a default new-worktree path for BRANCH next to ROOT. The default is a sibling directory of ROOT prefixed with the project name, e.g. <parent>/myproj-feature for branch feature of myproj. Slashes in BRANCH become dashes so hierarchical branches cannot collide; an empty BRANCH falls back to <project>-worktree." (let ((project (file-name-nondirectory (directory-file-name (expand-file-name root))))) (expand-file-name (concat project "-" (if (string-empty-p branch) "worktree" (replace-regexp-in-string "/" "-" branch))) (file-name-directory (directory-file-name (expand-file-name root))))))
(defun my/vc-git-worktree-add (path branch &optional new-branch start-point) "Create a Git worktree at PATH checking out BRANCH, then open `vc-dir'. An empty BRANCH checks out HEAD. With prefix argument NEW-BRANCH, create BRANCH as a new branch from START-POINT (defaulting to HEAD) instead of checking out an existing branch or revision." (interactive (let* ((root (my/vc-git--worktree-root)) (branches (or (my/vc-git-branches) '())) (make-new (if current-prefix-arg t nil)) (branch (if make-new (read-string "New branch name: ") (completing-read "Checkout branch (empty = HEAD): " branches nil nil))) (default-path (my/vc-git--default-worktree-path root branch)) (path (read-file-name "Worktree location: " (file-name-directory default-path) nil nil (file-name-nondirectory default-path))) (start (and make-new (completing-read "Start point: " (delete-dups (append (list "HEAD") branches (my/vc-git-tags))) nil nil nil nil "HEAD")))) (list (expand-file-name path) branch make-new start))) (my/vc-git-worktree--create path branch new-branch start-point))
(defun my/vc-git-worktree--create (path branch new-branch start-point) "Create a Git worktree at PATH and open `vc-dir' there. BRANCH names an existing branch or revision, unless NEW-BRANCH is non-nil, in which case BRANCH is created from START-POINT first." (let ((root (my/vc-git--worktree-root)) (expanded (expand-file-name path))) (let ((default-directory root)) (cond (new-branch (when (string-empty-p branch) (user-error "New branch name must not be empty")) (vc-git-command nil 0 nil "worktree" "add" "-b" branch expanded (if (string-empty-p start-point) "HEAD" start-point))) ((string-empty-p branch) (vc-git-command nil 0 nil "worktree" "add" expanded)) (t (vc-git-command nil 0 nil "worktree" "add" expanded branch)))) (message "Created worktree %s" expanded) (vc-dir expanded)))
(defun my/vc-git-worktree-checkout (path branch) "Check out existing BRANCH in a new worktree at PATH, then open `vc-dir'. The Magit-style `magit-worktree-checkout' first action: the location defaults to a sibling directory named <project>-<branch>." (interactive (let* ((root (my/vc-git--worktree-root)) (branches (or (my/vc-git-branches) '())) (branch (completing-read "Checkout branch in new worktree: " branches nil nil)) (default-path (my/vc-git--default-worktree-path root branch)) (path (read-file-name "Worktree location: " (file-name-directory default-path) nil nil (file-name-nondirectory default-path)))) (list (expand-file-name path) branch))) (when (string-empty-p branch) (user-error "Branch must not be empty")) (my/vc-git-worktree--create path branch nil nil))
(defun my/vc-git-worktree-branch (path branch start-point) "Create new BRANCH from START-POINT in a new worktree at PATH. Then open `vc-dir'. The Magit-style `magit-worktree-branch' first action: the location defaults to a sibling directory named <project>-<branch>." (interactive (let* ((root (my/vc-git--worktree-root)) (branches (or (my/vc-git-branches) '())) (branch (read-string "New branch name: ")) (start (completing-read "Start point: " (delete-dups (append (list "HEAD") branches (my/vc-git-tags))) nil nil nil nil "HEAD")) (default-path (my/vc-git--default-worktree-path root branch)) (path (read-file-name "Worktree location: " (file-name-directory default-path) nil nil (file-name-nondirectory default-path)))) (list (expand-file-name path) branch start))) (my/vc-git-worktree--create path branch t start-point))
(defun my/vc-git-worktree-move (path new-path) "Move the Git worktree at PATH to NEW-PATH (`git worktree move'). Interactively, prompt for a worktree, defaulting to the current one." (interactive (let* ((entries (my/vc-git-worktree-entries)) (paths (mapcar (lambda (e) (plist-get e :path)) entries)) (current (my/vc-git--worktree-root))) (unless paths (user-error "No Git worktrees found")) (let ((path (completing-read "Move worktree: " paths nil t nil nil current))) (list path (read-file-name "Move to: " (file-name-directory (directory-file-name (expand-file-name path))) nil nil))))) (let* ((root (my/vc-git--worktree-root)) (expanded (expand-file-name path)) (target (expand-file-name new-path))) (let ((default-directory root)) (vc-git-command nil 0 nil "worktree" "move" expanded target)) (message "Moved worktree %s to %s" expanded target) (when (derived-mode-p 'vc-dir-mode) (vc-dir-refresh))))
(defun my/vc-git-worktree-switch (path) "Interactively select an existing Git worktree and open `vc-dir' in it." (interactive (let* ((entries (my/vc-git-worktree-entries)) (table (mapcar (lambda (e) (cons (plist-get e :path) e)) entries)) (current (my/vc-git--worktree-root))) (unless table (user-error "No Git worktrees found")) (let ((completion-extra-properties (list :annotation-function (lambda (cand) (let ((e (cdr (assoc cand table)))) (when e (concat " [" (or (plist-get e :branch) (and (plist-get e :detached) "detached") "?") (when (plist-get e :bare) ", bare") "]"))))))) (list (completing-read "Switch to worktree: " table nil t nil nil current))))) (vc-dir path))
(defun my/vc-git-worktree-remove (path &optional force) "Remove the Git worktree at PATH, prompting first for confirmation. With prefix argument FORCE, pass --force to git. Interactively, prompt for a worktree, defaulting to the current one." (interactive (let* ((entries (my/vc-git-worktree-entries)) (paths (mapcar (lambda (e) (plist-get e :path)) entries)) (current (my/vc-git--worktree-root))) (unless paths (user-error "No Git worktrees found")) (list (completing-read "Remove worktree: " paths nil t nil nil current) (if current-prefix-arg t nil)))) (let* ((root (my/vc-git--worktree-root)) (expanded (expand-file-name path)) (main-root (plist-get (car (my/vc-git-worktree-entries)) :path)) (here (file-truename default-directory)) (gone (file-truename expanded))) (when (yes-or-no-p (format "Remove worktree %s%s? " expanded (if force " (forced)" ""))) (let ((default-directory root)) (if force (vc-git-command nil 0 nil "worktree" "remove" "--force" expanded) (vc-git-command nil 0 nil "worktree" "remove" expanded))) (message "Removed worktree %s" expanded) (when (derived-mode-p 'vc-dir-mode) (if (string-prefix-p gone here) (vc-dir (or main-root root)) (vc-dir-refresh))))))
(defun my/vc-git-worktree-prune () "Prune stale Git worktree metadata (`git worktree prune')." (interactive) (let ((root (my/vc-git--worktree-root))) (let ((default-directory root)) (vc-git-command nil 0 nil "worktree" "prune")) (message "Pruned worktrees in %s" root) (when (derived-mode-p 'vc-dir-mode) (vc-dir-refresh))))
(defvar-local my/vc-git-worktree-list--root nil "Repository root shown in the current worktree list buffer.")
(defvar my/vc-git-worktree-list-mode-map (let ((map (make-sparse-keymap))) (define-key map (kbd "RET") #'my/vc-git-worktree-list-visit) (define-key map (kbd "g") #'my/vc-git-worktree-list) (define-key map (kbd "q") #'quit-window) map) "Keymap for `my/vc-git-worktree-list-mode'.")
(define-derived-mode my/vc-git-worktree-list-mode special-mode "Worktrees" "Major mode for listing Git worktrees. \\{my/vc-git-worktree-list-mode-map}")
(defun my/vc-git-worktree-list-visit () "Open `vc-dir' for the worktree on the current line." (interactive) (let* ((here (line-number-at-pos)) (btn (or (button-at (point)) (save-excursion (beginning-of-line) (next-button (point) t)))) (ok (and btn (= here (line-number-at-pos (button-start btn)))))) (if ok (vc-dir (button-label btn)) (user-error "No worktree on this line"))))
(defun my/vc-git-worktree-list () "List Git worktrees of the current repository in a dedicated buffer. RET on a path opens `vc-dir' there; `g' refreshes the list, `q' quits." (interactive) (let* ((root (if (derived-mode-p 'my/vc-git-worktree-list-mode) (or my/vc-git-worktree-list--root (my/vc-git--worktree-root)) (my/vc-git--worktree-root))) (default-directory root) (entries (my/vc-git-worktree-entries))) (unless entries (user-error "No Git worktrees found")) (with-current-buffer (get-buffer-create "*vc-git worktrees*") (let ((inhibit-read-only t)) (erase-buffer) (my/vc-git-worktree-list-mode) (setq my/vc-git-worktree-list--root root) (setq default-directory root) (insert (format "Worktrees for %s\n\n" root)) (dolist (e entries) (let* ((path (plist-get e :path)) (head (or (plist-get e :head) "")) (short (if (> (length head) 7) (substring head 0 7) head)) (flags (string-join (delq nil (list (when (plist-get e :bare) "bare") (when (plist-get e :detached) "detached") (when (plist-get e :locked) "locked") (when (plist-get e :prunable) "prunable"))) ","))) (insert-button path 'action (lambda (btn) (vc-dir (button-label btn))) 'follow-link t) (insert (format " [%s] %s%s\n" (or (plist-get e :branch) "HEAD") short (if (string-empty-p flags) "" (concat " (" flags ")"))))))) (goto-char (point-min)) (pop-to-buffer (current-buffer)))))
(with-eval-after-load 'vc-dir (define-key vc-dir-mode-map (kbd "Z a") #'my/vc-git-worktree-add) (define-key vc-dir-mode-map (kbd "Z b") #'my/vc-git-worktree-checkout) (define-key vc-dir-mode-map (kbd "Z c") #'my/vc-git-worktree-branch) (define-key vc-dir-mode-map (kbd "Z g") #'my/vc-git-worktree-switch) (define-key vc-dir-mode-map (kbd "Z k") #'my/vc-git-worktree-remove) (define-key vc-dir-mode-map (kbd "Z l") #'my/vc-git-worktree-list) (define-key vc-dir-mode-map (kbd "Z m") #'my/vc-git-worktree-move) (define-key vc-dir-mode-map (kbd "Z p") #'my/vc-git-worktree-prune))

Some interactive commands: Z a for the general case, Z b for checking out an existing branch, Z c for a new branch from a start point, plus Z m to move a tree, Z g to switch between them with branch annotations in the completion, Z k to remove one (prefix forces, and a vc-dir sitting inside the removed tree falls back to the main root rather than dying), Z p to prune stale metadata, and Z l for a listing buffer where RET jumps to vc-dir. The letters deliberately mirror Magit's own worktree map, so Z b, Z c, Z g, Z m and Z k all do what a Magit user's fingers expect; Z a, Z p and Z l are extras with no Magit equivalent. The same set hangs off C-x v z … globally.

So the whole loop, start to finish, never leaves vc: Z c to spin up the worktree, hack away, C-x v v to commit, Z g back to main, merge the branch, and Z k to remove the worktree.

If any of this sounds useful, the pattern to steal is a small one: vc-git-command with default-directory bound to the worktree root does almost all of the heavy lifting, and vc-dir on the resulting path gives you the status buffer for free. The rest is just prompts and keybindings.

-1:-- Git Worktreess Without Leaving Built-in VC (Post James Dyer)--L0--C0--2026-09-08T09:31:34.007Z

Sacha Chua: 2026-09-07 Emacs news

The Emacs Carnival theme for September is "Games". Enjoy! Other really cool uses of graphics in Emacs: org-timegrid, poimap, canvas-minimap.

Also, public service announcement: doomemacs.com is not affiliated with doomemacs.org (see this Github discussion). Just in case someone reading this happens to be the person who made doomemacs.com, please consider setting up a redirect to doomemacs.org for more awesomeness and less confusion. Thank you!

And a reminder: EmacsConf - 2026 - Call for Participation target date for proposals Sept 18, conference is online Dec 12-13. Hope to see you there!

Links from reddit.com/r/emacs, r/orgmode, r/spacemacs, Mastodon #emacs, Bluesky #emacs, Hacker News, lobste.rs, programming.dev, lemmy.world, lemmy.ml, planet.emacslife.com, YouTube, the Emacs NEWS file, Emacs Calendar, and emacs-devel. Thanks to Andrés Ramírez for emacs-devel links. Do you have an Emacs-related link or announcement? Please e-mail me at sacha@sachachua.com. Thank you!

View Org source for this post

You can e-mail me at sacha@sachachua.com.

-1:-- 2026-09-07 Emacs news (Post Sacha Chua)--L0--C0--2026-09-07T23:44:14.000Z

Marcin Borkowski: Pasting primary selection from keyboard

A few years ago I wrote about a setup I coded for myself for when I want to make paying my bills a tiny bit less painful. One thing I mentioned then was the primary selection. For those of you who don’t know it: under X.org, you can select a portion of text (for example by dragging the mouse over it) and then paste it in the same or another application with clicking the middle button. It is extremely useful for quick copy-and-paste operations where you do not have to issue a separate “copy” command (usually C-c, but often also found on mouse context menu). And by the way, Wayland also supports this. Sometimes I find it useful to use it even without the mouse.
-1:-- Pasting primary selection from keyboard (Post Marcin Borkowski)--L0--C0--2026-09-07T19:41:52.000Z

Lars Ingebrigtsen: Emacs Canz Spell Goodly Know

Like many nerds, I feel so embarrassed whenever I see that I’ve misspelled something. But some years back when I started producing text in huge amounts for things like the Eclipse Comics read-through I realised that my options were: 1) Remain neurotic about this stuff and produce one blog post per week, or 2) Start caring less and produce a lot of blog posts.

I think I made the correct decision — I just don’t have time to proff-read all this stuff, so typos will abound. It’s just not feasible to put as much work into that stuff as you’d do for a properly published article. The reader gets what they pay for.

But now there’s a way to have your delicious cake and eat it too — just ask an LLM for help.

NOOOO! *audience runs in slow motion towards camera*

I know, I know. Whenever I read somebody in deep AI psychosis who thinks they’re being AI sceptical saying “well, of course you shouldn’t let the LLM write your articles. Instead I let it do the research, and then I make it provide an outline, and then I write some text based on that, and then it does formats and restates what I wrote. IT”S A WHOLE DIFFERENT THING! I WROTE IT!!!!”

No. You should never let an LLM do those things: Not the research, not the outline, not the formatting, not anything. Reading the output of LLMs is abhorrent to people. You should never subject anybody to an LLM generated text, even if you’ve done “quality control”.

LLM-generated text gives people the ick. It’s better to have an awkwardly phrased article you’ve written yourself than subjecting somebody to a Claude text. It’s not just the ick — it’s the horror.

However, you can use LLMs for spell- and grammer check:

I’ve whipped up a li’l minor mode for Emacs (and put it on Microsoft Github). The default prompt is very clear that I want the LLM to do nothing but copy edits — no structural changes, and no other helpful suggestions. Just fix grammer and tippos.

And then you can interactively go through the fixes and toggle back to the original ones if you disagree.

Yes, yes, sure — Grammarly, etc — stuff like this already exists. But I just don’t like using them. I use the built-in Emacs spellcheck, but it doesn’t catch everything. Having an LLM give the text a quick once-over for obvious mistakes seems nice.

While testing this minor mode now, I see that LLMs haven’t changed as much as I thought — they’ve gotten so much better on programming the last half year, it’s incredible. For stuff like this, though, it’s still pretty unreliable. Any two runs won’t necessarily fix up the same thing, or the same thing the same way. And getting it (I’m using Claude Sonnet 5) to output it in the requested form is not reliable at all — I’ve been reformulating the prompt, trying to get it to output it in the format I the mode needs, and… I think I’m at 95% now?

So, like all with all non-programming tasks, LLMs are still pretty much toys. But useful ones! For instance, this blog post is totally free of errors.

-1:-- Emacs Canz Spell Goodly Know (Post Lars Ingebrigtsen)--L0--C0--2026-09-07T15:41:27.000Z

Magnus: Emacs salmagundi, 2026-09-06

Compilation commands by project type

I while ago I added a minor mode, haskell-ng-project-mode, to my Haskell mode. The idea I had was that I could use it to add keybindings for running various tools (via compile), e.g. I bound cabal build and cabal test , p b and , p t respectively. Then I hooked in the minor mode in my haskell-ng-mode. Soon I realised it'd be nice to have those shortcuts available in dired too, so I hooked it in there as well. When I later wanted to re-run the tests after modifying a JSON file that was part of a golden test I realised that this wasn't a very nice solution at all.

After looking around a bit I found emacs-multi-compile. I removed the minor mode and add this configuration instead

(add-to-list 'multi-compile-alist
             '((haskell-ng-is-project) . (("cabal build" "cabal build -j --semaphore" (project-root (project-current)))
                                          ("cabal test" "cabal test" (project-root (project-current)))
                                          ("fourmolu" "fourmolu -i $(fd .hs$)" (project-root (project-current))))))

I've had some vague thoughts that it'd be rather easy to write something slightly more custom, but emacs-multi-compile works very well for me so I'm very happy with it.

Jumping between implementation and test, again

I wrote about this a while ago. At the time I came up with a way of bending ff-find-other-file to my will, but soon after that I ran into a case where there may be 2 "other files" – one of the Haskell projects at work put unit tests in files ending in Spec.hs and property tests in filed ending with Prop.hs. Unfortunately this is a situation that ff-find-other-file can't handle.

I ended up hacking together consult-kith to deal with it. The configuration to handle two kinds of test files looks like this

(setq-local consult-kith-alist `((,(rx (seq "Spec.hs" eol)) (".hs"))
                                 (,(rx (seq "Prop.hs" eol)) (".hs"))
                                 (,(rx (seq ".hs" eol)) ("Prop.hs" "Spec.hs")))
            consult-kith-search-directories '("src" "test"))

The project has since settled on only using Spec.hs for tests, but I'm sticking to consult-kith for now.

-1:-- Emacs salmagundi, 2026-09-06 (Post Magnus)--L0--C0--2026-09-06T16:33:00.000Z

Irreal: Prot’s Doric Themes

As many of you know, my idea of a theme is to set the background to “oldlace” and the cursor to red. For the rest of it, I just take what Emacs gives me. That sounds pretty haphazard but it works out pretty well. It has enough contrast to be easy to read without searing my eyeballs and has enough syntax highlighting to be useful without being overpowering.

Prot (Protesilaos Stavrou) has a much more refined sense for these things. He considers what colors go well to together and things like contrast levels. Just the oposite of my “do with me what you will” approach. Nevertheless, I mostly don’t bother much with formally defined themes although I do occasionally like to take a look at them.

The reason for this post is Prot’s latest release of his Doric Themes. They’re sort of minimalist, like my non-theme theme but still very nice looking if you don’t want your Emacs screen to look like an arcade. The doric-almond variation is, in particular, very similar to my color scheme. Mine has a little more contrast: the foreground text is a bit darker and the keywords in my code buffers are a blue that’s slightly brighter than doric-almond’s light green. Still, it’s a theme that I’d be comfortable with if I hadn’t taken the easy way.

Some people have multiple themes and change them according to the time of day, their mood, the phase of the moon, or whatever. I’d hate that probably for the same reason that I don’t do well with trying to use different editors: I’d always be confused as to what’s what.

If you’re looking for a minimalist theme and my method seems too minimal, take a look at Prot’s Doric Themes. There are several variations in both light and dark mode.

-1:-- Prot’s Doric Themes (Post Irreal)--L0--C0--2026-09-06T14:48:40.000Z

Joar von Arndt: Emacs does not need LSP


Language-server-protocol (lsp) is standard to communicate information about a current programming project to an editor integration, powered by a “language server” for the specific programming language. Emacs has two major implementations of this editor functionality: eglot (built-in) and lsp-mode. Getting this set up is usually one of the first things that new users will want to do, but in my experience one can get by for a surprisingly long time using Emacs without needing to reach for eglot or lsp-mode. That is because many of the functions that lsp-servers provide can be provided by Emacs through other means.

As someone who quite often moves between different programming languages, I find the need to set up each lsp-server to a bit clunky. Despite being presented as a simple one-stop solution, you often need to research exactly what server to use, how to set it up, and to tailor it to your machine in some ways. For me, this only makes sense in a few languages that I use often, and even then it might not be the first thing I take care of.

In-buffer completion

The most obvious functionality is auto-complete (aka in-buffer completion). In Emacs this is provided on multiple levels, which is sometimes confusing for new users. There are many ways to show in-buffer completions, but they are all powered by the same back-end functionality: completion-at-point-functions (capf). This is a list of functions that run in order, only returning a value if there is something to complete.

Some major modes (most notable perhaps lisp-interaction-mode) come with their own buffer-local values of capf1 that provide buffer-specific completions. completion-at-point will then move on to the global value if the buffer-local value of capf includes the value t. It is therefore this global value that we are often interested in modifying.

For this we can use the cape-package — standing for “Completion At Point Functions”. Specifically, we can make liberal use of the cape-dabbrev-function. This capf makes use of Emacs’ built-in dabbrev-functionality. Dabbrev will scan all the words in the buffers of your choice and give you completions for words that you have already used. This means that, while working on your project, you have quick and easy access to all the keywords and function names in your currently opened buffers.

This also works as a form of autocomplete for writing prose, and so we can combine these two using cape-wrap-super:

(defun cape-language (&optional interactive)
    (interactive (list t))
    (if interactive
        (cape-interactive #'cape-language)
      (cape-wrap-super #'cape-dabbrev #'cape-dict)))

This will combine the output cape-dabbrev and cape-dict2 into one capf that can run at the same time (and therefore not block each other). For a simpler contribution we also use the cape-keyword capf that comes with a number of pre-configured programming language3 keywords. This is a bit of a pre-lsp solution, where each editor provided support for each programming language. To set up cape, simple add something like this to your init file:

(add-hook 'completion-at-point-functions #'cape-history)
(add-hook 'completion-at-point-functions #'cape-keyword)
(add-hook 'completion-at-point-functions #'cape-file)
(add-hook 'completion-at-point-functions #'cape-language)
(add-hook 'completion-at-point-functions #'cape-abbrev)

This is the order in which cape will try your capfs, with the first matching blocking the rest. As mentioned earlier, locally bound capfs will supersede these global values, and so python-completion-at-point will run before any of these in python-mode buffers.

Another improvement we can do is to change the value of cape-dabbrev-buffer-function. The value of this variable is the function that returns the buffers to scan for dabbrev results. By default this is cape-same-mode-buffers, meaning that only buffers in the same mode (org-mode, python-mode, et cetera) will be used for results. This is a good default, but I would rather have always have some relevant completion candidate than nothing. So personally I set this value to cape-text-buffers, which will return all buffers in text-mode or prog-mode buffers. This means extra niceties like referring to practically anything you are working on when writing a commit message in git.

Jump to definition and references

A second major feature of lsp-servers is “jump to definition” that allows you to go to the code that creates a function (its definition) to inspect its inner workings. There are a few different ways to do this.

The first is the package named dumb-jump that uses ag, rg, or simply grep to find definition-matching strings. This works similarly to cape-keyword in that it uses a pre-configured set of language-syntax elements to match function-defining cases.4 I however do not personally use this.

Instead I use another of Minad’s wonderful additions to the Emacs’ ecosystem; consult. I must confess that I slept on consult for far too long. I had it in my config for a long time, but the number of (largely stand-alone) functions made it a bit overwhelming to learn.5 What Consult offers is a suite of improved versions of preëxisting Emacs utilities (and a few new ones building upon said features), largely based on the completing-read.

Emacs has a built-in feature called imenu that scans the buffer’s structure for top-level features (Org-mode and markdown headers, or in this case function definitions) and allows you to quickly move to them. Consult of course expands on this by instantly showing you the place you will move to, but it also offers an extension of this feature: consult-imenu-multi (as opposed to the regular consult-imenu). This will not only scan the current buffer, but also all other same-mode buffers. Better yet, it integrates with Emacs’ project.el, and so only checks buffers that belong to the same project (exempli gratia git repository). As I usually have the relevant buffers open in a project I am working on, this works very well as an alternative to dumb-jump, and the preview and subsequent confirmation mean that I am not worried about jumping to the wrong place — although of course at the cost of speed.

Then we of course have the venerable xref built into Emacs. This works a bit differently depending on the programming language backend, and plugs into an lsp-server automatically if it is available. Use xref-find-references6 Otherwise we can use etags-regen to automatically create and refresh the TAGS file that keeps track of the project structure:

(use-package etags-regen
  :config
  (setq etags-regen-ignores
        '("*.pyc" ".git" ".venv" "venv" "node_modules"))
  (etags-regen-mode 1))

Consult can also be used to improve upon this as well by replacing the ui interaction component:

(setq xref-show-xrefs-function #'consult-xref
      xref-show-definitions-function #'consult-xref)

The reason why I usually prefer using imenu is because it requires no trip to inspect the state of any files on the system — everything is already loaded and available, and perhaps 90% of the time it includes the things that I need (since they are things I am working on).

Syntax checking

Here we can use the built-in flymake or the standalone flycheck. Both of these make use of external syntax-checking tools for each language, and so do require some more setup on the host machine than the above solutions. In fact, Flycheck can even act as its own lsp-server just for syntax checking. But my experience has still been that this is easier to deal with than lsp-servers that need to be configured, started, reconnected, that crash, or have some other issue that need to be dealt with; A simple unix-style shell command that prints text (and that is then processed by Flymake or Flycheck) rarely needs much setup or maintenance in my experience. This is a matter that I really just want out of my mind.

Conclusion

There are only so many things that can be done merely through these static solutions. For example, neither of our in-buffer completion or jump-to-definition solution will complete external libraries; only buffers that have already been opened. There is no hover information.

I am not ideologically opposed to the use of language servers, and I will sometimes intentionally start one if I expect to be working on a larger code base for a longer amount of time. But tools like the ones above are nevertheless incredibly useful because they work in all buffers and contexts.

One of the strengths of Emacs is how it acts as a sort of force-multiplier for any task interacting with text, and how it builds upon improvements in one area throughout the entire editor. Having some of the most critical lsp-like abilities available anywhere you want without any extra effort is incredibly powerful. This is especially noteworthy for things like cape-dabbrev since it has made me so spoiled for autocompletion for everything I write.

I am sure that there are lots of different solutions and tools that I have missed that would work to replace even more lsp-based ones, and probably solutions that work better and/or are more elegant than the ones I use — the answer may even be to just use lsp. But honestly I only think about the matter of language server when I am talking with others. In day to day life I rarely feel the need for them at all. ❦

Footnotes:

1

For lisp-interaction-mode the value is (elisp-completion-at-point t).

2

The results of cape-dict are decided by the value of the cape-dict-file variable.

3

Here is the list of programming languages supported:

C++, C, Caml, Crystal, C#, D, Elixir, Erlang, f90, Go, Java, Javascript, Kotlin, Lua, Nim. Objective C, Perl, php, Purescript, Python, Ruby, Rust, Scala, Scheme, Swift, Julia, Thrift, sh.

4

This supposedly misidentifies things sometimes; as expected, it is “dumb” after all.

5

If you are using the built-in switch-to-buffer command (by default bound to C-x b) I highly recommend switching it to consult-buffer instead. Instant “previews” is super useful, and I often just spam C-n (N being right next to B) to cycle through my open buffers instead of starting to write the exact name that I want.

6

By default bound to M-?. To run xref-go-back press M-,.

-1:-- Emacs does not need LSP (Post Joar von Arndt)--L0--C0--2026-09-05T16:00:00.000Z

Irreal: Optimizing Startup With Use-package

James Cherti has another excellent post, this time on optimizing Emacs startup with use-package. The idea is that many configurations load a substantial number of packages and that this can cause a significant increase in startup time.

Most of you know by now that we here at the Irreal bunker refuse to worry about that because, after all, we hardly ever restart Emacs. Still, plenty of people do worry about it and there’s nothing wrong with learning a few easy to implement principles that will speedup Emacs startup without having to obsess about tiny details.

The TL;DR is to get use-package to defer loading of packages until they are actually needed. Most Emacers who aren’t complete n00bs already know this. The question is, how exactly, to specify the use-package commands to achieve that.

The obvious answers aren’t the right ones. For example, the :defer keyword seems like the natural answer but Cherti explains why this isn’t usually necessary and that it doesn’t do what you probably think it does. It’s much better to use one of the commands that implicitly defer loading but also arrange for autoloading the required functions when they’re needed.

Cherti also explains how to get use-package to add logging information to the *Messages* buffer to help with understanding the load process. There’s a lot of information in Cherti’s post and it serves as a handy reference for how to use use-package to control the loading of packages at startup time. If you’re at all worried about startup time or just want to understand how to use use-package to control package loading, take a look at his post.

-1:-- Optimizing Startup With Use-package (Post Irreal)--L0--C0--2026-09-05T14:44:09.000Z

Sacha Chua: Emacs Carnival September 2026: Games

The theme for the Emacs Carnival this September is "Games". Thanks to Raymond Zeitler for hosting!

One of the things I love about the Emacs community is people's sense of humour and whimsy. There's a grand tradition of people hiding Easter eggs in software, a bit of hidden fun that might make someone smile. But isn't this just bloat and frivolity? One might argue that the code that lets us play M-x tetris, dunnet, or doctor in Emacs is a waste of time and space compared to More Serious Things, but on the other hand, the Tetris source code could be a good jumping-off point if you find yourself wanting to make an interactive real-time simulation that displays graphical results. Want something more modern? Maybe DOOM on Emacs! Besides, the code for games isn't actually loaded until you choose to play it. It's okay for something to be fun by itself instead of needing to be productive. There's something wonderful about a tool that people choose not just for work but also for enjoyment.

Our adventure can start with Fun and Games in Emacs - Mastering Emacs or Emacs Is A Gaming Platform for Windows, Mac and Linux - YouTube, which cover a lot of classic built-ins. Here's a barrage of ideas; skip or explore anything you want: Pretend you're on a typewriter or turn on power mode or make c-c-combos. Go on to draw some ASCII art with artist-mode or with Emacs Lisp, and add some box shadows because why not. Maybe even use it to tell the time. Turn ASCII art into PNGs. Have fun with obscure calendars. Use kaomojis or write weird text. Explore the screensavers in zone and let them sprinkle characters through your buffer, take over your other frames, or teach you something new. Watch snow or a fireplace. Rotate cubes in ASCII or with hardware-accelerated graphics. Why limit yourself to cubes? Still, if you do want to stick to cubes, you can solve them. Go ahead, animate something. Play roleplaying games (also check out Howard Abrams's 2023 talk), rogue-likes, multi-user dungeons, classics, chess, trading, Klondike and lots of other card games, a racing game, Minesweeper, 2048, Boomshine, a Gameboy emulator (this other one handles NES). Simulate cities or physics or pets (here's another one) or life. Have fun with so many cats: timers, scrollcats, screensavers. Not a cat person? There's a parrot version and a fish version. Read comics or make memes. Guess words or find them. Learn other alphabets. Make art. You can even turn your Emacs into a Geiger counter for garbage collection.

Then take a step back and marvel at this community of people who try things out, tinker, follow that crazy idea that won't let go of them, and share what they came up with. If you write Emacs Lisp (or want to learn), rummage around in the source code for ideas on how to do things. See if you have any ideas that tickle your fancy. You might be able to get Emacs to do things text editors don't usually do! =)

Check out the Emacs Carnival page for Games to see other posts!

View Org source for this post

You can e-mail me at sacha@sachachua.com.

-1:-- Emacs Carnival September 2026: Games (Post Sacha Chua)--L0--C0--2026-09-04T01:04:46.000Z

Protesilaos: Emacs: doric-themes version 1.3.0

These are my minimalist themes. They use few colours and will appear mostly monochromatic in many contexts. Styles involve the careful use of typography, such as italics and bold italics.

If you want maximalist themes in terms of colour, check my ef-themes package. For something in-between, which I would consider the best “default theme” for a text editor, opt for my modus-themes.

Below are the release notes.


Version 1.3.0 on 2026-09-04

New theme screenshots

All sample images are updated to the current version and cover all the Doric themes: https://protesilaos.com/emacs/doric-themes-pictures.

New doric-lilac and doric-borage themes

I drew inspiration for both themes from plants that exist around my house. They both use the same colours in different shades and sequence. doric-lilac has a light background while doric-borage has a dark background.

More commands to load a theme

The commands doric-themes-rotate-light and doric-themes-rotate-dark are convenience wrappers of doric-themes-rotate. As their name suggests, they are restricted to one kind of theme in the collection.

Similarly, doric-themes-load-random-light and doric-themes-load-random-dark are variants of doric-themes-load-random.

Thanks to Lucas Jiménez for making this suggestion in issue 28: https://github.com/protesilaos/doric-themes/issues/28.

Broader face coverage

The following packages have some new faces or I tweaked them to look better with the rest of the theme.

  • completion-preview
  • elfeed
  • erc
  • flymake
  • gnus
  • mu4e
  • query-replace

Minor refinements to palette values

I made several small tweaks to several themes. The changes are probably too fine to notice or they should now look correct enough to be unnoticeable.

-1:-- Emacs: doric-themes version 1.3.0 (Post Protesilaos)--L0--C0--2026-09-04T00:00:00.000Z

James Cherti: Optimizing Emacs Startup - Guide to Deferred Package Loading with use-package

As an Emacs user, your configuration can easily grow from a few lightweight adjustments to a massive, hundred-package IDE. Without careful management, Emacs startup time can degrade from sub-second execution to several seconds, or minutes, in the worst cases. Eager package loading is one common source of startup overhead. This guide explains how Emacs loads libraries, how use-package configures package loading, and how deferred loading can reduce startup time.

Before we dive in, please consider sharing this article on your website, blog, Mastodon, Reddit, X, Linkedin, or other social media platforms.

Why does Emacs startup get slow?

A naive package declaration looks like this:

;; The kirigami Emacs package provides a unified method to fold and unfold
;; text in Emacs across a diverse set of Emacs modes.
(use-package kirigami)

By default, this causes use-package to load the package during initialization, unless the declaration contains a deferring keyword or another option that changes the loading behavior.

Conceptually, the eager loading is equivalent to:

(require 'kirigami)

The require function ensures that a feature is loaded.

When many packages are loaded this way, the time spent loading and evaluating their code contributes to increasing startup time.

What is Autoloading?

Autoloading registers a function without immediately loading the library that defines it. The library is loaded only when the function is called. For example:

;; Simplified representation of an autoload registration
(autoload 'kirigami-global-mode "kirigami" "Global mode for Kirigami." t)

If M-x kirigami-global-mode invokes the autoload, Emacs loads the kirigami.el library ("kirigami", the second argument), and then calls the actual kirigami-global-mode function. This allows the library's startup cost to be deferred until the function is actually needed.

Explicit vs. implicit deferral in use-package

The use-package macro makes package configuration and loading declarative. By default, a declaration is eager, meaning the package is loaded immediately while Emacs processes the configuration during startup, rather than waiting until the package is needed. A deferring keyword or another loading mechanism can postpone loading until later.

The explicit :defer keyword

Deferral using :defer t (Not necessary)

Note: Explicitly using :defer t is often unnecessary. Keywords such as :commands, :bind, :hook, :commands, and :mode establish loading triggers automatically. These keywords are discussed below.

If a package does not have another loading trigger, :defer t prevents use-package from loading it immediately:

(use-package kirigami
  :defer t)

This prevents the package from being required during startup. Without an autoload or another loading trigger, the package will remain unloaded until something else loads it.

Idle deferral: Numeric argument (Not ideal)

A numeric value to :defer schedules the package to be loaded after Emacs has been idle for the specified number of seconds:

;; Load kirigami after Emacs has been idle for 20 seconds
(use-package kirigami
  :defer 20)

Note: This is not ideal because loading a heavy package after a few seconds will block the event loop and can cause a freeze in the user interface. If multiple packages are deferred via idle timers, they can trigger sequentially during a single idle period, increasing the delay. To prevent unprompted blocking operations, it is best practice to configure explicit autoloads, such as :hook, :bind, :commands, or :mode. (These keywords are discussed below.)

Implicit deferral: Autoload triggers (Recommended solution)

Instead of manually adding :defer t to every use-package declaration, you can use trigger keywords to configure deferred loading automatically. These keywords generate autoloads, ensuring the package evaluates only when you interact with its components:

  • :commands: Generates autoloads for specific interactive commands. Emacs loads the package when you execute the command (for example, via M-x).
  • :bind: Maps keys to commands and automatically creates autoloads for them.
  • :hook: Adds a package function to a hook and arranges for deferred loading.
  • :mode: Adds a file-pattern entry to auto-mode-alist and defers loading until the mode is needed.

Example: Autoloading on major mode file extensions

Instead of loading Markdown mode immediately, use :mode to defer it until you open a .md file:

(use-package markdown-mode
  :mode ("\\.md\\'" . markdown-mode))

Note: Specifying :defer t is unnecessary here, as :mode automatically defers package loading.

Example: Autoloading on keybindings

The :bind keyword maps keys to commands and automatically creates autoloads for them. The package remains deferred until you press one of the defined key combinations:

(use-package embark
  :bind
  (("C-." . embark-act)       
   ("C-;" . embark-dwim)       
   ("C-h B" . embark-bindings)))

Example: Autoloading specific commands

If you do not want to define global keybindings but still need to defer loading until a command is called interactively (for example, via M-x), use the :commands keyword. This generates the necessary autoloads:

(use-package embark
  :commands (embark-act
             embark-dwim
             embark-bindings))

Example: Autoloading on hook execution

Instead of eagerly loading outline-indent-minor-mode (indentation-based code folding) at startup, you can configure Emacs to load and activate it on demand. This ensures the package evaluates only when a python-mode, python-ts-mode, or yaml-ts-mode buffer is opened:

;; The outline-indent Emacs package provides a minor mode for
;; indentation-based code folding.
(use-package outline-indent
  :commands (outline-indent-minor-mode
             outline-indent-backward-same-level
             outline-indent-forward-same-level)
  :hook ((python-mode . outline-indent-minor-mode)
         (python-ts-mode . outline-indent-minor-mode)
         (yaml-ts-mode . outline-indent-minor-mode))
  :custom
  (outline-indent-ellipsis " ▼"))

This also loads outline-indent when you invoke the command manually via M-x outline-indent-minor-mode, outline-indent-backward-same-level, or outline-indent-forward-same-level.

Note: There is no need to specify :defer t separately. The :hook keyword adds outline-indent-minor-mode to python-mode-hook, python-ts-mode-hook, and yaml-ts-mode-hook.

The unconditional use-package :init block

Code inside the use-package :init section is evaluated before the package is loaded. An autoloaded function called from :init can itself cause the package to load immediately, defeating the intended deferral. Therefore, :init is generally appropriate for settings that must be established before the package loads, while package-dependent function calls belong in :config or another deferred trigger (e.g., with-eval-after-load).

(Similarly, code inside the :preface block evaluates before package loading, but it also executes during byte-compilation. This distinction makes :preface the correct location for code required to satisfy the byte-compiler, such as defvar statements, helper macro definitions, or declare-function calls that prevent compiler warnings for deferred libraries.)

Forcing immediate load with :demand

With global deferral enabled, you will occasionally encounter packages that must be loaded immediately (e.g., themes, keybinding managers, or daemon-side servers). You can override global deferral on an individual basis using :demand t:

(use-package tomorrow-night-deepblue-theme
  :demand t ; Force immediate loading
  :config
  ;; Load the tomorrow-night-deepblue theme
  (load-theme 'tomorrow-night-deepblue t))

Note: If a declaration specifies both :demand t and :defer t (or triggers), :demand t takes precedence and forces eager loading.

The use-package :after keyword

The use-package :after keyword allows you to defer the initialization of a package until one or more specified target packages have been fully loaded. This is practical for:

  • Add-on packages that extend a base minor mode.
  • Integration packages that bridge two distinct tools together.

Consider embark-consult, a package that integrates embark with consult. You only need the embark-consult package after embark and consult are loaded:

(use-package embark-consult
  :after (embark consult))

In this scenario, embark-consult remains entirely inactive during startup. Emacs won't evaluate the embark-consult configuration until it has successfully loaded both embark and consult.

Forcing immediate loading

Consider this configuration:

(use-package kirigami
  :defer t
  :custom
  (kirigami-show-menu-bar t)
  (kirigami-show-context-menu t)
  :init
  (kirigami-global-mode 1)) ; This forces immediate loading

Although :defer t is present, evaluating (kirigami-global-mode 1) during :init triggers the autoload for kirigami-global-mode. The package is therefore loaded during initialization, defeating the intended deferral.

Loading after the Emacs init phase

If you prefer to load a package after the init phase, use the :hook keyword:

(use-package kirigami
  :custom
  (kirigami-show-menu-bar t)
  (kirigami-show-context-menu t)
  :hook (after-init . kirigami-global-mode)) ; Activate after init

Using :hook (after-init . kirigami-global-mode) adds the mode to after-init-hook. This ensures the package remains deferred while Emacs evaluates the rest of your init file, guaranteeing the mode is active before the editor is ready for user interaction.

Global laziness: use-package-always-defer (Not recommended)

You can configure use-package to automatically assume :defer t for declarations that do not otherwise establish eager loading:

(setq use-package-always-defer t) ; NOT RECOMMENDED

This changes the default behavior so that use-package declarations are deferred unless a declaration explicitly requires immediate loading or establishes another loading mechanism.

Note: Global deferral breaks packages that rely on background execution, global hooks, or immediate side effects to function correctly. You will find yourself tracking down silent failures and adding :demand t throughout your initialization file to force eager loading for things like themes, modeline managers, and daemon servers. Instead of relying on a blanket setting that obscures the loading state, setting use-package-always-defer to nil and explicitly deferring packages where appropriate results in a more deterministic setup.

Diagnostics and verification

To check whether a package feature has been provided in the current Emacs session, use the featurep function. For example, for the kirigami package:

(featurep 'kirigami)
  • Evaluates to nil when the feature has not been provided.
  • Evaluates to t when the feature has been provided.

Debugging and macro expansion optimization

If you need to diagnose loading order or macro expansion issues, use-package provides built-in settings to help debugging and performance.

Setting use-package-expand-minimally to t causes the use-package macro to generate less boilerplate code. If you byte-compile your initialization file, this reduces the size of the compiled file and can yield slight performance improvements:

(setq use-package-expand-minimally t)

Setting use-package-verbose to t forces use-package to log package loading events to the *Messages* buffer. This is useful for verifying whether deferred packages are loading when expected:

(setq use-package-verbose t)

Setting use-package-enable-imenu-support to t causes imenu to see use-package declarations. This allows you to quickly jump to specific use-package blocks using M-x imenu:

(setq use-package-enable-imenu-support t)

By default, the :hook keyword automatically appends -hook if you omit it. Setting use-package-hook-name-suffix to nil disables this behavior. Disabling this forces you to write fully qualified hook names (e.g., (python-mode-hook . outline-indent-minor-mode)):

(setq use-package-hook-name-suffix nil) ; NOT RECOMMENDED

Note: It is not recommended to set use-package-hook-name-suffix to nil if you maintain a pre-existing configuration that relies on the default implicit behavior. Modifying this variable globally instantly breaks every existing :hook (mode . function) declaration that omits the -hook suffix. Unless you are building a configuration from scratch or can rewrite and validate every hook assignment in a single pass, leaving the default behavior intact is the most practical decision.

Startup profiling: measuring the impact

To benchmark startup optimizations, measure startup at a point later than the end of init-file processing.

Benchmarking startup

For an accurate startup-time measurement, read:
Measuring Emacs startup time more accurately than the built-in emacs-init-time

Profiling individual packages

use-package can collect loading statistics. Enable statistics gathering after use-package has been loaded but before the use-package declarations are evaluated:

(setq use-package-compute-statistics t)

After restarting Emacs, execute: M-x use-package-report

This displays a tabulated buffer with timing information for the use-package phases.

Advanced profiling with benchmark-init

While use-package-report offers insights into declarative package loading, the third-party package benchmark-init (available on MELPA) provides a more granular view of the entire Emacs startup process. It tracks the time spent loading individual files and executing functions across the whole initialization sequence, allowing you to see exactly where you can influence startup time.

Related links

-1:-- Optimizing Emacs Startup - Guide to Deferred Package Loading with use-package (Post James Cherti)--L0--C0--2026-09-03T16:36:20.000Z

Irreal: The Vim UserGettingBored Autocmd

Evan Hahn has an amusing post about the Vim UserGettingBored autocmd. Autocmds in Vim are essentially like hook functions in Emacs. They get fired when certain events happen in the editor.

Way back before Vim 6.0, Bram Moolenaar added the UserGettingBored autocmd to the Vim documentation. It was a joke and the command didn’t actually do anything—or rather, it was never invoked and didn’t really exist. It was like an Emacs hook that was documented but didn’t actually occur. The documentation said “When the user hits CTRL-C. Just Kidding.” In 2013 the documentation changed to “When the user presses the same key 42 times. Just kidding! :-)” and, there were various other tweaks along the way. Finally, in 2022 an unofficial plugin was created by Mike Smith that actually implemented the autocmd and did something. You can read what it was in Hahn’s post.

None of this matters at all, of course, except to provide evidence that Vim users can be as whimsical as us Emacs users. You probably won’t see anything like this in VSCode. It wouldn’t be “professional”.

-1:-- The Vim UserGettingBored Autocmd (Post Irreal)--L0--C0--2026-09-03T15:02:23.000Z

Dave Pearson: next-gh-pr.el v1.1.0

A quick little update to next-gh-pr.el. Since writing the first version I've found it to be really useful, but I've also bumped into one small issue. Sometimes I'll create a draft PR before I remember to start the changelog entry, and so then I want to insert the current PR link rather than the next one.

Now, sure, I can just go and copy the URL and create the link in the changelog, but where's the fun in that when I can solve the problem in Emacs Lisp? So, with this in mind, I've just updated the package so that, with a prefix argument to the command, it will include a link for the latest PR rather than for the next one.

This does, of course, make the name a little less correct, but I'm not going to rename the package or the command for this one minor difference in intent.

-1:-- next-gh-pr.el v1.1.0 (Post Dave Pearson)--L0--C0--2026-09-03T14:17:17.000Z

TAONAW - Emacs and Org Mode: RSI/Pinky Update

I wanted to write a short update to my Emacs pinky / RSI situation.

I am still using my Kinesis Freestyle Edge keyboard. I think I’ve decided on the Glove80 as a replacement, but the price and the inability to easily return it are giving me second thoughts. Its style is very different, and I know I will have a hard time adjusting. Still as someone pointed out to me it’s not like I’m forced to throw away my current keyboard, which is true. I see myself using my Kinesis for work on the Mac while trying the new Glove80 on Linux for blog posts and more leisure-time typing.

I’ve made a few adjustments, however:

Auto-generated description: A split ergonomic keyboard with blue backlighting is connected by a cable, positioned on a large owl-themed mousepad alongside a Logitech gaming mouse.

First, the spread between the two halves is much wider. This is something I was considering when I first got this keyboard, but I didn’t follow through with it. I got used to the wider split within a couple of days. It does make my shoulders feel better and more natural, especially when I’m standing at my desk. With that, I also started using the keyboard’s full tenting capability at all times: I used to have the keyboard flatter when sitting down.

The mouse is in the middle, and since I use it for quick bursts between otherwise typing sessions, it’s fine. When I game, I move it back to the right. I can basically ignore the entire right half of my keyboard while gaming, since most of the action happens on the left.

Gone is the Caps Lock as Ctrl. Caps is just Caps again. I’m still pressing it now and then out of habit, but for the most part, I switched back to using the original Control keys. I try to force myself to use my thumb, which means I have to curve my thumb into my left palm if I use the left side, or I just go all the way to the right and use my Ctrl key there. This is the most difficult part because it’s not just muscle memory: the Ctrl key is out of the way, and it can slow me down.

I also realized that the Windows key, which also acts as the Command key on my Mac, should be way more prominent than this keyboard allows. Thanks to PopClip on the Mac, I don’t use it much for copy-pasting on macOS (and since you have to use your mouse anyway to highlight text in Emacs, having PopClip is just a natural extension that I’m happy to have).

Speaking of the mouse, I know some of you will wrinkle your nose in disgust — but I started using it more in Emacs. Sometimes when I want to select text further down or up from where I am with the mark, it’s just easier to grab it and highlight the text this way instead of using the keyboard. I am also in the process of rediscovering the menus in Emacs (yes, those menus that all of you Emacs gurus love to hide), especially when it comes to reminding myself of useful shortcuts I keep forgetting — like registers, the mark ring, and other org-mode tricks I keep forgetting about.

My pinky still annoys me, but less. I seem to have this habit now of letting it hover over the keyboard instead of resting on the shift key. It’s almost as if I lost my ability to place my left hand correctly and naturally at times. It’s one of the things I hope the Glove80 might help with.

-1:-- RSI/Pinky Update (Post TAONAW - Emacs and Org Mode)--L0--C0--2026-09-02T21:36:31.000Z

Irreal: The Newcomers Presets Theme

As the result of a long standing discussion on the Emacs devel list, Emacs 31 comes with what amounts to a starter kit called newcomers-presets1. The idea is to provide newcomers with a set of useful configurations to help them ease into Emacs. It is, however, much more than training wheels for n00bs. The presets described in it are ones that most experienced Emacs users are already using.

I know this because last April Sacha Chua wrote a splendid post that describes what’s in it. I somehow missed it back when she first posted it but she mentioned it in the latest Emacs News. As I’ve said before, I don’t usually write about things that she’s already covered in Emacs News but I broke that rule yesterday so I might as well break it again, especially since her post is especially worth your attention.

When newcomers-presets was first introduced I didn’t pay too much attention to it but then Prot recommended it for experienced users as well as beginners. The problem was that I was unclear on what exactly it did. Then I saw the reference to Chua’s post. It has a comprehensive list of what it does along with a explanation for each setting.

I won’t be using it because I’m not a fan of starter kits for experienced users and because even if I like everything in it today, it could change tomorrow. It’s better, in my opinion, to see what’s in it and add the things you think are useful to your own configuration. Chua includes a handy “Copy code” button with each item to make that easy.

Footnotes:

1

For some reason it’s described and implemented as a “theme” but actually has little to do with themes as we normally think of them.

-1:-- The Newcomers Presets Theme (Post Irreal)--L0--C0--2026-09-02T15:32:19.000Z

Emacs Redux: Working with Git Worktrees in Magit

I’ll admit that until fairly recently I had no idea git worktrees existed. They’ve been part of git for a decade1 and I never once needed them. Feature branches did the job just fine - create a branch, do the work, merge it, delete it, and start over.

What finally introduced me to worktrees was, of all things, AI coding agents. Tools like Claude Code create a worktree for each task, so several agents (or several sessions of the same agent) can work on the same repo in parallel without stepping on each other - or on you. Suddenly my projects directory was full of cider-this and cider-that siblings, and I figured I should understand what’s actually going on there.

Worktrees vs Branches

A branch is just a movable pointer to a commit, and making one is basically free. The catch is that a repository has a single working directory, so working on two branches means switching that directory back and forth. You know the routine: stash your half-done work (or commit it), check out the other branch, do the thing, check out the first branch again, unstash. It works, but it’s tedious, and it gets worse when the two branches leave your project in different build states and every switch means recompiling half the world.

A worktree gives you an additional working directory attached to the same repository:

$ git worktree add ../cider-smart-targeting -b smart-form-targeting

Now ~/projects/cider-smart-targeting is a full checkout of that branch, while your main checkout stays exactly where it was. The object database, refs, stashes and remotes are all shared - a worktree is not a clone, so fetching in one is fetching in all, and creating one is nearly instant. Each worktree gets its own HEAD and index, and git enforces one simple rule: a branch can only be checked out in one worktree at a time.

When are they worth it? Whenever two things need to happen at the same time: a long test run on one branch while you work on another, reviewing a PR without disturbing your half-done work, or - the reason everyone is talking about them these days - AI agents doing their thing in isolation. The price is pretty modest: your working files exist on disk more than once, and anything that isn’t tracked by git (dependencies, build caches, node_modules and friends) has to be set up again in each worktree.

If branches have never felt limiting to you, that’s fine - they didn’t for me either, for over a decade. Worktrees are one of those features you don’t miss until your workflow changes.

What About Jujutsu?

While we’re on the topic of working copies - the most interesting thing happening in version control right now is Jujutsu (jj), a git-compatible VCS that makes the problem worktrees solve mostly go away. In jj the working copy is a commit, snapshotted automatically as you work. There’s no staging area and no stash, because there’s no uncommitted state that could be lost or get in the way - switching contexts is always safe. It also has proper support for parallel working directories (jj workspace), plus an operation log that makes practically everything undoable. That last bit is part of why the AI agent crowd has taken an interest in it too.

You can use jj on top of an existing git repository (they call this a colocated repo), and your colleagues - and Magit - will keep seeing a normal git repo. I’m still just an observer here, but it’s clearly a project to keep an eye on.

Worktrees in Magit

Back to Emacs land. Magit has had worktree support for years, hiding behind Z:

Key Command Description
Z b magit-worktree-checkout Check out an existing branch in a new worktree
Z c magit-worktree-branch Create a new branch and worktree in one go
Z g magit-worktree-status Jump to another worktree’s status buffer
Z m magit-worktree-move Move a worktree
Z k magit-worktree-delete Delete a worktree

The best part is that there’s nothing else to learn. Each worktree gets its own Magit status buffer, and every Magit command operates on the worktree the current buffer belongs to. Z g is the only switching mechanism you need, and even that is just a shortcut for visiting another status buffer.

A few practical tips from my (admittedly recent) experience:

  • By default the status buffer doesn’t list your worktrees. Fix that with:
(magit-add-section-hook 'magit-status-sections-hook
                        #'magit-insert-worktrees
                        nil t)

Now every status buffer shows all worktrees of the repo, and you can hit RET on any of them to jump there.

  • Create worktrees as siblings of the main checkout with descriptive names (cider-smart-targeting next to cider), not nested inside it - nesting confuses grep, find and plenty of other tools.

  • Magit’s branch selection annotates branches that are checked out in another worktree with the worktree’s path, and refuses to check them out a second time (that’s git’s rule, not Magit being difficult). If you ever wondered why a branch “won’t check out”, that’s usually why.

  • Each worktree is its own project as far as project.el (or Projectile) is concerned, so project switching, per-project buffers and search all work naturally.

  • Anything that’s not tracked by git doesn’t come along for the ride. You’ll have to install dependencies once per worktree and start with a cold build cache. For Elisp that costs you nothing; for a big JVM or JS project it’s the main downside of the whole approach.

  • When you’re done with a worktree, delete it with Z k (or with git worktree remove from the command line). If you just delete the directory by hand, git worktree prune will clean up the leftover bookkeeping.

Closing Thoughts

These days my workflow with agents usually looks like this: an agent does its work in a worktree, I review the changes there in Magit (often while another task is running in a second worktree), and the worktree goes away once the branch is merged. Maybe one day I’ll find other uses for worktrees - we’ll see.

Are you using git worktrees - and did you discover them the same roundabout way I did? I’d love to hear about it in the comments!

That’s all I have for you today. Keep hacking (in parallel)!

  1. They were introduced in git 2.5, released all the way back in July 2015. 

-1:-- Working with Git Worktrees in Magit (Post Emacs Redux)--L0--C0--2026-09-02T14:28:00.000Z

Tim Heaney: Alpine Linux

I just installed Alpine Linux on an old laptop. Works great! Last year, I complained about all of the lightweight Linux distributions being based on Debian. Alpine is not based on Debian and is very lightweight indeed; it uses busybox in place of many GNU utilities, musl libc in place of glibc, and openrc in place of systemd. Alpine is so small that lots of folks use it for containers and things.
-1:-- Alpine Linux (Post Tim Heaney)--L0--C0--2026-09-02T00:00:00.000Z

Sacha Chua: Emacs Chat 30: Fabrice Niessen (en français, partie 2)

Nous avons parlé en direct avec Fabrice Niessen d'Emacs pour la deuxième fois. Voici ses notes.

Correction : J'ai voulu dire Speaches (un serveur de reconnaissance vocale) au lieu de speechd (un serveur de synthèse vocale)

Voici notre conversation précédente : Emacs Chat 28: Fabrice Niessen (en français)

Chapitres

  • 0:00 Introduction
  • 3:00 gptel
  • 13:42 diff
  • 17:52 gptel-commit-message
  • 23:30 docstrings
  • 24:03 hs-minor-mode - hideshow
  • 31:03 Super-whisper
  • 41:21 Dotfiler
  • 59:12 Enseignement d'Emacs

La transcription légèrement corrigée

Details

0:00 Introduction

Prot: Tu peux prendre le lien correct cette fois.

Fabrice: Ah, voilà, je suis prêt avec.

Sacha: Ah, ok. Ok. Nous sommes en direct.

Prot: Ah, nous sommes en direct. Très bien.

Sacha: Donc, je vais passer à vos notes. Bonjour à toutes et à tous et bienvenue au 30e épisode d'Emacs Chat. C'est la suite de notre conversation avec Fabrice Nissen. Merci de nouveau Fabrice d'être là et merci également à Prot de nous rejoindre. Donc, on continue. Lors de la conversation précédente, nous avons parlé de ton expérience sur Windows et sur WSL, des thèmes de publication vers HTML ou vers PDF, de ton kit de configuration Leuven, des petites améliorations et des automatisations et des formations et des cours particuliers en français, en anglais, en néerlandais et en espagnol. C'est incroyable, ça! Du coup, si ça ne te dérange pas, j'essaie d'en savoir plus sur ton flux de travail, puis repasser à une discussion sur l'enseignement d'Emacs, de nombreux changements et de grandes incertitudes à l'époque de l'IA. Qu'en penses-tu? Depuis notre conversation, tu as ajouté beaucoup de fonctions, d'outils, de préréglages (presets) de gptel à ta configuration. C'est un sujet très tendance YouTube a évidemment beaucoup de video de ce genre en anglais, mais pas encore en français, je pense. Peux-tu nous montrer ton flux de travail avec l'IA sur Emacs?

Fabrice: Volontiers. Merci pour l'invitation et l'image bouge. Merci pour l'invitation pour cette trentième fois. Donc, je vais partager mon écran.

Prot: Merci.

Fabrice: Alors, je tape share, voilà. Et donc, ce sera celui-ci. Est-ce que vous voyez mon écran ?

Prot: Oui, je peux voir ton écran.

Fabrice: Ok, très bien. Très bien.

3:00 gptel

Fabrice: Donc en fait, sur gptel, j'utilise pas encore beaucoup, beaucoup, beaucoup, mais de plus en plus. Je suis aussi un peu novice, un peu, mais j'ai effectivement, et récemment d'ailleurs, fait quelques petites améliorations de layout au standard. Donc j'ai, par exemple, utilisé le raccourci C-<F1> pour lancer… Je n'ai pas lancé le chat. Je vois que je n'ai pas ma dernière configuration. C'est peut-être l'occasion aussi de parler, juste un petit break. Vous voyez que j'utilise Helm. Je ne sais pas ce que vous utilisez vous. Je suis extrêmement content de Helm, ce qui me permet de taper. Je ne dois plus savoir où se trouve un fichier, dans quel répertoire. Il suffit que je connaisse quelques morceaux de son nom. Et je vais retrouver directement le fichier ou le buffer actif. Donc ici c'est gptel et ce sera le point el. Il est là. Donc ici, je vais lancer et je vais faire vb rapidement pour le keychord eval-buffer. Voilà, donc il est évalué. Donc je vais avoir un look un peu différent maintenant si je recommence. Voilà, C-<F1>. Donc on peut lui dire bonjour. C-c C-c pour envoyer. Voilà. Et donc ici il répond. Donc une petite utilisation que j'ai faite, c'est gptel-highlight-mode qui permet de mettre la réponse avec un certain fond et une petite barre ici dans la frange pour bien identifier la différence entre les questions et les réponses. Je peux lui demander d'écrire Un exemple de code Python. S'il fait comme d'habitude, il va faire la suite de Fibonacci. Voilà. Et donc, qu'est-ce qu'il a fait ? Ah non, les nombres premiers. Ici, c'est quelque chose que je commence à utiliser de plus en plus, le buffer de conversation gptel. Une petite customisation que j'ai faite. D'ailleurs, j'ai envoyé un issue à l'auteur de gptel pour éventuellement qu'il l'introduise directement dans... Dans gptel, c'est le fait de recoloriser les codes blocs dans les réponses en utilisant la couleur que j'ai dans mon thème pour Org Mode. Donc ici, c'est exactement comme dans un fichier Org Mode. J'ai le fond jaune pour le code et les délimiteurs ici, de début et de fin, en gris. Et ça, c'est très important, je trouve, pour mettre en évidence comme il faut le code bloc. Et le fait, bon ici ce n'est pas de l'Emacs Lisp, mais si j'avais demandé, si je pouvais demander de faire la version, donnez-moi la version de l'Emacs Lisp. C-c C-c. Et l'avantage évidemment de faire ici, donc à la fin de la réponse, il y a un scroll automatique vers le bas. Ah d'accord. Et ce qu'il fait aussi, c'est qu'il fait un formatting pour que ça reste moins de 80 caractères. Et donc ici, l'avantage, c'est que c'est dans Emacs et que c'est l'Emacs Lisp. Je peux directement faire C-x C-e. Et je pourrais tester les fonctions. Je pourrais faire ici. Ça me permet d'éviter de faire des copier-coller entre ChatGPT ou Copilot ou n'importe lequel. Ici j'ai tout directement là dedans. C'est extrêmement pratique.

Sacha: Tu as aussi des fonctions pour ajouter des contextes, des fonctions, d'autres choses dans ta configuration. Au lieu d'utilisation de conversation buffer, tu as aussi utilisé les fonctionnalités gptel dans ton code.

Fabrice: Il y a le rewrite que j'utilise parfois, ou alors pour répondre avec le contexte, il y a notamment le add context ou add context file. Je n'ai pas encore beaucoup joué avec, donc je sais qu'on peut aller dans Dired et puis faire gptel-add-file, add context file, je crois. Et je l’ai déjà bindé sur C-c g, donc tout ce qui est C-c g et c’est gptel. J’ai déjà utilisé le raccourci avec un petit a, donc C-c g a pour ajouter du contexte. Mais je ne l'ai pas encore vraiment utilisé, donc je n'ai pas encore beaucoup d'expérience avec. Par contre, je fais parfois dans des fichiers, je peux faire, si je prends un fichier Org, on peut aller dans le fichier ici avec mes petites notes d'aujourd'hui. Ça pourrait m'arriver ici de dire je veux traduire ces deux paragraphes en anglais par exemple. Je vais les sélectionner. Je vais faire C-u C-c RET pour avoir le menu de gptel. Là, je vais changer la directive puisque la directive ici c'est tu es un assistant, etc. Je vais changer la directive. Je vais en fait utiliser le S, Set System Message. Là, j'ai une série de prompt, en fait, de system prompt, de directives, ça s'appelle, sous gptel. Donc, une série de directives, de prompt qui sont prêts. Mais ici, je vais faire un spécifique. Donc, je vais encore faire une fois s. Et là, je vais taper, par exemple, traduit. Là, quand on a fini, on peut faire C-c C-c. Ici, on voit la directive qui est là. Pour l'instant, si je ne change rien, il va envoyer ma sélection et il va insérer la réponse. Là, je ne vois pas très bien. Il faut que je déplace un truc. Il va insérer la réponse à la fin. Je pourrais faire ça, par exemple, pour voir comment il a traduit. Comme ça, je garde au-dessus l'input. Je vais faire effectivement RET ici. Parfois, c'est un peu compliqué à lire.

Prot: Qu'est-ce qu'il m'a fait ?

Fabrice: On dirait qu'il a pris plus de lignes.

Sacha: Il y a un problème des démos en direct.

Fabrice: Je vais bien le refaire. C-u C-c RET. Je vais bien sectionner ma région. La directive est là. Donc, je vais faire ici Enter. Voilà. Donc j'ai vu que l'input était, la section était toujours sélectionnée, donc j’ai fait C-w pour l'entrée, et puis j'ai gardé ce qu'il y a. Donc ici, voilà, typiquement, il a fait le job facilement, donc je suis nouveau restant dans Directement dans Emacs, on fait des copies-coller avec d'autres interfaces externes.

Sacha: Je vois que tu peux aussi remplacer la sélection avec les résultats ou peut-être rediriger l'output à un autre tampon pour comparaison.

Fabrice: Exact. Donc, je n'ai pas encore tout utilisé, mais je pourrais ici essayer. On va essayer. On va traduire en français, par exemple. Donc, j'ai bien sélectionné. Je vais faire C-u C-c RET pour avoir de nouveau le menu. Ou alors, M-x, j'ai fait tel menu. Donc là, je vais changer et je vais mettre la directive 2 fois S. Et cette fois-ci, je vais traduire en français. C-c C-c, voilà. Et donc, je vais mettre ici le respond in place. Et donc, en théorie, il va écraser ma sélection par sa réponse. Donc, je vais faire le i ici. Et donc, en fait, il faut toujours lire la dernière ligne ici. On voit ce qu'il va faire. C'est un résumé de ce qu'il va faire en fonction des options qu'on a choisi. Donc ici, quand je vais faire ret, quand je vais faire return, il va remplacer la sélection avec la réponse. Donc je fais RET. Ah, et là j'ai peur. The conversation must end with a user message. Qu'est-ce qu'on va faire ? On va rester, on ne sait jamais. C-x C-x C-u RET Non, ce n'était pas ça. C-x u. C-x u. Voilà. C-x C-x C-u RET Ben si, c'est ça. Non, C-u C-c RET. Il y a trop de raccourcis. Je fais la sélection, voilà. C-u C-c RET. J'ai mon menu, j'ai ma directive en haut qui est bonne. Je vais faire le i, Replace, la sélection du response, RET. Bon, j'ai l'erreur. Donc là, il faudrait que je regarde un peu plus. Je n'ai pas beaucoup utilisé ça. Et en fait, justement, sur le rewrite, il faut que j'utilise aussi un peu plus pour du texte et pour du code. Et je sais qu'il y a plusieurs possibilités après.

13:42 diff

Fabrice: Il y a aussi moyen de dire, il va remplacer en théorie par le nouveau texte, mais il y a moyen de montrer aussi son changement sous forme de diff ou de ediff. Donc il y a moyen de dire accepte. J’ai vu qu’après on peut faire C-c C-a pour accepte, C-c C-e pour ediff, C-c C-d pour diff. Et donc il peut rouvrir différents buffers pour bien visualiser la différence. Si c'est une traduction, tout serait changé. Et si on modifie du code, par exemple, en disant rajoute-moi un paramètre, on a envie de voir vraiment la différence et de sûr qu'il n'a pas fait plus de choses que ce qu'on a demandé. Donc il y a moyen de faire un diff ou un ediff après.

Prot: C'est peut-être mieux comme ça avec Ediff.

Fabrice: Oui, moi j'aime beaucoup Ediff. D'ailleurs, c'est quelque chose, je ne crois pas que je l'ai montré l'autre fois. Donc ici, moi j’ai C-<f9> chez moi, c’est privé, pour lancer vc-dir sans me poser de questions sur quel est le répertoire qu'on veut analyser. Donc ici, je vais faire C-<f9>. Donc il me lance vc-dir sur le répertoire dans lequel je me trouvais. On voit que j'ai fait une modification dans le fichier readme. Si je fais égal, on voit un diff unifié avec les plus et les moins en dessous. Il faut toujours relire avant de committer les diffs parce que là il y a une ligne qui n'était pas prévue d'être committée en imaginant que je devais faire le commit. Je vais d'ailleurs effacer cette ligne. Ce qui est pratique ici dans tout ça, c'est que j'ai fait une modification, j'ai sauvé. Ce buffer-ci est le diff. Je n'ai pas besoin de retourner dans le vc-dir et de refaire un égal. Je peux faire g, faire refresh, il recalcule le contenu. Donc ici, j’ai fait g et simplement, il a recalculé le diff. Et je ne vois plus que le paragraphe qu'on devait traduire, qui a été effacé. Pour l'instant, c'est une possibilité. Et l'autre possibilité à partir de vc-dir. Pour l'instant, je n'utilise pas encore Magit parce que j'ai eu pendant des années des répertoires, des repos qui étaient sous SVN, subversion. Certains étaient déjà sous Git. Je voulais une seule interface pour gérer tous mes repositories. Et donc, j'ai tout construit sur vc-dir qui marche très bien. Voilà, je suis sûr que Magit, pour Git, est plus sophistiquée, plus jolie à regarder, sans doute. Mais en fait, je fais tout mon boulot ici avec vc-dir. Donc, si j'avais égal, j'ai un diff unifié. Ça, c'est standard, je crois. Si je fais grand E, ça, c’est pas standard. Ça m'appelle ediff. Donc là, directement, j'ai ediff, puis j'ai plus qu'à faire next. Voilà, next. Bon, j'ai qu'un changement ici. Ce paragraphe là qui a disparu. Donc ça c'est très pratique, directement pouvoir lancer Ediff à partir d'un fichier modifié. Et alors une petite chose que j'ai récemment rajoutée dans Git, c'est donc ici je viens de refaire un égal pour avoir un Ediff unifié. Donc ici j'ai le Le diff unifié où j'ai ce paragraphe-là qui est apparu. En fait, depuis n'importe quel diff, donc output qui est sous forme de diff avec des plus et des moins, Ici, c'est à partir de vc-diff. Ça pourrait être le résultat d'un Git diff dans un buffer. Ça pourrait être dans Magit. Je peux lancer la touche avec w, ça marche. Ça m'a lancé la génération d'un message de commit via gptel. J'ai écrit une petite fonction qui va envoyer tout ce buffer-là.

17:52 gptel-commit-message

Fabrice: Ça envoie le buffer en entier à gptel. En lui demandant de m'écrire un message de commit. C'est gptel-commit-message. Je l'ai mis sous GitHub. Il génère un message de commit à partir d'un fichier, d'un buffer diff. Il envoie tout ça et donc il a été généré n'importe comment, donc ce n'est pas lié à Magit. Parce qu'il y a beaucoup, il y a plusieurs solutions qui existent déjà dans MELPA et c'est lié souvent à Magit. Ou à Git, en fait, il faut avoir stagé les fichiers. Moi, je n'ai pas spécialement envie de mettre des fichiers, de les stager dans Git. J'ai envie de pouvoir simplement sélectionner moi-même ici dans l'interface un, deux, trois fichiers et regarder la différence pour ces fichiers-là. Génère-moi un message pour ces fichiers. Ici, dans le package, qui est tout petit, il fait une fonction. Il y a juste une fonction. Il y a le nom du buffer et la fonction. Derrière, je l’ai mappé sur w. Quand je suis dans le diff-mode-map, dans vc-dir, Je tape w pour write. Ça me gênait le fichier de ce message. Donc, si je reviens sur ton changement, voilà. Donc, j'avais ici, j'avais fait W à partir d'un buffer diff. Ça a envoyé via gptel, ça a envoyé le diff, ça a récupéré un message de commit, ça l'a mis dans le presse-papier et ça m'a ouvert un buffer, donc ça a rajouté un buffer à côté où je pouvais le voir. Je vais le refaire ici. Donc ça envoie, voilà. Et donc on voit, ça ouvre un buffer avec le message en bas. Et en fait, je peux très bien me dire, tiens, en fait, ce message-là, il ne me plaît pas trop. Puisqu'en fait, on a chaque fois des réponses différentes. Donc je peux très bien refaire W en haut. Il va réenvoyer le truc. Cette fois-ci, ça ressemble assez fort. Je vais le refaire. Il change un petit peu, il n'y a pas beaucoup de changement ici parce que ce n'est pas le même texte. Donc, remove personal introduction from readme.

Prot: Et il a copié aussi sur le kill ring.

Fabrice: Oui, exactement. Donc, maintenant, en fait, je n'ai plus qu'à refaire ctrl . Je suis déjà là-dessus. Je n'ai plus qu'à faire dans V, c'est dire, next action, c'est V. Pour dire, fais l'action suivante qui a du sens. Ici, puisque j'ai un fichier modifié, l'action suivante qui a du sens, c'est de committer. Donc, il m'ouvre un buffer pour pouvoir mettre mon message de commit. Donc, je fais simplement C-y. Et éventuellement, je vais un peu le modifier. C-c C-c, c’est envoyé. Et donc, c'est committé. Ça, j'utilise depuis quelques mois quand même, je dirais depuis 4, 5, 6 mois. Depuis cette période-là, je fais tous mes messages de commit avec gptel, l'intelligence artificielle. Et c'est merveilleux parce que ça a augmenté la qualité de mes messages de commit. C'est indéniable. Avant, je mettais souvent dans mes fichiers à moi « update ».

Sacha: Moi aussi.

Fabrice: Et en plus, non seulement j'ai un beau message qui dit vraiment ce qu'il y a, et je relis encore, non seulement avant de committer, je relis toujours. Au minimum, je fais égal pour voir la différence. Ou s'il y a beaucoup de différences, je vais faire un grand E pour pouvoir en Ediff comparer les versions avant et les versions après, systématiquement. Mais en plus, je fais générer mon message de commit et je lis aussi le message de commit. Voilà, pour voir qu'il a bien compris et qu'effectivement aussi, le résumé est correct par rapport à ce que j'ai fait. Parce que parfois, s'il y a eu des centaines de changements, je pourrais voir aussi quelque chose qui me tient. Il me parle de ça, c'est bizarre qu'il me dise que... Je ne sais pas, que j'ai enlevé un paragraphe, ce n'est pas normal. Voilà, ça pourrait aussi m'attirer l'attention sur, tiens, est-ce que je n'ai quand même pas trop vite lu le diff et donc d'aller voir des trucs. Donc, c'est vraiment... Ça change la qualité du commit sous Git. C'est vraiment quelque chose que je conseille à tout le monde. Et donc ici, pour vc-dir, il a fallu que j'écrive ma fonction moi-même, parce que les autres Git gptel commits, il faut avoir stagé les fichiers, donc ça regarde ce qu'on a stagé, et ça fait un message de commit par rapport à ce qu'on a stagé. Maintenant, on essaie de dire, on ne stage pas. On ne voit pas cette opération-là, ça se fait tout seul. Donc on a les fichiers modifiés et puis on fait le commit directement. Donc ça fait le git commit -a, git commit add… On ne passe pas par cette étape de staging. Et puis voilà, je trouve que c'est plus simple, simplement de sélectionner les fichiers, ceux que je veux, et puis de voir la différence, et puis de committer.

Prot: Oui, c'est mieux comme ça.

23:30 docstrings

Sacha: Tu utilises également gptel pour générer des docstrings. Peux-tu montrer ?

Fabrice: Alors, on pourrait aller dans un fichier. Par exemple, ce fichier de gptel-commit-message. Donc voilà. Donc, j'ai une seule fonction.

24:03 hs-minor-mode - hideshow

Fabrice: Et donc, chez moi, quand je rouvre par défaut, j'utilise aussi H.S. hs-minor-mode, hideshow. Donc, tous les corps des fonctions, etc., il les réduit. J'ai rajouté qui m'indique le nombre de lignes pour avoir quand même une petite idée, pas juste trois petits points, mais avoir une petite idée de ce qui se cache en dessous. Et donc après je fais metashifta chez moi, ce qui est visible mode. Donc ça m'ouvre. Il y a visible mode pour les fichiers Org avec les drawers et puis ça fait plusieurs choses visible mode et aussi HS show all. Donc ici, voilà, j'ai un message, j'ai une fonction, j'ai un message, un docstring et on va dire que j'ai écrit moi-même un docstring un peu plus bête. Je mets xxx à la place. Et donc, ce que je peux faire, c'est sélectionner toute la fonction. Oui, les métas, parenthèses, accolades, fréquentes. Ils ne sont pas toujours faciles sur un clavier français, d'ailleurs. Il faut faire le AltGr, tu vois. Le QWERTY est plus facile pour certains trucs. Donc ici, j'ai sélectionné, donc je vais faire C-u C-c RET. Donc je retombe sur le gptel menu. J'ai une directive traduite en français, donc ça, c'est pas très bon. Donc je vais changer la directive, donc je vais faire ici le s pour Set System Message. Encore une fois, le s, puisque on pourrait imaginer, si j'avais ça tout le temps, que j'ai write the string. Je n'ai pas de preset, de prompt avec ça, donc je vais essayer de le faire à la main. Je vais refaire S pour pouvoir moi-même éditer le message. Et donc ici, je vais taper à la place écrit un docstring correct montrant ce que la fonction fait. exactement. directive est correcte. Alors, je n’ai pas ?? le b, là, other buffer, ou je pourrais le mettre dans le kill ring aussi. On va le mettre dans le kill ring, par exemple, donc je vais faire k. Donc j’envoie la réponse dans le kill ring, k. Donc on lit bien, toujours en bas, ce qu'il va faire. Donc ici, quand je vais faire RET, il va envoyer les lignes sélectionnées avec la réponse dans le kill ring. Parfois, ce qui est dommage, c'est qu'on n'a pas un sablier comme des applications Windows. Voilà, ici, j'ai la réponse. C'est dommage qu'il n'y ait pas de sablier. Il y a tellement de choses qui passent sur l'echo area qu'on ne voit pas nécessairement ce qui se passe. Je vais remonter ici et on va imaginer, je vais directement effacer ça, faire C-y. Il a fait un peu trop. Alors, ben, voilà, ici en fait, c'est voilà, il faudrait je juste que j'édite un petit peu, voici un docstring correct. Il marque copié. Il faudrait voilà, il faudrait justement faire un prom pour lui dire ne me donne que le docstring sans répéter le nom de la fonction et cetera et cetera. Mais en gros, ouais, bah tout ça a l'air bon en fait hein. Ici, il m'avait rajouté du texte et des infos. Après, il explique ce qu'il a fait. Voilà. En gros, ça c'est bon. C'est juste que je ne sais pas pourquoi il a mal lié ceci.

Sacha: [Je me suis] trompée, peut-être. Tu as une fonction boost-gptel-generate-docstring dans ta configuration.

Fabrice: Ah ! Je ne l'ai pas encore testé, celle-là. Justement, avec la section de la fonction, je pense que ça ne marche pas toujours très bien. Il y a des choses qui ne marchent pas encore très bien. Il y a des choses qui sont en test un peu. Ça fait partie des tests. On peut réessayer. Je vais essayer de voir si elle marche. On voit ici qu'il a fait un docstring assez long, d'ailleurs. Un peu trop, à mon goût. En théorie, il doit détecter les bounds de la fonction et envoyer toute la fonction à gptel et avec un prompt. Ça a l'air de marcher assez bien. Sauf que de nouveau, il me fait plus, il me répète toute la fonction. Non, voilà, c'est ça, il me répète toute la fonction. Oui, jusque... Jusque l'interactif. Jusque là, oui, jusque l'interactif. Donc, c'est pas mal. Donc, oui, voilà, ça...

Prot: Oui, pas mal.

Fabrice: Voilà, hop. On peut voir, effectivement... Donc, ça, c'est nouveau, c'est... Enfin, c'est... Changez, changez, oui. Et donc l'interactive, je n'avais pas d'interactive apparemment. Si je devais l'avoir. Je ne sais pas si j'ai un interactive ou pas, donc je fais C-<f9>. Et on peut voir ce qu'il a modifié. On va le faire en vertical. Hop. Oui, si j'avais un interactif avant. Il considère, il n'est pas aligné au niveau du truc. Forcément, je devais avoir un interactif. Pour terminer ou pas avec tout ce qui est intelligence artificielle, j'ai un petit truc ici qui traîne sur l'écran. Ça fait une semaine que je l'essaye. J'ai déjà payé pour la version si il y avait trois jours d'essai et ça me paraît très bien.

31:03 Super-whisper

Fabrice: Donc c'est Super Whisper. Je vais le faire d'abord dans un Notepad par exemple pour vous montrer. Je l'ai mappé sur la touche Escape, ce qui n'est pas encore la meilleure touche. Je vais expliquer. Je voulais aussi pouvoir l'appeler à partir d'Emacs. J'aimerais bien que ce soit une touche assez facile, donc un peu une extrémité du clavier. La touche Escape, c'est une extrémité du clavier. Voilà, il n'y en a pas beaucoup, et puis il faut qu'elle ne soit pas utilisée dans Emacs, parce qu'il faut que je la fasse arriver dans Emacs. Et donc ici, quand je suis dans un endroit où on peut insérer du texte, donc ça pourrait être aussi en formulaire sur une page web, ici je suis dans Notepad, ça peut être dans Word, ça peut être n'importe où, je vais faire escape, et on voit à ce moment-là qu'il écoute.

Prot: On voit des ondulations là.

Sacha: Je ne le vois pas.

Prot: Je ne peux pas voir l'indication.

Sacha: Parce que tu vas partager seulement le fenêtre Emacs.

Fabrice: Je vais stopper la présentation et je vais faire un share.

Sacha: Je suis très curieuse à la reconnaissance vocale.

Fabrice: Voilà, screen 1. Voilà, ok.

Prot: Est-ce que vous le voyez ? Maintenant, oui. Oui, oui. Ok. On va bien danser dans le pad, ici.

Fabrice: Il y a une minute, j'ai fait Escape. Qui a lancé, donc, le Super Whisper. Et donc, on voit ici qu'il écoute, on voit des petites ondulations. Il y a une integration dans Notepad, aussi. Je le vois assez discret, d'ailleurs.

Sacha: Je pense que parce que tu as aussi, dans ton séance de réunion virtuelle, la reconnaissance vocale a du mal avec l'audio.

Fabrice: Je pense que ça va aller, mais en fait, c'est ça qui est bien. donc W-I-S-P-R. En fait, ça marchait bien, sauf qu'il y avait des conflits avec Emacs. Et que dans Emacs, de temps en temps, j'avais des caractères qui étaient générés de manière aléatoire donc j'ai compris que c'était Wispr parce qu'en j'étais désactivée, quand j'aie désinstallé Wispr, je n'avais plus de problème avec Emacs de config. Et Wispr, lui, il écoutait ce que tu disais et tu voyais afficher au fur et à mesure les mots.

Prot: Peut-être tu peux désactiver Whisper, parce que on ne peut pas écouter bien ce que tu dit. Peut-être maintenant...

Fabrice: Ok, excusez-moi, je n'avais pas compris, vous n'entendiez pas. Je n'avais pas compris, donc je vais réexpliquer. En fait, j'avais utilisé il y a quelques mois Wispr, W-I-S-P-R, et cette application-là, elle avait des conflits avec Emacs, donc j'avais des caractères qui généraient dans Emacs aléatoirement à un certain moment. Et quand je l'ai désinstallé, je n'ai plus eu ça, donc je ne sais pas pourquoi, mais il y avait des problèmes avec Emacs. Et l'application Wispr, elle écrivait les mots au fur et à mesure. SuperWhisper.com, je pense. Elle, elle écoute tout ce qu'on dit, donc je fais Escape pour l'activer, elle écoute tout ce que je dis, et puis quand je refais Escape, donc c'est un toggle, elle écrit tout d'un coup. Mais l'avantage, c'est qu'elle a du contexte. Et donc si je dis par exemple, je lui ai demandé comment ça va, en fait il va recopier, je lui ai demandé deux points, ouvrez les guillemets, comment ça va, point d'interrogation, fermez les guillemets. Parce qu'il interprète en fait, il reconstitue le paragraphe, le texte à la fin. Parfois, si je fais une faute et qu'il y a un autre orthographe, et puis je répète orthographe, il va comprendre que c'est le même mot. Il est assez malin, il remet le texte. de manière plus synthétique. Il met tous les mots, mais à la fin, au niveau typographie, etc., il met deux, trois petits points. Il met parfois des guillemets. Il comprend bien les questions. Je ne sais pas pourquoi ici, j'ai un caractère un peu spécial. Il y a quand même de temps en temps des fautes aussi. Tout à l'heure, je parlais de aléatoire. Il a marqué aléatoire. Oui, j'espère que j'ai cité tout à l'heure Donc voilà, il fallait marquer W, I, S, P, R. Et puis une fois, il a marqué c'est A, I, R. Bon, c'est pas parfait, mais en fait, ça marche et c'est beaucoup plus rapide quand même. Et j'écris une petite fonction qui marche dans WSL, en tout cas pour ma config, de telle façon que dans... Donc si je vais dans un buffer scratch, par exemple. Que dans Emacs, ça fait la même chose aussi quand je fais escape. Parfois, j'ai des problèmes de clipboard, donc le kill ring. Quand je fais le deuxième escape, parfois il me remet un vieux truc qui n'est pas ce que je viens de dire, donc il y a encore des choses à comprendre. Je vais essayer quand même juste une phrase, je fais ESC Je sais pas... Ah, voilà. Il se lance. Mais... Il se lance. Est-ce que cela marche ? Ah, tu vois, j'ai un problème. J'ai dit est-ce que cela marche ? Il vient de remettre système, il faut que je comprenne. Il y a des petites configurations dans Super Whisper à faire pour le presse-papier. C'est un peu plus compliqué avec le presse-papier Windows et le presse-papier Emacs. J'ai encore des petits réglages à faire, mais quand ça marchera bien, ce sera fantastique de pouvoir directement aussi dans Emacs utiliser ça. Il y a peut-être d'autres solutions d'ailleurs. Ici, il marche assez bien et le fait qu'il insère le truc une fois qu'on a fini de parler, ça fait quand même des phrases qui sont plus correctes. On hésite parfois, donc ça reconstitue un truc de meilleure qualité.

Sacha: En fait, j'utilise aussi la reconnaissance vocale sur Emacs et j'utilise le substitute de Prot pour corriger facilement les erreurs et sauver les remplaçantes.

Fabrice: Et ça marche bien, donc ?

Sacha: Oui, oui, oui, ça marche.

Fabrice: Tu es contente ?

Sacha: Oui, je suis très contente. Je l'utilise pour les sous-titres, dicter mon texte. Ça marche bien.

Fabrice: Mais il faudrait que je regarde ta configuration, alors. Pour m'en inspirer, pour faire des tests aussi.

Sacha: Si tu rediriges l'output de la reconnaissance vocale à Emacs, tu pourrais traiter avec Emacs Lisp pour faire des remplaçants.

Prot: process sentinel, il y a les processus qui...

Sacha: Oui, j'ai lancé un serveur de [Speaches] pour offrir un service de la reconnaissance vocale qui utilise aussi Whisper, le mode Whisper.

Fabrice: Oui, il y a beaucoup de belles choses qui vont arriver, c'est sûr. En fait, le problème maintenant, c'est que je n'aime plus quand je suis ici au bureau parce que j'ai des collègues, on est en open space, donc je ne peux pas utiliser ça. C'est ça le problème. Quand je suis à la maison, je peux parler, mais quand on est au bureau, on ne peut pas.

Sacha: Et dans ce temps, j'utilise avec les enregistrements que j'ai fait en marche, à pied, et d'autres environnements qui sont

Prot: Et quand il y a les autres gens, il peut comprendre seulement toi ou il écrit toutes les phrases de les autres gens aussi?

Fabrice: Je ne sais pas, en fait. Je ne l'utilise pas avec d'autres personnes. Et si je l'utilise, je vais utiliser le casque. Et je pense que le casque est assez... Le micro est assez directif. En gros, il n'y a quand même que moi qu'on entend, je pense. Il faudrait vraiment qu'il y ait des bruits assez forts pour que ça puisse passer aussi.

41:21 Dotfiler

Sacha: Je veux aussi consacrer du temps pour ton outil dotfiler pour gérer les fichiers professionnels et les fichiers personnels. Comment tu l'utilises-tu?

Fabrice: Oui, alors ça, ce n'est pas de l'Emacs. Par contre, c'est très utile, y compris pour Emacs. Et donc, j'ai écrit ici une petite note là-dessus. Je vais ouvrir l'HTML, par exemple. Donc, là, ici, vous voyez bien l'HTML, oui, OK. Donc, en fait, j'utilise le programme dotfiler, donc c'est son nom, c'est le nom du programme. Il y a un outil qui s'appelait Stow dans le temps qui fait, je pense, à peu près la même chose. Je ne sais pas très bien les différences entre les deux, d'ailleurs. Mais donc l'idée en fait c'est, j'ai des repositories, donc j'ai par exemple, et ça me sert notamment pour Org, donc justement j'ai un repository avec des fichiers Org personnels, j'ai un repository avec des fichiers Org professionnels, et en fait, dotfiler, en fait c'est plus clair ici dans mon explication, donc j'ai un repo A, Et sous le repo A, j'ai un répertoire Org, j'ai un répertoire bin, j'ai un répertoire Lisp. Repo B, je ne suis pas obligé d'avoir les trois. Donc, j'ai une autre structure, mais j'ai par exemple Org, bin, Lisp aussi, examples, peu importe. Et donc, quand on utilise dotfiler, Il va créer, donc tout ça est mis par défaut dans .dotfiles sous le home directory. C'est là que tous les repos se trouvent, ceux qui sont gérés par dotfiler. Et donc j'ai fait des clones, donc au début je vais faire, donc ici si je vais dans mes .dotfiles, c'est tous les repos que j'ai. Il y a des repos privés et des repos publics. Et je vais faire un dot add d'une URL Git. Par exemple, si je veux cloner un nouveau repository. Et donc, il va mettre sous .dotfiles, il va mettre repo c, par exemple. Donc, si j'ai fait un dot add. Donc, la commande, c'est dot update, dot add. Donc je vais faire un add d'un repo C. Donc le repo va être cloné localement sous dot files, un repo C. Et puis je vais faire un dot update. Donc quand l'update, imaginons au départ que j'avais que le repo A, et puis je fais ça, donc j'ajoute un repo B. Comment on va faire ? Donc au départ, quand je n'ai que le repo A, ce qu'il fait, c'est que tout ce qu'il trouve en dessous de chaque repo, Il les met directement via des symlinks sous le home directory. Donc le répertoire Org va se retrouver via un symlink sous le home directory. Le répertoire bin va se retrouver sous le home directory. Et pareil pour l'isp. Donc si je n'ai qu'un repo au début d'ailleurs et que je fais un update, dot update, donc il va créer des symlinks. Un dot update crée ou détruit des symlinks. Ça fait juste ça, en fonction de ce qui a évolué. Et donc la première fois, il va juste faire un symlink du répertoire tilde slash Org vers tilde.dotfiles repo a Org. Et il va faire la même chose avec bin, la même chose avec Lisp. Quand j'ai le deuxième repo, le repo b, que j'ai colonné, j'ai fait... dot add de ce repo-là. Puis je fais un update. À ce moment-là, évidemment, il y a deux répertoires Org qui doivent être symlinkés. Ils doivent avoir des liens symboliques dans le tilde. Donc, il ne peut plus faire ça au niveau du répertoire. Et donc, ce qu'il fait, c'est qu'il supprime le symlink qu'il avait sur le répertoire Org et il scanne tous les fichiers qui sont là-dedans et il va faire un symlink individuel, fichier par fichier. Donc si j'ai 10 fichiers ici, donc fichier 1, fichier 2, fichier 3, il va me faire des symlinks dans tilde Org fichier 1 vers le fichier 1 qui se trouve physiquement là. Et ainsi de suite pour tous les fichiers. Il va faire la même chose avec tous les fichiers qu'il va trouver ici. Et donc tous les fichiers qui se trouvaient dans repo A Org et dans repo B Org, via des symlinks, se retrouvent en fait dans tilde Org. Et pareil ici dans mon exemple avec bin et avec Lisp. Donc si je vais voir ici, si je vais dans mon répertoire bin, vous voyez, donc j'ai, je vais faire un peu plus petit, j'en ai des, enfin on ne va pas, ici, voilà, celui-ci, il vient de Archibus Reports, de ce repo-là. Celui-ci, il vient de Archibus, OIL, MCD, Datamodel, Diagram. Donc de ce repo-là. Ici, il vient de gitboost. Donc j'avais dans ces différents répertoires, j'avais un sous-répertoire bin. Donc dans ces différents repos, j'avais un sous-répertoire bin. Et tous ces fichiers qui se trouvent directement dans bin, sous les repos, se retrouvent in fine via des symlinks dans mon bin à moi, dans mon directory. Ce qui veut dire que dans ma configuration de mon shell, j'ai juste à dire path égale tilde slash bin de point $path. Et donc il va connaître tous mes petits scripts qui sont dans les bins d'un coup. Pour Org, Ça veut dire que, org-agenda-files, je fais pointer simplement vers tout ce qui se trouve dans ~/org. Et vu que dans ~/org, je retrouve tous les fichiers Org du repo A, du repo B, et ainsi de suite, j’ai une seule variable, un seul setq, et j'ai tous mes fichiers qui se retrouvent directement dedans. Et donc, quelle que soit la machine, si j'ai différentes machines, une machine pro, une machine perso. Sur ma machine perso, j'aurai mon repo privé et mon repo pro. Sur la machine professionnelle, j'aurai que le repo professionnel. En fait, mon fichier Emacs ne change pas. C'est le même fichier. Je dis juste scan tous les fichiers. Mon Emacs, en se lançant, il fait toujours un scan de tout ce qu'il y a. Donc, il va, org-agenda-files va regarder tout ce qu’il y a dans ~/org et tout ça est rajouté à mon org-agenda-files. Et donc, il va, à chaque lancement d'Emacs, il va voir tous les fichiers qui sont dans la configuration. Mais c'est le même fichier Emacs. Pour ça, il ne change pas. C'est le même fichier Emacs d'un côté ou de l'autre. Simplement d'aller voir tous les fichiers qui sont en ~/org. Et en fait, c'est des symlinks vers les fichiers qui sont dans un repo privé, un repo public, etc. Pareil avec le bin. Donc ça vient de 36 repositories. Pareil avec le Lisp. Justement, et ça me force un peu aussi à faire une structure un peu plus standard. Donc ici, si je vais dans… dans ~/.dotfiles gptel-commit-message. J'ai un répertoire. J'ai un répertoire Lisp. Et c'est dedans, donc c'est des lisps. J'ai ici le petit package, le petit fichier avec la fonction pour écrire le message de commit. Si je vais dans... Si je remonte... Je vais dans Emacs Leuven Lisp. Là, j'ai tous mes fichiers de configuration. Et tout ça se retrouve en fait dans ~/lisp. Grâce à symlink. Donc si je vais ici dans ~/lisp. Lisp. Voilà. Dotfiler, Emacs gptel, mon fichier de configuration, le nouveau avec gptel. Qu’il voit dans ~/lisp, en fait, physiquement, il est dans /home/fni/.dotfiles/emacs-leuven/lisp, il est physiquement là. Celui avec le gptel-commit-message, Physiquement, il est dans un autre repo, il est dans le repo gptel-commit-message, sous le répertoire Lisp. Donc en fait, en dessous de chaque repository, tous les fichiers qui sont là vont directement tel quel dans le home et tous les répertoires qui sont là vont directement tel quel dans le home. Donc en fait, il faut que je vois vraiment tous mes repos comme des puzzles, pièces de puzzle. Et tous les fichiers ou tous les répertoires à la racine de chaque repo vont se retrouver via des symlinks dans mon ~ Directement en tant que fichier. Par exemple, j’ai toujours un README.org dans chacun de mes repos. Là, c'est une exception parce que on peut dire celui-là n’essaie pas parce que j’ai README.org dans chacun de mes repos. Donc, il ne saurait pas créer des symlinks d'un README.org dans ~ vers tous les repositories. Celui-là, c'est une exception. On me dit de ne pas le symlinker. Et tous les autres fichiers se retrouvent directement dans mon ~ avec des symlinks. Et tous les répertoires, pareil. S'il y a des répertoires qui sont communs, donc j'ai chaque fois plusieurs fois des répertoires bin dans les repositories, donc à ce moment-là, il fait des symlinks sur les fichiers qui sont dans les répertoires bin. Et donc pour le Lisp, pareil, dans Emacs, j'ai juste dit de scanner tous les fichiers que j'ai dans ~ Lisp et de faire un load library. Donc je fais un dolist avec un find, etc., Et donc en une seule commande, qui reste la même sur la machine professionnelle et la machine privée, j'ai la même commande qui fait scanner tout contenu mon ~/lisp et donc dynamiquement, sur une machine, j'aurai potentiellement plus de fichiers Lisp ou pas, mais c'est la même commande et je n'ai pas de config particulier à faire. C'est juste en fonction des repositories que je vais cloner sur telle ou telle machine que j'aurai plus ou moins de fichiers disponibles et tout ça est caché.

Sacha: Il y a une question dans le chat. Ces liens sont gérés dans Git ?

Fabrice: Non, non. Imaginons... Je peux le montrer en live si tu veux. Je vais aller dans Emacs... Donc, cd. Donc dans .dotfiles, dans emacs-chat-sacha-chua. Donc dans ce répertoire-là, j'ai une image, j'ai quelques fichiers, d'accord ? Et je vais rajouter un fichier. Fichier des mots, .Org. Voilà, donc ici, maintenant, j'ai un fichier de plus dans ce repository. Je pourrais l'ajouter dans Git, etc. Mais je n'ai même pas besoin de faire ça, en fait. Pour les symlinks, je peux... Donc, pour l'instant, ce fichier Org, imaginons que j'ai des tâches dedans. Pour l'instant, il sera... Dans ce cas-là, d'ailleurs, je devrais... Enfin, je vais... Je vais créer un répertoire Org et je vais le déplacer, fichier des mots, temps Org.

Prot: Voilà.

Fabrice: Donc, ici, dans... Dans ce répertoire-là, j'ai ce fichier de démo et on peut imaginer que j'ai des tâches. J'aimerais bien voir ce fichier-là dans mon Org agenda files. Et pour l'instant, il n'est pas visible à partir de mon tilde. Il est physiquement là, mais il n'y a pas de symlink dessus. Pour que le symlink soit créé, je vais juste faire .dot files bin. Dans bin, il y a la commande dot. On peut voir son aide. Il y a dot update, dot status, dot add. Add, c'est pour ajouter un nouveau repository. Et update, c'est pour scanner le contenu des repositories et mettre à jour les liens. Ici, je vais faire dot update. Et alors, je vais faire moins skip pool parce que sinon, si on ne fait pas moins skip pool, il va en même temps pooler les repos. Donc, on va essayer que tout soit à jour. Donc ici, je vais faire le --skip-pull. Et on va voir qu'il va créer un lien, en fait, Vers ce fichier-là, voilà. Donc ici, il a créé, donc dans mon Org, donc dans mon tilde en fait, ça c'est mon tilde, donc dans ~/org, fichier-demo.Org, il l’a lié vers la position physique du chemin. Donc ça veut dire que maintenant, je peux dans Emacs, ici, donc si je fais C-x C-f, dans Org, donc c'est des fichiers, voilà. Il est directement là. Donc il est vide. Voilà, et donc ici, fichiers des mots, ça pointe dans mon type d'org, il pointe vers sa position physique. Donc les symlinks, c'est lui qui gère ça, c'est lui qui met à jour en fonction des fichiers ajoutés ou des fichiers supprimés. Il garde, il compare, voilà. Il y a tout ça dans les repos. Il y a tous ces symlinks qui existent à partir de ~ et il met à jour. Il rajoute ou il retire en fonction.

Prot: C'est Git qui garde toujours le fichier original.

Fabrice: Et donc dans Git, tu as les fichiers physiques. Git ne voit pas de symlinks, etc. Dans Git, tu as les fichiers physiques. Ici, si je vais... Imaginons que je mette Hello. Dans Git, je vais faire vc-dir, C-<f9>. C-<f9>. Qu'est-ce qui se passe ? Peut-être parce qu'il n'est pas... Ah oui, c'est parce que justement, je suis sur le Simlink, là, il faut que j'aille dans dotfiles. Donc c'était Emacs, chat, Sacha, voilà. Donc là, je fais C-<f9>. Et donc je vois que j'ai un nouveau fichier à ajouter. Et là, ce n'est pas un Simlink, ce sera le fichier physique. Donc au niveau Git, c'est les fichiers physiques dans des repos. Simplement, conventionnellement, tous mes repos vont se retrouver en dessous de ~/.dotfiles. C'est là qu'ils scannent tout ce qu'il y a en dessous de ~/.dotfiles. C'est là qu'ils scannent tous les repositories et qu'ils regardent le contenu de chaque repository. Si j'avais ton site web ou ta configuration, tu rajoutes un fichier, je vais faire dot update. Il va les puller Et puis, si tu as rajouté des fichiers, si tu as fait des modifications, il ne se passe rien. Au niveau des symlinks, il ne se passe rien. Puisque simplement, j'aurai la nouvelle version à jour dans mon clone, dans le sandbox. Mais s'il y a des nouveaux fichiers ou des fichiers qui ont été enlevés, il va rajouter ou effacer des symlinks. Maintenant, si j'efface ce fichier-là, donc un delete, je vais l’effacer de .dotfiles. Emacs. Sacha Chua, le chat. Donc je vais effacer le répertoire ou le fichier ici. Donc hash, yes. Voilà, le fichier n'existe plus dans mon repository. Donc je vais relancer dot update avec le --skip-pull. Il va montrer rm, voilà. Donc il vient d'effacer le symlink vers tilt Org. C'est génial parce qu'en fait, dans le temps, chaque fois que j'ai rajouté un repository, il fallait voir où se trouvent les bins, puis faire add dans le path de tous les bins. Ici, non, tout se retrouve, pour autant que ce soit directement dans le repository sous ./bin, ça va se retrouver après le symlink dans ~/bin. Et donc j'ai qu'à rajouter une fois le fait que Tiltbin est dans mon path. Donc ça pourrait marcher pour les manpaths, pour les fichiers d'info, enfin pour les lists, pour l'org, donc c'est extrêmement pratique pour ça.

59:12 Enseignement d'Emacs

Sacha: Pendant les dernières minutes, j'aimerais connaître ta point de vue sur l'enseignement d'Emacs. Comment tu structures ton cours pour ne pas noyer les gens sur les fonctionnalités?

Fabrice: Mon cours, je le vois plus comme étant un cours pour des gens qui connaissent déjà Emacs, donc c'est plutôt pour leur montrer des choses qu'ils ne connaissent peut-être pas parce qu'ils n'ont pas lu des dizaines ou des centaines ou des milliers de fichiers de configuration comme moi. Donc voilà, c'est des gens qui a priori connaissent déjà Emacs, donc ils savent faire enregistrer, ouvrir un fichier, Et simplement, je vais leur montrer des choses en plus, comme Helm. Moi, j'utilise Helm pour tout ce qui est la gestion des fichiers et des buffers. Je vais leur montrer aussi des fonctionnalités parfois qui sont peu utilisées ou parfois un peu cachées, donc peu utilisées comme les macros ou le multiple curseur qu'on ne connaît pas nécessairement et qui n'est pas standard, mais qui est facile à installer. ou l'édition rectangle, qui est standard, mais que pendant longtemps, je n'avais pas connu. Et donc voilà, je vais leur montrer des choses comme ça. A priori, c'est des gens qui connaissent déjà, donc ça va un peu plus vite. Maintenant, j'avais quand même une fois dans un de mes cours, Martin, qui ne me connaissait pas du tout Emacs. Son père connaissait Emacs, il l'avait amené de force presque. Si on écoute et qu'on suit, je repassais aussi. En fait, je réexpliquais tout depuis le début. J’avais des slides sur C-a, C-e, M-n, M-p, C-s, C-r. Je réexpliquais tout plus ou moins rapidement en fonction du niveau des gens, s'ils connaissent déjà. Je vais aller assez vite. En fait, c'est plus facile si les gens connaissent déjà pour aller plus vite. Sinon, il faudrait plus que deux jours de formation. En fait, j'ai quand même fait quatre jours de formation Emacs. Donc, c'est deux jours sur ce que j'appelle les fondamentaux. Tout ce qui est édition rectangle, macro, multiple curseur, donc avec des choses en plus. Et puis, deux jours plus orientés sur Org. Il y a tellement de choses dans Org. Il y a les Org agendas, le time blocking que j'utilise pour tout, pour mon temps, pour faire des factures derrière. Et puis il y a évidemment Babel, Tangling. Donc tout mon fichier de configuration Emacs, il est dans un fichier qui est tanglé. Un fichier de doc, la code littérée de programming de Knuth, où je décris pour un humain. Et puis j'ai des petits bouts de code qui vont se retrouver. dans des fichiers de code où il faut pour être exécuté. Mais l'objectif, c'est de faire un document qui soit lisible par un humain d'abord. Et donc, même en quatre jours, ça passe vite. Il y a trop à montrer.

Sacha: C'est aussi le point lié à des incertitudes économiques. Quels sont les avantages du coaching ou de l'enseignement humain par rapport à l'IA?

Fabrice: Donc, répète les avantages du coaching par rapport à l'IA.

Sacha: Parce qu'il est facile de poser des questions à l'IA gratuitement, mais je pense que tes formations, ton cours ont des avantages.

Fabrice: Absolument. En fait, dans tous les domaines, c'est comme ça. C'est que l'IA, si j'ai une question précise ou une question claire, il va pouvoir me répondre très facilement et de manière avec plein de détails, donc extrêmement fantastique. Mais l'IA, le problème, c'est qu'on ne sait pas ce qu'on ne sait pas. Et donc, dans la formation, je me doute que plein de gens, même qui sont des utilisateurs d'Emacs depuis 20 ans, Plein de gens ne connaissent pas l'édition rectangle, utilisent peu ou pas les macros, ne connaissent pas Wdired, le Dired mode éditable, par exemple. Si je mets des photos sur mon disque, je veux mettre la date de la photo dans le nom du fichier, je vais passer en mode direct éditable et je vais faire une petite macro qui va me copier la date. Et donc voilà, en fait, ChatGPT, si je dis, ChatGPT, explique-moi comment marche l'édition rectangle, il va l'expliquer. En fait, il ne va jamais répondre à dire, tiens, il y a l'édition rectangle que tu ne connais pas. C'est ça le problème. C'est justement de pouvoir juger ce que les gens savent et de ce qu'on pourrait leur apporter en plus. Et là, l'IA va être moins bonne parce qu'elle n'a pas des propositions comme ça de choses que tu ne connais pas. C'est de ce point de vue-là que c'est beaucoup plus pratique en encadrement personnel ou par groupe où on peut expliquer des choses qu'on pense que les gens ne savent pas et donc ils ne vont jamais poser la question à l'IA là-dessus, par définition.

Sacha: Merci beaucoup à vous deux, à tout le monde.

Fabrice: Merci beaucoup pour cette organisation, cette deuxième séance. Peut-être à une prochaine fois. Merci beaucoup en tout cas pour l'organisation, Sacha. Au revoir.

Prot: À la prochaine, au revoir.

Fabrice: Salut, je vais vite dans l'autre.

Chat

  • protesilaos: ​à bientôt!
  • julienlambe78: ​Bonjour de Belgique 🙂 Sacha, le volume de votre micro est un peu bas. Nous n'entendons pas bien vos questions. Pensez-vous pouvoir augmenter le volume un petit peu?
  • SaMusz73:​ ​synlinks `ln -s` et hardlinks `ln ` vers des fichiers sont gérés dans git ? J'ai cru comprendre que git gére assez mal ces symlinks
  • SaMusz73: Merci :)
  • JeromeVanLunter: ​​MERCI
  • SaMusz73: Merci à vous tous !
View Org source for this post

You can e-mail me at sacha@sachachua.com.

-1:-- Emacs Chat 30: Fabrice Niessen (en français, partie 2) (Post Sacha Chua)--L0--C0--2026-09-01T15:58:31.000Z

James Cherti: The Emacs security settings that might silently compromise your system

Emacs ships with default settings intended for backward compatibility and convenience over strict security. The following settings enforce TLS certificate verification, reduce unintended ffap network lookups, restrict the evaluation of potentially unsafe local file, and directory variables, automated package review system, and protect authentication credentials stored by Emacs.

Network and communication security

By default, Emacs prompts the user interactively if a connection appears untrustworthy. You can additionally require certificate validation to fail at the TLS library level, causing invalid certificates to result in a connection error.

Requiring certificate validation at the TLS layer reduces the possibility of accidentally accepting an invalid certificate in response to an interactive warning.

Define these variables in your early-init.el to ensure the settings are established before startup code initiates network connections:

(setq gnutls-verify-error t)
(setq tls-checktrust t)
(setq gnutls-min-prime-bits 3072)
  • gnutls-verify-error: Controls GnuTLS certificate verification. Setting this to t makes any certificate validation failure fatal.
  • tls-checktrust: Controls external TLS binaries. Setting this to t ensures certificate validation is enforced if Emacs falls back to using external tools.
  • gnutls-min-prime-bits: Defines the minimum acceptable size for Diffie-Hellman key exchange primes. Setting this to 3072 rejects handshakes using primes smaller than 3072 bits.

The benefit of strictly enforcing connection security at the TLS layer is preventing the accidental acceptance of invalid certificates or weak cryptography.

Find file at point network requests

The ffap (Find File At Point) package scans text around point to identify file paths, URLs, and hostnames. This triggers when you invoke an ffap command, most commonly find-file-at-point, which replaces standard C-x C-f if you have (ffap-bindings) enabled, while your cursor is resting on or near a string of text that resembles a domain name.

Setting ffap-machine-p-known to 'reject prevents FFAP from probing hostnames with known domains:

(setq ffap-machine-p-known 'reject)

The tradeoff is that ffap will no longer automatically recognize hostnames as remote connection targets, meaning you must enter remote host paths explicitly.

Encrypting auth sources

Emacs' auth-source uses files such as ~/.authinfo and ~/.netrc to store authentication credentials that other Emacs packages can retrieve when connecting to remote services. For example:

  • Tramp: Passwords for remote server access.
  • Gnus and Message: Credentials for mail retrieval (IMAP, POP3) and transmission (SMTP).

An auth-source entry can contain information such as a hostname, username, password, port, or protocol. Because these files may contain sensitive credentials, storing them as plain text exposes them to any process or user that can read the file.

To enforce the exclusive use of an encrypted file and prevent accidental plain-text credential storage, redefine the authentication sources list:

(setq auth-sources '("~/.authinfo.gpg"))

Additional security configurations:

  • To encrypt the file using specific GPG public keys, define the recipients using auth-source-gpg-encrypt-to:
(setq auth-source-gpg-encrypt-to '("your.email@example.com"))
  • Emacs can cache passwords to minimize prompt interruptions. The cache expiration can be configured using auth-source-cache-expiry:
(setq auth-source-cache-expiry 3600)

Clear auth-source cache when the user is idle

The auth-source library uses password cache to store authentication data in memory. By default, auth-source-do-cache is enabled (t) and auth-source-cache-expiry is set to 7200 seconds (2 hours). You can restrict this credential exposure using idle timers, targeted cache clearing, or by disabling the cache entirely.

Use an idle timer to purge credentials after a period of inactivity instead of waiting for the absolute cache expiry limit:

(defun my-security-clear-caches ()
  "Clear all cached authentication data managed by auth-source."
  (when (fboundp 'auth-source-forget-all-cached)
    (auth-source-forget-all-cached)))

(run-with-idle-timer 900 t #'my-security-clear-caches)

The benefit is that it limits memory exposure to 15 minutes of idle time. The tradeoff is that background operations requiring authentication may fail or block while awaiting a passphrase when returning to the editor.

Symbol shorthand code execution

Emacs 28.1 added a feature called symbol shorthands (read-symbol-shorthands). A vulnerability exists in Emacs versions 28.1 through 31 pretest where this feature can be abused to trigger arbitrary code execution simply by opening a specially crafted file. The malicious execution occurs immediately, even before the file contents are displayed.

The exploit relies on Emacs evaluating symbol shorthands during unsafe intern calls in vc-find-backend-function and c-compose-keywords-list. Emacs 31.1 mitigates the vulnerability. Earlier versions, including affected Emacs 31 pretest versions, remain vulnerable unless the mitigation is applied manually.

For users on Emacs 31 pretest or older, you can implement a mitigation by defining an advice function that locally disables symbol shorthands and applying it around the vulnerable operations.

(defun my-suppress-shorthands (orig &rest args)
  "Call ORIG function with ARGS while binding `read-symbol-shorthands' to nil.
ORIG is the original function being advised.
ARGS is the list of arguments passed to the original function.
This acts as advice to prevent arbitrary code execution via symbol shorthands
during unsafe operations like interning symbols on file open."
  (let (read-symbol-shorthands)
    (apply orig args)))

;; A workaround patch (Commit 8466eb44) was applied to the emacs-31 release
;; branch on August 5, 2026. Early pretest versions of Emacs 31 do not
;; include this mitigation.
(when (< emacs-major-version 32)
  (advice-add 'vc-find-backend-function :around #'my-suppress-shorthands)

  (with-eval-after-load 'cc-fonts
    (advice-add 'c-compose-keywords-list :around #'my-suppress-shorthands)))

This protects the editor from silent code execution attacks embedded in untrusted source files without requiring an upgrade to an unreleased Emacs version. The tradeoff is that it modifies core function behavior via advice, though the impact on version control and C/C++ fontification performance is negligible.

Package Management

Emacs 31.1 added package-review-policy, an automated package review system, allowing inspection of upstream code changes before installation:

(setq package-review-policy t)

Setting package-review-policy to t causes Emacs to require a review of packages before installation or upgrade. The package is unpacked into a temporary review directory, where the source can be compared with the previous installation (diff), and reviewed. The package is installed only if the review succeeds.

The main downside is that updates can no longer run in the background. Having to review diffs by hand gets tedious when upgrading multiple packages at once.

Securing .dir-locals.el and local variables

Emacs automatically applies project-specific configurations through file-local and directory-local (.dir-locals.el) variables when opening a file or directory. While this feature ensures consistent settings across environments, it can cause security risks and persistent prompt fatigue when editing source code. Malicious .dir-locals.el files or file-local variables containing eval forms can execute arbitrary Lisp code if Emacs is configured to evaluate them, or if the user explicitly approves the relevant prompt. This article outlines configurations for securing file-local and .dir-locals.el variables:
Emacs .dir-locals.el and Local Variables - Securing and Reducing Prompts.

-1:-- The Emacs security settings that might silently compromise your system (Post James Cherti)--L0--C0--2026-09-01T14:53:19.000Z

Irreal: Wordcraft

I do a lot of writing, almost all of it in Emacs so I’m always happy to see how other people use our editor of choice for writing. The latest example I’ve found is from JTR over at The Art Of Not Asking Why. His post considers some simple expansion functions in Emacs.

He starts off with dictionaries. As many of you know, I’ve been a Webster 1913 dictionary user ever since James Somers explained why I (and you) should be. I have gone through many iterations of integrating it into Emacs but have converged on the same solution that JTR uses: the built in dictionary-search function built into Emacs that we both have configured to use the online collection dict.org, although I still have a local copy from my previous iterations.

The dict.org collection is nice because it contains several dictionaries besides Webster’s, and dictionary-search will return entries from all of them that have the word you’re looking for. The best part is that it’s trivial to set up You don’t have to download dictionaries or packages. You simply set a variable saying you want to use dict.org and that’s it. There are some nuances with that so be sure to read JTR’s post or the documentation.

JTR’s post is worth reading just for his dictionary advice but there’s more.

Next he briefly discusses Hippie Expand, a way of completing words based on a list of functions that you can specify. I’ve been using it for a long time, but his list of completion functions was more complete than mine so I stole it.

Finally, he considers ispell. If you’ve been following along, you’ll know that I recently started using Jinx instead of ispell but JTR showed me a couple of things I didn’t know about. I’ve always used ispell with flyspell so I never learned some of its extra tricks. One of those is a sort of fuzzy search for a word you’re not sure how to spell. That happens to me a lot so I was glad to find out about ispell’s ability to handle it.

The other thing you can do with ispell is to use it as a quick way of doing a replace. Take a look at JTR’s post for more on both those functions.

JTR’s post is from last week so Sacha will doubtless cover it before you see this but I had a lot to say about it so I decided to write about it anyway. It’s definitely worth a careful reading.

-1:-- Wordcraft (Post Irreal)--L0--C0--2026-09-01T14:29:01.000Z

Jeremy Friesen: How I’m Feeling Emacs Command

In late April of this year, I’ve began experiencing a heightened anxiety. I started therapy then later taking anti-anxiety medication. I worked to change some of my behaviors. And started writing even more personal journal entries.

I found a list of feelings and responses that resonated with me:

  • Angry: Lift weights
  • Stressed: Go for a walk
  • Procrastinating: Set a 10-minute timer
  • Sad: Get sunlight
  • Can’t focus: Clean your workspace
  • Negative thoughts: Write 3 gratitudes
  • Stuck: Change your environment
  • Financial stress: Build an emergency fund
  • Low energy: Fix your sleep
  • Overthinking: Journal it out
  • Lonely: Call someone
  • No motivation: Start with 2 minutes
  • Anxiety: Slow your breathing
  • Brain fog: Drink water and move
  • Low confidence: Keep small promises
  • Lost: Define one clear goal

Twice I’ve looked to that list and found the emotions I was feeling and took action from the responses. The second time I found myself digging back through my journal to find the entry and pick how I was feeling.

And I realized, that I could encode this as an Emacs 📖 function and when I found myself needing to interrogate my “feels” I could type M-x how-im-feeling and get a set of responses I could then use to take action.

Here’s the how-im-feeling command and supporting variable:

(defvar how-im-feeling-responses
  '(("Angry" . "Lift weights")
    ("Stressed" . "Go for a walk")
    ("Procrastinating" . "Set a 10-minute timer")
    ("Sad" . "Get sunlight")
    ("Can't focus" . "Clean your workspace")
    ("Negative thoughts" . "Write 3 gratitudes")
    ("Stuck" . "Change your environment")
    ("Financial stress" . "Build an emergency fund")
    ("Low energy" . "Fix your sleep")
    ("Overthinking" . "Journal it out")
    ("Lonely" . "Call someone")
    ("No motivation" . "Start with 2 minutes")
    ("Anxiety" . "Slow your breathing")
    ("Brain fog" . "Drink water and move")
    ("Low confidence" . "Keep small promises")
    ("Lost" . "Define one clear goal"))
  "How I'm feeling and a simple response to move with that feeling")

(defun how-im-feeling ()
  "Prompt for how I'm feeling and respond with what to do."
  (interactive)
  (let ((feels
         (completing-read-multiple "I'm Feeling: "
                                   how-im-feeling-responses
                                   #'completing-read-omit-p
                                   t))
        (concatter
         (lambda (feel)
           (format "- *%s:* %s"
                   feel
                   (alist-get feel
                              how-im-feeling-responses
                              nil nil #'string=)))))
    (insert
     (format "I'm feeling:\n\n%s\n"
             (mapconcat concatter feels "\n")))))

(defun completing-read-omit-p (thusfar)
  "Omit completions THUSFAR given."
  (let ((input
         (butlast (split-string
                   (minibuffer-contents-no-properties)
                   crm-separator)))
        (test
         (car (last (split-string thusfar crm-separator)))))
    (not (member test input))))

Wrapping Up

Now, when I’m feeling the feels, I have another strategy for approaching and addressing those feels. The prompts don’t provide direct answers but instead nudge me towards moving with and through those feels. All in service of helping me cope with anxiety.

-1:-- How I’m Feeling Emacs Command (Post Jeremy Friesen)--L0--C0--2026-09-01T10:35:14.000Z

Raymond Zeitler: Games: The Emacs Carnival Post For September 2026

I don't play games within Emacs. Instead I enjoy learning about the many functions and modes it has. I even derived a Tip of the Day that displays help on a randomly-chosen function. I invoke it at startup; there's a non-zero probability that I'll spend 15 minutes or so exploring the function it provides.

I get the impression that I'm not the only one who enjoys Emacs this way. When I read others' postings, I see references to "customize," "configure," "init file," "rabbit hole." I'm pretty sure I also came across a few posts that contained this: "my ex-spouse made me chose between him/her and Emacs." Often, reading these posts is enjoyable in itself.

I enjoy writing and blogging, too. I started my first journal when I was 13 years old. My answer to "What book would you choose if stranded on an uninhabited island?1" would be "a blank notebook that comes with a pencil." I also enjoy interacting with the Emacs community. It's as if this "Ray on Emacs" blog provides entertainment squared. This can be expressed mathematically as E2, where E is entertainment and 2 is two.

While researching this topic, I was surprised at how many games we can choose from, even in plain vanilla. Naive folks might scoff at programmers who write games for Emacs. But they don't understand that games demonstrate Emacs' capabilities. I suspect the underlying motivation is that writing games for Emacs is fun. It's a challenge that leads to accomplishment.2

I'm looking forward to reading submissions to Emacs Carnival September 2026 and trying some games. participate is Humor not to necessary.3

1 aka "Desert Island" see Uninhabited island on Wikipedia

2 Folks in the MS-DOS USENETs would joke about writing Batch Files that could do calculus. At least I think it was a joke.

3 "Humor is not necessary to participate" (as evidenced by this post).

-1:-- Games: The Emacs Carnival Post For September 2026 (Post Raymond Zeitler)--L0--C0--2026-09-01T04:00:00.000Z

Sacha Chua: 2026-08-31 Emacs news

Lexical binding: If you've recently updated to Emacs 31, you might have gotten warnings about lexical-binding in your packages or personal Emacs Lisp configuration. You can check your packages to see if the package authors have updated them. For your personal Emacs Lisp configuration, consider adding

;;; -*- lexical-binding: t -*-

to the top line. If that makes your code stop working correctly when you restart Emacs, set it to nil instead with:

;;; -*- lexical-binding: nil -*-

to get it to work for now. Additional resources:

Emacs Carnival: Check out the entries for August in "The Search for Knowledge" and stay tuned for September's topic, "Games."

Links from reddit.com/r/emacs, r/orgmode, r/spacemacs, Mastodon #emacs, Bluesky #emacs, Hacker News, lobste.rs, programming.dev, lemmy.world, lemmy.ml, planet.emacslife.com, YouTube, the Emacs NEWS file, Emacs Calendar, and emacs-devel. Thanks to Andrés Ramírez for emacs-devel links. Do you have an Emacs-related link or announcement? Please e-mail me at sacha@sachachua.com. Thank you!

View Org source for this post

You can e-mail me at sacha@sachachua.com.

-1:-- 2026-08-31 Emacs news (Post Sacha Chua)--L0--C0--2026-08-31T19:23:36.000Z

James Cherti: Securing Emacs .dir-locals.el and local variables

Emacs automatically applies project-specific configurations through file-local and directory-local (.dir-locals.el) variables when opening a file or directory. While this feature ensures consistent settings across environments, it can cause security risks and persistent prompt fatigue when editing source code. Malicious .dir-locals.el files or file-local variables containing eval forms can execute arbitrary Lisp code if Emacs is configured to evaluate them, or if the user approves the relevant prompt by mistake.

This article outlines configurations for securing file-local and directory-local variables while reducing prompts.

Allowing safe local variables

The enable-local-variables variable controls how Emacs handles file-local variables. File-local variables are configurations embedded directly within a file. Here is an example of a file-local variables:

;; Local variables:
;; fill-column: 100
;; byte-compile-warnings: (not free-vars)
;; End:

To mitigate security risks and prevent interactive prompts, set enable-local-variables to :safe by adding the following to your init file:

(setq enable-local-variables :safe)

The benefit is that Emacs applies only verified safe values while silently dropping risky ones, reducing prompt fatigue without requiring manual confirmation. The tradeoff is that non-standard but safe project variables will be ignored unless their values are explicitly recognized as safe, for example through safe-local-variable-values or the safe-local-variable property.

Note: Setting enable-local-variables to nil disables normal processing of local variables entirely. (Specific settings, such as lexical-binding, remain active via the permanently-enabled-local-variables list.)

Whitelisting specific local variables

When you set enable-local-variables to :safe, Emacs silently ignores unverified variables. To prevent custom project variables from being ignored, you can explicitly mark specific variable/value pairs as safe using safe-local-variable-values, or define a predicate with the safe-local-variable property:

;; Whitelist a specific variable and value: Add the pair directly to the list.
(add-to-list 'safe-local-variable-values '(my-custom-variable . "expected-value"))

;; Whitelist a variable based on a predicate type: Define a property that
;; validates the variable type to accept any matching value.
(put 'my-custom-variable 'safe-local-variable #'stringp)

;; Whitelist a variable with a predicate: Only allow "hello" or "world"
;; as safe values.
(put 'my-custom-variable 'safe-local-variable
     (lambda (val)
       "Return t if VAL is 'hello' or 'world'."
       (member val '("hello" "world"))))

Whitelisting specific directories

The safe-local-variable-directories variable allows you to designate specific paths where all directory-local variables are automatically considered safe. When a directory is added to this list, Emacs trusts every variable defined in its .dir-locals.el file without prompting. Note that this setting applies exclusively to directory-local variables and completely ignores file-local variables.

(add-to-list 'safe-local-variable-directories "/path/to/trusted/project/")

Disabling Local Eval Forms

The enable-local-eval variable controls whether Emacs processes eval forms in file-local and directory-local variable definitions. Unlike ordinary local variable assignments, eval can cause Emacs to evaluate arbitrary Lisp code when processing the local variables.

If your workflow does not require dynamic project configurations, you should disable this feature by setting this variable to nil:

(setq enable-local-eval nil)

The tradeoff is that useful evaluation forms, such as project-specific configurations dynamically generated through eval, will not execute.

Keeping default values for security

Emacs comes with the following defaults for local environment handling. Sticking with these defaults is already a good choice:

(setq enable-dir-local-variables t)  ; Apply directory-local variables.
(setq enable-remote-dir-locals nil)  ; Prevent loading dir-locals over TRAMP.
  • enable-dir-local-variables: This enables the use of directory-local variables (.dir-locals.el). Note that Emacs also reads .dir-locals-2.el if present, and non-file buffers like Dired can inherit these settings.
  • enable-remote-dir-locals: Leaving this as nil prevents Emacs from loading directory-local variables from remote filesystems. This avoids applying potentially untrusted remote .dir-locals.el settings and avoids the additional work required to search for them.
-1:-- Securing Emacs .dir-locals.el and local variables (Post James Cherti)--L0--C0--2026-08-31T17:34:49.000Z

Curtis McHale: The Search for Knowledge - Emacs Carnival

August Emacs carnival is about the search for knowledge. While I take notes and link them, I don't use Emacs. I've been on the Obsidian train pretty much since it came out with a short deviation into Craft.

I've loved the plugins that Obsidian has, but those plugins are also it's downfall. I used the Long Form plugin for a while, but then stopped and move that writing to Emacs because the plugin was loosing my data any time I tried to use it. But despite my love for Emacs, I haven't moved my notes over to it and I have no plans on moving my notes over to Emacs.

Currently Obsidian has around 9,000 individual files across my notes and writing. It's saved in a portable markdown format. Obsidian's sync service works flawlessly all the time and the mobile apps are good.

On the Emacs side, every few weeks I have some issue with Syncthing that means I need to sort out some type of sync conflict and because of this I really only add content from my phone via Beorg to my inbox.org file. I don't edit other files if I can help it, the risk is simply to high that I'll have some huge mess to sort out when I get back to my desktop.

The main reason I don't switch is that I don't think that migrating tools is the big productivity boost that most people think it is. Yes the Emacs vim key bindings are better than what Obsidian provides. Yes I can very easily hack Emacs to do whatever I want, but Obsidian does everything I need already. So moving my notes to Emacs would most likely be me finding Emacs more fun right now than Obsidian because it's new.

I've wondered about accessing Obsidian from Emacs so I have Emacs goodness in my Obisidian, but even that feels like me searching for a reason to use Emacs where I don't really need it.

If you want to read how I setup Obsidian, my 2026 walkthrough is still how I do it.

-1:-- The Search for Knowledge - Emacs Carnival (Post Curtis McHale)--L0--C0--2026-08-31T16:34:00.000Z

Irreal: Maiorana On Directory Local Variables

Chris Maiorana has a useful post on directory local variables. The idea is that rather than putting a lot of context specific configuration in your init.el file, you can put those configurations in a .dir-locals.el file and they will be set only when you open a file in that directory.

I really like the idea of directory local variables but it has never worked well for me. That’s mainly because I usually wanted to execute some code using the eval keyword. That always causes some problems involving a stack overflow, no matter how simple the code is. Lately, I’ve been using them a lot in conjunction with Jinx, which has the nice feature of adding words to the local file or local directory in addition to the other usual options.

I find that really useful for my blogging. I can add post specific words to the post source file itself or I can add them to all posts in my blog directory. Maiorana uses then to set things like compilation settings, fill and wrap settings, and indentation styles.

If you have different workflows with different setting needs, directory local variables is a very nice way of dealing with them. Of course, not everything should go in a directory local variable. Some things apply to all your buffers and should go in your init.el but for those things that are specific to particular workflows, directory local variables are just what you need.

-1:-- Maiorana On Directory Local Variables (Post Irreal)--L0--C0--2026-08-30T14:37:17.000Z

James Cherti: Fixing Emacs Dired defaults - Settings for better file management

This article presents a set of Emacs Dired configuration changes to enhance file management. The configurations sort directories before files for easier navigation, hide dotfiles, reduce unnecessary confirmation prompts, prevent UI lockups caused by network latency, among other improvements. Each configuration explains the behavior of the relevant setting, its benefits, and its tradeoffs, making it easier to determine which changes are appropriate.

Keeping Dired clean by hiding dotfiles

Using dired-omit-mode helps hide hidden files and directories, such as .git or .DS_Store, by applying a specific regular expression. The tradeoff is that required configuration files are hidden by default, forcing you to toggle dired-omit-mode off to see them. Disabling dired-omit-verbose prevents the mode from displaying status messages when omitting files.

(setq dired-omit-verbose nil)
(setq dired-omit-files (concat "\\`[.]\\'"
                               "\\|\\.\\(?:elc\\|a\\|o\\|pyc\\|pyo\\|swp\\|class\\)\\'"
                               "\\|^\\.DS_Store\\'"
                               "\\|^\\.\\(?:svn\\|git\\)\\'"
                               "\\|^flycheck_.*"
                               "\\|^flymake_.*"))

(add-hook 'dired-mode-hook #'dired-omit-mode)

To hide all hidden files whose names begin with a dot, add the following regular expression to the dired-omit-files variable:

(setq dired-omit-files (concat dired-omit-files "\\|^\\."))

Hiding details

Enabling dired-hide-details-mode automatically hides file details such as permissions, size, and modification dates.

(add-hook 'dired-mode-hook #'dired-hide-details-mode)

Sorting directories first

The following code snippet configures Dired to sort directories before files. The benefit is improved navigation, as folders are grouped at the top of the buffer, matching standard file managers. The tradeoff is a deviation from strict alphabetical sorting, which can disorient users accustomed to raw UNIX or Linux ls output.

(setq ls-lisp-verbosity nil
      ls-lisp-dirs-first t)

(when (eq system-type 'darwin)
  (setq dired-use-ls-dired nil)) ; macOS/BSD ls

(let ((args "--group-directories-first -ahlv"))
  (when (or (eq system-type 'darwin) (eq system-type 'berkeley-unix))
    (if-let* ((gls (executable-find "gls")))
        (setq insert-directory-program gls)
      (setq args nil)))
  (when args
    (setq dired-listing-switches args)))

Killing the current Dired buffer upon navigating into a different directory

Setting dired-kill-when-opening-new-dired-buffer to t (Emacs >= 28.1) causes Dired to automatically kill the current Dired buffer upon navigating into a different directory. The benefit is a cleaner buffer list, as it prevents leaving behind a trail of open buffers when traversing deep directory hierarchies. The tradeoff is the loss of buffer state. Navigating back to a parent directory requires generating a new buffer, which erases previous cursor positions, active file marks, and modified subdirectory visibility.

(setq dired-kill-when-opening-new-dired-buffer t)

Version control integration

Enabling dired-vc-rename-file causes Dired to perform file renames through the underlying version control system when supported, using the vc-rename-file function. The benefit is accurate version control history during Dired rename operations. The tradeoff is that VC renames can introduce slight latency on very large repositories.

(setq dired-vc-rename-file t)

Simplifying deletion confirmations

This configuration accelerates file cleanup by reducing the number of manual prompts required to delete files and directories.

(setq dired-deletion-confirmer 'y-or-n-p
      dired-recursive-deletes 'top
      dired-clean-confirm-killing-deleted-buffers nil)
  • Setting dired-deletion-confirmer to 'y-or-n-p: Changes the deletion prompt to accept a single 'y' or 'n' keypress instead of requiring you to type the full word 'yes'.
  • Setting dired-recursive-deletes to 'top: By default, deleting a non-empty directory in Dired can be a tedious process because Emacs will prompt you to confirm the deletion of every single nested subdirectory as it traverses down the tree. Setting this variable to 'top establishes a practical balance between speed and safety. Emacs will ask for your confirmation exactly once for the top-level directory you have marked. Once you confirm that initial prompt, it silently and recursively deletes all nested files and folders inside that tree without any further interruptions.
  • Setting dired-clean-confirm-killing-deleted-buffers to nil: Suppresses the prompt that asks for permission to close Dired buffers associated with directories you just deleted, closing them automatically instead. This makes sense because once a directory is removed from the disk, any open buffer pointing to it becomes orphaned and unusable. Automatically killing these dead buffers keeps your buffer list clean, as deleting the directory already establishes your intent to remove it from your workflow entirely.

The benefit is faster file management and fewer repetitive keystrokes. The tradeoff is an elevated risk of accidental data loss, as the safety barrier to executing destructive operations is significantly lowered.

Removing disk space indicator

Disabling dired-free-space (Emacs >= 29.1) removes disk space indicator from Dired buffers. The tradeoff is that you lose quick visibility into remaining disk capacity.

(setq dired-free-space nil)

Restricting vertical cursor movement

Setting dired-movement-style to 'bounded-files (Emacs >= 29.1) restricts vertical movement to file lines in the Dired buffer. When point reaches the first or last file line, further vertical movement stops instead of moving to other non-file lines. The benefit is increased navigation efficiency. When scrolling rapidly to the top or bottom of a directory, the cursor stops exactly on the first or last file or directory, avoiding empty space or metadata lines. The tradeoff is that placing the cursor on the directory header to copy its path requires alternative navigation commands, as standard vertical movement keys will no longer reach it.

(setq dired-movement-style 'bounded-files)

Managing recursive copies and destination directories

These variables force Dired to copy directories recursively by default without prompting, while explicitly asking for confirmation before creating new, non-existent destination directories. The benefit is bulk copying combined with a safety check against typos in destination paths. The tradeoff is that large, deeply nested directory structures might be copied unintentionally since the recursive copying happens automatically.

(setq dired-recursive-copies 'always
      dired-create-destination-dirs 'ask)

Efficient auto-reverting for dired buffers

Configuring dired-auto-revert-buffer to use dired-directory-changed-p causes Dired to automatically revert the buffer when the listed directory has changed. The benefit is improved efficiency because Dired does not unconditionally rebuild the listing. The tradeoff is that changes that do not modify the directory itself, such as in-place edits to existing files, might not trigger an automatic refresh.

(setq dired-auto-revert-buffer 'dired-directory-changed-p)

Note: On Unix-like filesystems, a directory's mtime (modification time) is updated when its directory entries change. This occurs when a file or subdirectory is created, deleted, or renamed within the directory. Modifying the data inside an existing file updates the file's mtime, but does not update the mtime of its parent directory.

Reverting destination buffers after file operations

This configuration automatically updates destination Dired buffers after file operations like copying or renaming, but uses a custom predicate to skip remote directories. The benefit is automatic synchronization of local Dired buffers without performing the same operation on remote directories, avoiding unnecessary TRAMP network activity and associated latency. The tradeoff is that remote directory buffers require a manual refresh to reflect recent file transfers.

(setq dired-do-revert-buffer (lambda (dir)
                               (not (file-remote-p dir))))

Enabling mouse drag-and-drop

Setting dired-mouse-drag-files to t (Emacs >= 29.1) allows you to click and drag files directly from a Dired buffer into external desktop applications, such as graphical file managers or web browsers.

(setq dired-mouse-drag-files t)

Cross-platform file associations

The following snippet configures Dired to open specific file extensions using the default application handler of the host operating system. It identifies the environment (macOS, Linux, or Windows) and assigns the corresponding system command (open, xdg-open, or start) to the dired-guess-shell-alist-user variable for documents, images, and media files. The main benefit is that it delegates file association management to the operating system, removing the need to configure individual applications within Emacs for every file type.

(defvar my-dired-xdg-open-cmd nil)
(with-eval-after-load 'dired
  (when-let* ((cmd (cond
                    ((eq system-type 'darwin)
                     "open")
                    ((memq system-type '(gnu gnu/linux gnu/kfreebsd
                                             berkeley-unix))
                     "xdg-open")
                    ((memq system-type '(cygwin windows-nt ms-dos))
                     "start"))))
    (setq dired-guess-shell-alist-user
          `(("\\.\\(?:docx\\|pdf\\|odt\\|odg\\|ods\\|djvu\\|eps\\)\\'" ,cmd)
            ("\\.\\(?:jpe?g\\|webp\\|png\\|gif\\|xpm\\)\\'" ,cmd)
            ("\\.\\(?:xcf\\)\\'" ,cmd)
            ("\\.tex\\'" ,cmd)
            ("\\.\\(?:mp4\\|mkv\\|m4a\\|avi\\|flv\\|rm\\|rmvb\\|ogv\\)\\(?:\\.part\\)?\\'" ,cmd)
            ("\\.\\(?:mp3\\|flac\\)\\'" ,cmd)))
    (when cmd
      (setq my-dired-xdg-open-cmd cmd))))

How to use it: To use this configuration in practice, navigate to a directory using Dired and place your cursor over a file. For example, if your cursor is on a video file, press ! (which invokes dired-do-shell-command) or & (for asynchronous execution). Dired will prompt you in the minibuffer with the appropriate system command already populated based on your operating system, such as xdg-open. Press RET to confirm, and the video will open in your operating system's default media player.

If you prefer forcing all file types to open asynchronously via the operating system handler (e.g., xdg-open), the following snippet routes every file through the system default application.

(defvar my-dired-xdg-open-cmd nil)
(with-eval-after-load 'dired
  (when-let* ((cmd (cond
                    ((eq system-type 'darwin)
                     "open")
                    ((memq system-type '(gnu gnu/linux gnu/kfreebsd
                                             berkeley-unix))
                     "xdg-open")
                    ((memq system-type '(cygwin windows-nt ms-dos))
                     "start"))))
    (setq dired-guess-shell-alist-user
          `((".*" ,cmd)))
    (when cmd
      (setq my-dired-xdg-open-cmd cmd))))
-1:-- Fixing Emacs Dired defaults - Settings for better file management (Post James Cherti)--L0--C0--2026-08-29T15:45:17.000Z

Irreal: Emacs Configuration Organization

Emacs configurations are like well shuffled card decks: no two are alike. But, as Arlo Guthrie famously sang, that’s “not what I came to tell you about”. Rather, this post is about differing organizations for those Emacs configurations.

The idea was suggested to me by a post from Andros Fenollosa on his own Emacs configuration. Fenollosa says that he doesn’t use any of the all-in-one configurations like Spacemacs or Doom because they include too much and are hard to understand and make changes to. He also doesn’t use the popular literate configuration method using Org mode because it’s just another layer of complexity that can go wrong.

His idea is to to have a separate file for each “area of responsibility” and to use init.el as a sort of index that loads them in order. I like that idea but don’t use separate files. I keep everything in init.el and just organize it in sections.

Still, Fenollosa has a good argument for the separate files but I think I get the same benefits with a single file broken into sections. Actually, it’s not quite true that everything is in a single file. I run Emacs on several type of systems and keep the system/OS specific information in separate files that I load conditionally based on the system and OS type1. That way I don’t have a lot of conditionals dealing with system/OS type in init.el itself.

There aren’t as many ways of organizing your Emacs configuration as there are configurations but there are a lot of them. What principle do you use?

Footnotes:

1

If you’re interested, here’s the code:

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Pull in system and platform specific configurations                    ;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

;; Just keep on going if the requisite file isn't there.
;; Manipulations in second load is for "gnu/linux" → "linux"

(load (car (split-string (system-name) "\\.")) t)
(load (car (reverse (split-string (symbol-name system-type) "/"))) t)
-1:-- Emacs Configuration Organization (Post Irreal)--L0--C0--2026-08-29T15:12:53.000Z

Sacha Chua: Carnaval d'Emacs d'août 2026 : la gestion de l'information et les graphes de connaissances

Cet article est inspiré par le Carnaval d'Emacs sur la recherche de connaissances. Merci à Charlie Holland pour son accueil ! C'est une bonne occasion de réfléchir à la gestion de mes notes, qui est un de mes intérêts forts.

Saisir : Je souhaite saisir et publier ce que j'apprends aussitôt que possible parce que ma mémoire n'est pas fiable. Je préfère le format texte brut car il est le plus consultable et le plus archivable à long terme. Je suis loin de mon ordinateur la plupart du temps, donc j'utilise Orgzly Revived sur mon téléphone pour saisir des notes courtes dans ma boîte de réception inbox.org ou mon fichier de brouillons posts.org. Une fois que je suis sur mon ordinateur, je développe mes notes en utilisant Org Mode sur Emacs. J'utilise org-refile pour déplacer des notes vers d'autres fichiers comme organizer.org. J'utilise quelques grands fichiers Org. Pour les tâches répétitives comme mes flux de travail, j'y ajoute des détails autant que possible.

Chercher : Pour chercher mes notes publiques, j'utilise souvent Google. Les notes publiques me permettent de les récupérer facilement. Si je publie mes notes, d'autres personnes peuvent en profiter et les enrichir. Forcément, j'ai aussi des notes personnelles. J'utilise org-refile pour chercher par titres ou consult-ripgrep pour naviguer dans mes notes privées. J'utilise aussi consult-line et isearch si je veux chercher par mots dans le corps. Ils reposent sur des comparaisons exactes, mais si j'essaie des mots similaires, je ne peux pas trouver ce que je cherche. C'est la raison pour laquelle je m'intéresse au paquet p-search et aux plongements de phrases (embeddings) pour la recherche approximative, mais je n'ai pas encore mis en place un bon flux de travail. Je rêve d'un système pour suggérer automatiquement des liens vers mes autres articles, ma configuration et mes notes privées, ce qui peut me rafraîchir la mémoire sur des choses oubliées. Une vraie évaluation doit attendre d'avoir plus de temps libre.

Naviguer : J'utilise C-u org-refile pour naviguer dans mes sous-titres n'importe où dans mes fichiers org-refile-targets. Je relis aussi ma boîte de réception et mes brouillons de temps en temps. J'ai une fonction sacha-blog-edit-org qui ouvre le code source Org à partir d'un lien, même s'il est dans mon posts.org ou seulement dans la copie publiée.

Lier : Beaucoup de mes idées sont inspirées par des articles d'autres personnes, donc quand je trouve un article intéressant sur mon téléphone, je l'envoie à Orgzly Revived pour l'ajouter à ma boîte de réception. Ça me permet d'inclure le lien ou les liens dans l'article une fois que je réussis finalement à l'écrire et à le publier.

J'ai une petite fonction sacha-org-contacts-suggest-mentions qui m'aide à notifier l'auteur de l'article précédent et peut-être d'autres personnes qui sont potentiellement intéressées en utilisant mon fichier people.org, où j'ai spécifié des expressions régulières à comparer au texte.

Pour m'aider à lier l'article aux autres ressources, j'ai des fonctions pour lier :

(Hmm, je peux automatiser les liens vers les autres parties de ma configuration qui définissent les autres fonctions…)

Mes pensées sont souvent déconnectées à cause d'un cerveau qui aime sauter d'un sujet à l'autre.​ (Bien évidemment, même avec cet article…) En écrivant un article, j'ajoute de nombreuses idées dans ma boîte de réception. À bien y penser, je pourrais ajouter à Org Mode un genre de lien qui résoudra un vrai lien une fois que l'article lié sera publié, comme les WikiWords, ce qui peut m'aider à les connecter. Je sais qu'il y a des paquets qui offrent cette fonctionnalité.

Publier : J'utilise le générateur de site statique 11ty avec ox-11ty.el. Une fois que je publie une note, le code source org est aussi copié dans le même répertoire.

Visualisation et exploration

J'ai toujours envie de graphes de connaissances comme celui dans le cerveau de Jerry. Je pense que l'affichage du voisinage autour de l'article actuel est plus utile qu'un aperçu global (comme ceux de ) qui est impressionnant mais un peu trop difficile à utiliser.​

J'adore également les grands jardins publics de connaissances comme celui d'Andy Matuschak même si on n'a pas une véritable carte ou un graphe. La richesse des liens m'encourage à les explorer. Je préfère les liens intégrés plutôt que les listes ou les connexions ​sans explications. Je suis curieuse aussi du projet Anagora, qui essaye de créer un grand réseau de graphes de connaissances personnels et de faciliter le saut de l'un à l'autre par sujet.

Mais je ne veux pas investir trop d'efforts pour le faire moi-même. La majorité de mes articles (sauf le bulletin d'information Emacs News et les revues) contiennent peu de liens. Si je reste au niveau des catégories, il y a trop de points près d'Emacs. Je pourrais convertir les sous-catégories dans mon catalogue en données pour la visualisation… Quand même, le catalogue aurait bien besoin d'une mise à jour.

Il y a d'autres genres de graphes que j'utilise fréquemment. Je dessine souvent pendant que j'écris. Je fais des cartes mentales et des notes dessinées. En fait, c'est plutôt que je jette plusieurs mots sur une page et puis que je les déplace, les connecte, et les range graduellement. Je le fais pour découvrir ce que je veux dire et comment je peux les organiser. De temps en temps, j'inclus des dessins dans les articles ou les publie dans mon carnet de croquis public, et c'est une agréable surprise quand un de ces dessins intéresse d'autres personnes.

J'accumule beaucoup de brouillons dans mon fichier posts.org, que je synchronise avec mon téléphone via Syncthing pour éditer sur Orgzly Revived. De temps en temps, j'utilise une carte arborescente (ou treemap) pour visualiser les tailles des brouillons oubliés (et qui sont peut-être presque finalisés, il leur manque seulement une petite révision ou un ajout). (Hmm, je me demande comment je peux ajouter la taille du sous-arbre aux titres d'Org…)

2026-08-29_08-16-41.png
Figure 1: Une capture d'écran de ma carte arborescente pour mon fichier actuel posts.org

En plus, j'apprécie l'analyse des tendances saisonnières ou annuelles dans ma fréquence de publication (qui me rassure sur le fait que les journées chargées de l'été sont normales et je retrouverai bientôt un peu plus de temps libre):

Tendances mensuelles
import json
import seaborn as sns
import re
from collections import defaultdict
import requests
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
with open('/home/sacha/proj/static-blog/_site/blog/all/index.json') as jsonfile:
    posts = json.load(jsonfile)
    jsonfile.close()
monthly_counts = defaultdict(lambda: defaultdict(int))
for post in posts:
    title = post.get("title", "")
    date_str = post.get("date", "")
    if re.search(r'emacs news', title, re.IGNORECASE):
        continue
    if date_str and len(date_str) >= 7:
        year = date_str[:4]
        month = int(date_str[5:7])
        monthly_counts[year][month] += 1
months_labels = ['Jan', 'Fév', 'Mar', 'Avr', 'Mai', 'Juin', 'Juil', 'Août', 'Sept', 'Oct', 'Nov', 'Déc']
years = ['2022', '2023', '2024', '2025', '2026']
df = pd.DataFrame(index=range(1, 13), data={'Mois': months_labels})
for year in years:
    df[year] = [monthly_counts[year][m] for m in range(1, 13)]
df.iloc[8:, -1] = None
df_long = df.melt(id_vars=['Mois'], value_vars=years, var_name='Année', value_name='Compte')
num_old_years = len(years) - 1
gray_shades = np.linspace(0.8, 0.3, num_old_years)  # 0.8 is lighter, 0.3 is darker
custom_colors = {year: str(shade) for year, shade in zip(years[:-1], gray_shades)}
custom_colors['2026'] = "#000000"  # Force the current year to pure black
widths = {year: 1 for year in years}
widths['2026'] = 3
plt.figure(figsize=(10, 5))
ax = sns.lineplot(data=df_long, hue='Année', x='Mois', y='Compte', size='Année', palette=custom_colors, sizes=widths, sort=False)
ax.set_xticks(range(12))
ax.set_xticklabels(months_labels)
plt.title("Fréquence mensuelle des articles sur sachachua.com\n(hors Emacs News)", fontsize=12, fontweight='bold')
plt.xlabel("Mois", fontsize=10)
plt.ylabel("Nombre d'articles publiés", fontsize=10)
plt.grid(True, linestyle='--', alpha=0.5)
plt.legend(loc='upper right')
plt.tight_layout()
plt.savefig('frequence.svg')
return df
  Mois 2022 2023 2024 2025 2026
1 Jan 6 19 20 23 16.0
2 Fév 0 3 0 10 8.0
3 Mar 0 5 1 24 15.0
4 Avr 0 2 1 14 22.0
5 Mai 0 1 1 9 15.0
6 Juin 0 3 1 9 13.0
7 Juil 1 0 0 6 9.0
8 Août 7 2 1 5 11.0
9 Sept 1 8 12 17 nan
10 Oct 2 12 29 11 nan
11 Nov 5 1 19 8 nan
12 Déc 4 15 6 6 nan
2026-08-30T08:43:07.159204 image/svg+xml Matplotlib v3.11.0, https://matplotlib.org/

et la croissance graduelle de mon vocabulaire français selon mon journal:

01_cumulative_vocab.png

Bien qu'ils ne soient pas des graphes de connaissances classiques, ils m'aident à voir des tendances qui ne sont pas évidentes au jour le jour.

Savoir collectif

Je suis plus intéressée par le réseau de savoir collectif plutôt que par mes notes personnelles. Depuis plus de dix ans, je rassemble et catégorise de nombreux liens pour Emacs News, ce qui permet de les parcourir. J'adore rencontrer beaucoup d'idées et de nombreuses personnes en chemin. C'est facile à faire et cela ne demande pas trop de temps, donc j'ai pu continuer ainsi malgré les interruptions de ma vie de maman. En parlant de graphes de connaissances, le bulletin d'information sert à connecter des nœuds et des personnes. De temps en temps, je suis très contente d'entendre qu'un article en inspire un autre, et encore un autre, puis une collaboration… C'est le pouvoir de créer des liens entre les personnes qui apprécient des choses similaires. Si je deviens incapable de faire ce bulletin hebdomadaire, j'espère que quelqu'un le continuera.

Grâce à mon assemblage de liens pour Emacs News, je rencontre souvent des occasions de recommander un article ou une personne en réponse à une question ou un message. Si je peux trouver l'article dans mon fichier d'archive d'Emacs News avec consult-line ou isearch, je peux coller le lien exact. De temps en temps, je veux présenter une personne, ce qui est un peu difficile sur Mastodon parce que les noms de Mastodon sont souvent très différents de leurs vrais noms. Je note les noms de Mastodon dans mon fichier people.org, et j'ai une petite fonction sacha-mastodon-insert-handle-from-contacts pour compléter les noms de Mastodon. J'ai aussi une autre fonction sacha-mastodon-insert-interested-handles qui ajoute des personnes qui peuvent être intéressées sur la base des correspondances d'expressions régulières dans le message, comme quand j'écris mes articles.

En plus des connexions entre des articles et des personnes, je suis également intéressée par les connexions entre des sujets. Avant d'apprendre une chose, quels sujets doivent la précéder ? Quand on apprend une chose, qu'est-ce qui est proche et plus facile à apprendre par la suite ? C'est peut-être utile pour donner des conseils ou des suggestions en tant que coach, ou pour apprendre en mode autodidacte. Les besoins et les chemins diffèrent selon la personne, donc c'est impossible de tracer un parcours qui convient à tout le monde. J'ai commencé à faire une carte des ressources pour les débutants, mais je pense que c'est toujours un peu intimidant, même pour moi. La place naturelle de ces liens est peut-être EmacsWiki pour permettre aux autres personnes de les trouver et de les enrichir, si je trouve une façon de sauvegarder les données et si je résous le problème de l'édition de quelques pages. En plus, si j'étudie les flux de travail et que je les classifie par aspect, je pourrais aider quelqu'un à trouver des ressources qui sont similaires à ses idées.

Je veux aussi identifier des lacunes sur lesquelles me focaliser quand j'ai du temps libre. Il y a beaucoup de ressources pour les débutants, mais grâce à l'immense capacité de personnaliser Emacs, il y a une explosion combinatoire de possibilités au niveau intermédiaire. J'ai hâte d'explorer mes questions et d'autres. C'est difficile de trouver et de naviguer dans les informations, donc si je commence avec des améliorations personnelles ou des questions concrètes de personnes spécifiques, c'est mieux que d'écrire isolément. Je suis sûre qu'en répondant aux questions individuelles, les points communs émergeront.

Je suis aussi fascinée par mes études sur d'autres sujets. J'apprends le français et je m'amuse tellement à bricoler mon environnement Emacs et mon processus d'apprentissage. Un des conseils pour les débutants est de développer des îlots linguistiques : les mots autour d'un sujet passionnant. (Comme cet article-ci que j'écris en français pour me forcer à enrichir mon vocabulaire.) Je me demande comment visualiser ces choses… Par exemple, si je récupère des listes de mots triés par fréquence (peut-être en analysant la base de données lexique), je peux créer une matrice de carrés… Hmm…

Mes obligations de maman diminuent au fur et à mesure que ma fille grandit. J'espère pouvoir consacrer plus de temps à explorer, à saisir et à partager des choses intéressantes.

View Org source for this post

You can comment on Mastodon or e-mail me at sacha@sachachua.com.

-1:-- Carnaval d'Emacs d'août 2026 : la gestion de l'information et les graphes de connaissances (Post Sacha Chua)--L0--C0--2026-08-29T14:52:30.000Z

TAONAW - Emacs and Org Mode: Emacs Config Gems - Part 5

Being a text editor (among other things), Emacs has quite a few tricks up its sleeve for making writing not just easier but also fun and seemingly magical. I call this section in my config “Wordcraft.”

Emacs Dictionary

When we need a dictionary, Emacs got us covered and then some. After version 28, Emacs connects to a dictionary as a two-step process by default:

  1. It tries to connect to a local host.
  2. If it fails, it next connects to an online dictionary at www.dict.org,

This workflow remains in place as long as dictionary-server is nil, which is the default. It’s a bit confusing (to me anyway), since “nil” might indicate that no dictionary is set up, but here it means we simply didn’t introduce any configuration to it.

The online version at dict.org contains the Webster dictionary from 19131, which (if you follow the footnote) will start sounding like an excellent resource. What I didn’t know up until now however is that dict.org contains more than just this wonderful dictionary.

When you search for something like “US” with Emacs’ built-in dictionary, it checks this database and gives you results from:

  1. The Collaborative International Dictionary of English
  2. WordNet
  3. V.E.R.A. – Virtual Entity of Relevant Acronyms
  4. The Free Online Dictionary of Computing (it has an entry as an ASCII character)

There are others, like the CIA Factbook and the Bible, among others. All of those are at your fingertips, inside Emacs, from a simple quick search2.

The problem is that it’s an online dictionary. No internet, no dictionary. On my MacBook this can happen as I take it with me when I travel. Meanwhile, on Linux, I’m always online (desktop, wired connection).

Follow the footnote, and you will learn how to get that Webster dictionary (but just that, not the database at dict.org) working offline. As for me, I don’t want to set up an offline dictionary because macOS’s dictionary app3 (which works fine offline) is good enough — a quick copy-paste and I’m done. So I configured my dictionary-server to point directly to dict.org, so Emacs doesn’t even search for a local one first.

Here is the setting itself, including the localhost option as a reference and a reminder:

;; (set dictionary-server nil) default, search localhost and then dict.org
;; (setq dictionary-server "localhost") local only, no online search
  (setq dictionary-server "dict.org")

Hippie Expand

I learned about hippie-expand through Mastering Emacs. It’s one of those things that make Emacs magic. It uses different expansion functions (not to be confused with completion functions, which we will learn more about in a minute) to guess which word should come next in our text as we write, depending on which buffer we are in.

Following Mickey’s advice (linked above), I rebound this to M-/, which is bound to dabbrev-expand by default. It’s similar, but not as powerful (essentially it’s more basic; hippie-expand takes the functions that dabbrev-expand uses and builds on top of those with additional ones). Keep pressing M-/ to flip through suggestions.

The catch is that you need to open the buffers you want it to look through. For example, if I want it to pull words from my personal journal while writing this post, I need to have my journal buffer open somewhere. If you’re one of the people who don’t turn off Emacs often, this is not a problem — just keep that in mind when you use it.

Turning it on (by replacing the command, using the same binding):

(global-set-key [remap dabbrev-expand] 'hippie-expand)

We also want to change the order in which it searches for things so it works better for us. For me, as a person who does less coding and more writing, I prefer it to look at Lisp-related functions toward the end. The same logic applies to global (all-buffers) lists vs those that operate on the visible buffer only. So our first M-/ will give us suggestions from our current buffer, and as we keep pressing M-/ and exhaust those, it will search all the other buffers we have open. for other suggestions4.

(setq hippie-expand-try-functions-list
      '(try-expand-dabbrev
        try-expand-dabbrev-all-buffers
        try-expand-dabbrev-from-kill
        try-expand-line
        try-expand-line-all-buffers
        try-expand-list
        try-expand-list-all-buffers
        try-complete-file-name-partially
        try-complete-file-name
        try-expand-all-abbrevs
        try-complete-lisp-symbol-partially
        try-complete-lisp-symbol))

ispell

ispell is one of those things I set around the time I started using Emacs and never looked at again. I’ve made a mistake: I bound ispell-region to M-$. Those of you who use ispell often are probably perking an eyebrow: “huh? Why would you do that for? ispell does it by default!”

But somehow I didn’t know this. For years I highlighted whatever I needed to spell check, even single words. The default keybinding attributes M-$ to ispell-word, which checks the word at the marker, but if you have a region selected, it spellchecks the region. I should have given Emacs more credit; today I know better.

Another really handy thing I did not know about ispell is its ability to complete words I’m unsure of. But before we go on, please note that word completion (and completion functions) in Emacs is a big topic that I just spent a whole week on, and what I’m going to discuss here is just the tip of the iceberg.

First, the useful built-in completion built into ispell itself: l for lookup. Say you forgot how to write “psychiatry”. You type what you know — “psy” — and then call ispell, and as the suggestions show up, press l. ispell will ask you what to complete, along with a wildcard, so add * and then y so you have “psy*y”. ispell will show you what words start with psy and have whatever letters after but end with y. Useful!

Another thing I didn’t know: use R for query replace. This is good when you make the same mistake across several words. For example, when I wrote this post, I originally capitalized every ispell as Ispell. No problem. Stand on one of those, call ispell, press R to replace, and change it to ispell. Now you are prompted if you want to change all of them one by one, or, just hit ! and get it over with in one swoop.

With those quick tips, now back to completion functions.

Years ago, when I started using Emacs, I installed company with its minimal default settings and called it a day. It’s been working OK, and up until I started writing this post, I didn’t realize how much about Emacs completion-at-point functions (capf). Prot has a good video about this, which I’m still chewing through, but basically for ispell the magic happens when you stand on a word you don’t know how to finish and ask Emacs to suggest completions.

To understand how this works, Let’s say we don’t know if water is written as watter or watar, as a simple example.

  1. We write “wate” and we ask Emacs to complete the word by calling completion with ispell with M-C i. Because the only word that exists (in English anyway) that starts with “wate” is water, Emacs will complete it for us to “water” automatically.
  2. Now, while standing on the word “water,” we call completion again. Emacs informs us: complete, but not unique. In other words: the word water is real and complete (we have it spelled right) but there are other words that start with water.
  3. While still standing on “water”, we call complete yet again. Emacs goes “OK. So you know it’s a word that exists, and you also know there are other words that start with water, so if you’re calling complete again, you probably want to see the other words that start with water.”

Bam, we have a buffer full of words that start with water, like waterboard or watermelon. We can select a word from this menu, even though it’s a bit weird — by going down or up with M-⇡ and M-⇣, then insert the word we want with M-Ret5.

This is nice, but usually you want to complete words that you already know, or kind of know. If I type “psych,” I don’t need a whole buffer of words if I’m looking for “psychology”; I just slowly add letters until I find what I need — and this is exactly what packages like company or corfu6 do.

Harper, languagetool and abbrevs

Let me just remind you of those for now and finish up this post. We will dig into those more next time.


  1. This post from irreal is a good place to get a bit more understanding of how to use a local dictionary, a copy of the online one, and why. If you read it, you may find your way into Irreal’s older post and then this wonderful gem he found: You’re probably using the wrong dictionary. The way these posts are written, I feel it makes more sense to read them in the reverse order I presented here, inviting more exploration as you go — however, if you’re short on time or patience, I recommend you read at least that last one, especially if you enjoy writing. ↩︎

  2. As I was learning this, this next question popped into my mind: can we then look for a definition in Wikipedia and the Urban Dictionary as well? The straight answer: yes (it’s Emacs, duh). The full answer: it’s complicated. We can’t integrate it into the results buffer from dictionary-search directly, but what I can do is have it inside a “wrapper” buffer that will contain the dict.org definition with the Wikipedia and Urban Dictionary below it. For now, I’m fine with just using eww-search-words which initiates a search with Duck Duck Go, which already pulls Wikipedia search results near the top (Wikipedia itself forces JavaScript usage, which blocks Eww) ↩︎

  3. By the way, something fun I discovered while researching: we can call the macOS dictionary using dict:// (as a URL link), and we can build a function in Emacs for it if we want. Further — we could create something like this for offline situations only, but this is not needed at least in my case. Worth mentioning: there’s also the osx-dictionary package, which bridges Emacs and the built-in dictionary app on macOS. But why, when you have a different (and arguably better) version built in? ↩︎

  4. hippie-expand doesn’t really have a help file that lists all the functions, though I think I’ve listed most of those if not all above. Check out the package directly if you want to read more about the functions; they are described in comments at the top of the code. Online, it’s here, or you can navigate your way to the package inside Emacs with C-hkM-/, and then go from there to the package. ↩︎

  5. Turns out you don’t have to do it this way: you can switch to the completion buffer, and then you can move around with arrows, including up and down, and select what you want with Enter like a normal human being. You could also add (setq completion-auto-select t) to your init for this switch to happen automatically whenever you press C-M-i↩︎

  6. As mentioned earlier, these packages and their functions go beyond what I’ll write in this post, but a few things are worth mentioning: company is much older then corfu, which came into existence after Emacs implemented completion-at-point functions; as a result, corfu is based on capf, and doesn’t come with anything “extra” out of the box besides a few option to display completion in a dynamic matter. Company, on the other hand, is a much bigger package that has a whole backend to it, complete with a lot of additional functions. I ended up installing and trying corfu, with Emacs’ native functions, and then installed cape (completion at point extensions) makes more functions available in a buffer than you’d usually have. What does that mean? Well, say you’re writing something in org-mode. the cap functions you have available do not include, for example, file names and paths, which you might want; these functions will be available to you when you are in a shell buffer (usually) — it’s Emacs being Emacs and trying to be useful. So with cape, you can ask for that function to be available in org-mode as well. ↩︎

-1:-- Emacs Config Gems - Part 5 (Post TAONAW - Emacs and Org Mode)--L0--C0--2026-08-29T12:34:14.000Z

Donovan R.: My way of handling knowledge (Emacs Carnival - The Search for Knowledge)

The Search for Knowledge

This post is my participation in this month’s Emacs Carnival, on the theme of The Search for Knowledge.

About my current workflow

My way of handling notes and knowledge has evolved a lot over time. I’ve written about my note-taking journey in this blog post.
As of today, my knowledge management sauce is composed of Emacs and Vakana.mg. I’ve been using Logseq less and less over the past few months.

Indoor

In my day-to-day computer usage, I have the Emacs client open all day long with a scratch buffer ready for anything. I dump everything into the scratch buffer, remove or add paragraphs as needed and use vundo for time traveling. I use no structure except some dashes as separation between topics.
Whether it’s to capture fleeting insights or mold prompts for LLMs, the scratch buffer acts as a buffer between my mind and the computer (notes, prompts, etc.).
If something interesting is worth saving, then I save it. I use Denote for that.
If I’m not using my computer, I use pen and paper to capture thoughts and ideas, which I extract later on using a VLM if it’s worth saving. I talk about the process in this post.

Outdoor

For external capture I use Vakana.mg, my own offline mobile app. It solves the problem of taking photos, writing notes and commenting on them at the same time.
Very practical when I’m in the wild documenting plants. When I’m back home, I can export to an Org file from my phone and send it to my computer using LocalSend.

The Search for Knowledge

Retrieving

I don’t bother too much about retrieving. In Emacs, I either just open Denote or use project-find-regexp to reach whatever I want. Vakana.mg’s search also does fuzzy finding inside note contents.

On knowledge graphs

From what I’ve learned and in my own experience, retention of knowledge works better when the owner defines the visual representation of the information to retain, so it matches the way their mind works, and not the other way around (by letting the patterns emerge).
Putting direct intent into the graph’s shape makes the retention of the knowledge stronger. It’s the same mechanism as “charging an image” for better retention in the Memory Palace technique.
Vakana.mg’s visual design works better for me because it’s easier to traverse nodes in a linear way. And custom threads representing specific topics make for clean reading and understanding.

Emacs Packages for Knowledge Management

I like Hyperbole and org-roam but I don’t use them at all. I use Denote and plain org files. The simpler, the better for me. Even the information structure is very simple: one file = one topic. The length may vary.

Where do all my notes come from?

I don’t copy-paste much from other sources. Most of what I write comes from my brain. Either by digesting some books or through insights and observations.
The exceptions are pictures I take with my phone.

What’s next? What I wish to have

  • Resurfacing random notes.
    I write more notes than I can review. A lot of notes are buried forever. In my spare time, I want to be able to go through my old collected notes. I added the feature in Vakana.mg. I don’t have the system in place yet in Emacs.

  • Whiteboard inside Emacs.
    Artist mode is really good but it’s not that great for sharing because not everyone is a fan of ASCII art. Especially in a professional context. For that, I rely heavily on Excalidraw for diagrams. Now that we have the Emacs canvas, I really hope someone will soon build something similar to Excalidraw within Emacs.

-1:-- My way of handling knowledge (Emacs Carnival - The Search for Knowledge) (Post Donovan R.)--L0--C0--2026-08-29T12:00:00.000Z

Meta Redux: Smarter Form Targeting Is Not Coming to CIDER

A couple of days ago I wrote that smarter form targeting was coming to CIDER, and I ended that post by asking whether I’d got the resolution rules right and whether anything still surprised people. I got an answer. CIDER 2.1 will ship with the classic behaviour intact.

This is not a sad story, though. The detour turned up a bug that was quietly mangling people’s comments, and CIDER came out of it better than it went in.

What I was actually after

The targeting change wasn’t really about cursor positions. What I wanted was for every CIDER command that operates on a form to behave the same way, without adding yet another command to get there. CIDER has an enormous surface of evaluation commands, and every “at point” variant I could have added would have made that worse. If the existing commands simply resolved the form you meant, newcomers would have had fewer commands to learn, not more.

That was the bet: consistency by redefinition rather than by addition. In hindsight it was the wrong bet for a project this old. Fifteen years in, the existing behaviour isn’t an implementation detail I get to tidy up - it’s the contract. And as it turned out, the redefinition wasn’t nearly as transparent as I’d convinced myself it was.

The feedback

Several users, including CIDER’s co-maintainer Sashko Yakushev, voiced concerns and flagged issues I’d overlooked while playing with this initially. The most important one: inspection is just another flavour of evaluation, and people inspect bare symbols constantly. If it got the same treatment, the disruption would be real enough to run a fork over.

My instinct was that this was an edge case, so I measured it instead of arguing. Across every cursor position in a buffer, only three rules actually differ, and the flagship flow - type a form, hit C-x C-e - is identical under both. Two of those three were fine. The third was this one:

Cursor on a closing paren: the classic rules evaluate the last form inside, smart targeting evaluates the whole enclosing call

The cursor doesn’t move between those two evaluations. That’s the same position, twice, and the answers differ - "b" under the classic rules, "ab" under the new ones.

I’d filed “cursor on a closing paren” as an oddity nobody hits deliberately. But think about when you land there: you finish typing the last thing inside a form, and the cursor is now sitting on the ). For someone inspecting symbols all day, that fires constantly. And the classic answer isn’t arbitrary either - as Sashko put it, the rule of thumb is “whatever paredit-backward jumps back to”, which is a better description of the tradition than anything I’d written down.

Why the tradition exists in the first place

Here’s the part I under-weighted, and it’s worth spelling out for anyone who finds “the form before the cursor” arbitrary.

Emacs form navigation overwhelmingly leaves the cursor after a form. C-M-f (forward-sexp) moves over the next form and stops just past its closing delimiter. C-M-e (end-of-defun) leaves you after the whole top-level form. C-M-n (forward-list) does the same for the enclosing list. Paredit’s paredit-forward behaves the same way, and so does typing: finish a form and the cursor is, by definition, right after it.

So “evaluate the preceding form” isn’t a quirk - it composes with how you already move around. Navigate forward over a form, evaluate it. Type a form, evaluate it. The cursor is already in the right place, every time.

Getting onto a form instead takes deliberate effort: C-M-b (backward-sexp), C-M-a (beginning-of-defun), paredit-backward, or a jump package like avy. All perfectly good tools, but you have to reach for them - unless you’re clicking around with a mouse, in which case the cursor lands wherever you pointed and “the form before the cursor” genuinely is useless. Which, I suspect, is exactly the workflow difference behind this whole argument.

What CIDER got instead

The half of the idea that was never controversial is still there, as commands you opt into rather than a new meaning for keys you already use. Every operation now has an “at point” variant, not just eval and tap: cider-inspect-sexp-at-point, cider-pprint-eval-sexp-at-point, cider-macroexpand-1-at-point, cider-macroexpand-all-at-point, cider-format-edn-sexp-at-point, cider-insert-sexp-at-point-in-repl.

Three ways to say which form you mean, and now every command supports all of them:

The same expression evaluated three ways: the preceding form, the form at the cursor, and the enclosing top-level form

The at-point commands fall back to the preceding form when there’s nothing to point at, so they’re drop-in replacements rather than a separate mode of working. Which means anyone who wanted smart targeting can simply have it:

(with-eval-after-load 'cider-mode
  (define-key cider-mode-map (kbd "C-x C-e") #'cider-eval-sexp-at-point)
  (define-key cider-mode-map (kbd "C-c C-e") #'cider-eval-sexp-at-point))

Two lines, no hidden mode, and everyone else’s fingers keep working. This is what I should have shipped in the first place, and it’s what the manual now recommends. Yes, it’s more commands than I wanted. It’s also the version that doesn’t break anyone.

Macroexpansion is the one place a bit of cleverness survived on its own merits, because an expansion needs a call form. Stand on a bare symbol and cider-macroexpand-1-at-point widens to the call around it, since expanding a lone symbol is never what anyone meant.

The bug at the bottom of the hole

While unifying the plumbing I found this, which is much worse than anything form targeting was ever guilty of. Put the cursor at the end of a comment:

(defn foo [])
;; a comment|

CIDER answered comment. Not the (comment ...) form - the word, lifted out of your prose, because sexp motion has no notion of comments once the cursor is inside one and happily reads the words as symbols.

For evaluation that produced a puzzling error. For the in-place macroexpansion commands, which replace the region they resolved, it did this:

;; a comment          ->   ;; a EXPANDED<comment>
(+ 1 2) ; hey         ->   (+ 1 2)          ; EXPANDED<hey>

It rewrote the comment. That bug has been in CIDER for years, and I only found it because I went looking at the primitives while cleaning up after myself.

Then I checked the neighbours, and this is my favourite part of the whole episode: SLIME, SLY and Emacs Lisp itself all still do this. Both Lisp environments use a bare backward-sexp, and if you put the cursor after (+ 1 2) ; hey in any Emacs Lisp buffer and ask for the preceding sexp, you get hey. CIDER now steps out of the comment first, which as far as I can tell makes it the only one of the family that gets this right.1

One idea worth stealing

The same survey turned up something CIDER was missing. SLY briefly flashes the region it compiled, so you see what it acted on. That’s a direct answer to the confusion this whole saga was about - “which form did it just take?” - and it changes nothing about what gets taken.

(setq cider-flash-evaluated-region t)

Off by default, because I’ve learned my lesson about switching things on for everyone. But if the targeting rules ever puzzle you, turn it on for a day.

The moral

In the original post I described cider-form-targeting, the escape hatch back to the classic rules, as “living on borrowed time” - clutter left over from a plan I’d since reversed, which I was itching to delete.

That option is the only reason the conversation stayed a conversation. Its existence made the objection “please don’t remove the fallback” rather than “I’m forking CIDER”, and I very nearly removed it before anyone had tried the change.

Every mistake is a learning experience for me, and this one was cheap: nothing had shipped, so the whole thing cost a few days and some rewriting. The testing and feedback cycle worked exactly as it should have - people tried something on master, told me plainly what was wrong with it, and the result is better than either what I proposed or what we had before. Everyone gets the behaviour they want, and CIDER lost a text-eating bug on the way.

Thanks to everyone who took the time to tell me I was wrong.

That’s all I have for you today. Keep hacking!

  1. If you’re an Emacs maintainer reading this: elisp--preceding-sexp has the same behaviour, and I’d be happy to be told why it’s intentional. 

-1:-- Smarter Form Targeting Is Not Coming to CIDER (Post Meta Redux)--L0--C0--2026-08-29T06:37:00.000Z

Protesilaos: Emacs: completion-preview-mode and the Completions buffer (Emacs 31)

Raw link: https://www.youtube.com/watch?v=8V4ZyEL_i-s

The built-in completion-preview-mode lets us expand what we type in a minimalist, distraction-free way. It can also be combined with the default Completions buffer to show all the available options. The Completions buffer has several nice features, starting with Emacs 31, which make this an excellent combination that does not require any third party packages (though packages can still augment the overall experience). I also mention the Emacs 31 ‘newcomers-presets’ theme, which configures some of these options as well: it is a great way to start your Emacs configuration.

Sample configuration

(use-package completion-preview
  :ensure nil
  :demand t
  :bind
  ( :map completion-preview-active-mode-map
    ("M-i" . completion-preview-insert-word)
    ("M-n" . completion-preview-next-candidate)
    ("M-p" . completion-preview-prev-candidate)
    ("M-<return>" . completion-preview-insert)
    ;; With TAB we effectively defer to the *Completions* buffer to
    ;; show more completion candidates at once.
    ("<tab>" . completion-preview-complete))
  :config
  (setq completion-preview-minimum-symbol-length 2)
  (with-eval-after-load 'org
    (add-to-list 'completion-preview-commands #'org-self-insert-command))
  (global-completion-preview-mode 1))

;; I have a more detailed configuration (and explanation) that works
;; with Emacs 31 built-in completion capabilities:
;; https://protesilaos.com/codelog/2026-07-29-emacs-default-minibuffer-completion-overview/.
(use-package minibuffer
  :ensure nil
  :demand t
  :bind
  ( :map completion-in-region-mode-map
    ("M-i" . minibuffer-choose-completion)
    ("M-n" . minibuffer-next-completion)
    ("M-p" . minibuffer-previous-completion))
  :config
  (setq completions-format 'one-column)
  (setq completions-max-height 12)
  (setq completion-auto-help t)
  (setq completion-auto-select nil)
  (setq minibuffer-visible-completions t)
  (setq completion-eager-update t))

Finally, the newcomers-presets theme is an excellent way to start your configuration. It is available as part of Emacs 31. Put this at the top of your init.el:

(load-theme 'newcomers-presets)
-1:-- Emacs: completion-preview-mode and the Completions buffer (Emacs 31) (Post Protesilaos)--L0--C0--2026-08-29T00:00:00.000Z

James Cherti: Measuring Emacs startup time more accurately than the built-in emacs-init-time

As an Emacs configuration grows, startup time can gradually increase. Measuring that increase accurately makes it easier to identify regressions. The majority of Emacs users use emacs-init-time. However, this built-in function does not measure all the work performed during startup.

Emacs startup proceeds through several stages: it loads the early init and regular init files, processes command-line options, sets after-init-time, runs emacs-startup-hook, performs additional initial-frame setup such as applying frame parameters from the configuration, and then runs window-setup-hook after the initial frame parameters have been configured. This makes window-setup-hook a useful place to record a broader startup-time measurement.

For a more accurate startup-time measurement, add the following Emacs Lisp code to early-init.el or init.el:

;; Emacs startup proceeds through several stages: it loads the early init and
;; regular init files, processes command-line options, sets after-init-time,
;; runs emacs-startup-hook, performs additional initial-frame setup such as
;; applying frame parameters from the configuration, and then runs
;; window-setup-hook after the initial frame parameters have been configured.
;; This makes window-setup-hook a useful place to record a broader startup-time
;; measurement.
;;
;; URL: https://www.jamescherti.com/measuring-emacs-startup-time/
(defvar my-recorded-startup-time-message nil
  "Stores the formatted string of the Emacs startup metrics.")

(defun my-record-startup-time ()
  "Calculate and record the elapsed startup time.
This function records the time when `window-setup-hook' runs."
  (setq my-recorded-startup-time-message
        (format "Emacs loaded in %.2f seconds (Init time: %.2fs) with %d garbage collections."
                (float-time (time-since before-init-time))
                (float-time (time-subtract after-init-time before-init-time))
                gcs-done))
  ;; Output to the *Messages* buffer during the initial launch
  (message "%s" my-recorded-startup-time-message))

(defun my-display-startup-time ()
  "Display the previously recorded Emacs startup time in the echo area."
  (interactive)
  (if my-recorded-startup-time-message
      (message "%s" my-recorded-startup-time-message)
    (message "Startup time was not recorded.")))

;; Read startup summary:
;; https://www.gnu.org/software/emacs/manual/html_node/elisp/Startup-Summary.html
(add-hook 'window-setup-hook #'my-record-startup-time 99)

This provides a broader measurement than M-x emacs-init-time because it continues measuring after after-init-time has been set, including subsequent startup processing through window-setup-hook.

A depth of 99 places the function near the end of window-setup-hook. This makes the measurement less likely to omit work performed by other functions on the same hook.

The variable gcs-done tracks the total number of garbage collections during the session. Because this function formats and saves the message string during window-setup-hook, it locks in the exact number of garbage collections triggered during the startup phase, giving you a metric of memory allocation bottlenecks during initialization. Ideally, it should be 0 or 1.

Capturing the time the UI finishes rendering provides a reliable metric to track down performance regressions. Once Emacs has finished loading, you can retrieve the recorded startup time at any point during your session. Execute M-x my-display-startup-time to print the exact initialization metrics in the echo area. This interactive command ensures the startup data remains accessible even if the initial message is cleared from the screen by subsequent buffer activity.

-1:-- Measuring Emacs startup time more accurately than the built-in emacs-init-time (Post James Cherti)--L0--C0--2026-08-28T14:38:01.000Z

Andros Fenollosa: How I organize my Emacs configuration

I am not going to share my Emacs configuration, but the way I keep it organized. I do not think I am discovering anything new, but it might be useful for someone who is starting to organize their configuration and does not know where to begin.

I do not use any framework (neither Doom nor Spacemacs) nor the literate approach of Org mode. Everything is vanilla Emacs and each file is an area of responsibility, while init.el acts as the index.

Why vanilla and not a framework? Frameworks decide for you. They lend you a huge configuration that you do not understand and that is hard to tweak. For me, the literate approach of Org is one more layer where something can break. I prefer the simplicity of the file I edit being the file that runs. The less magic, the fewer things that break.

So, inside my configuration folder (~/.emacs.d/), I have the following files:

  • "init.el": the index of my configuration. It loads the rest of the files in order.
  • "core.el": base Emacs behavior, such as handling backups, paths, clipboard, etc.
  • "functions.el": my personal scripts and macros.
  • "packages.el": repository configuration, such as MELPA (among others).
  • "ui.el": everything related to visual aspects: theme, modeline, typography, visual behavior, etc.
  • "ide.el": LSP, debugger, linters and programming configurations.
  • "plugins/init.el": third-party packages, each with its own configuration.

An example of a block you might find inside plugins/init.el:

;; ===
;; vterm-editor
;; Compose text in a full Emacs buffer and send it to vterm with a single keystroke.
;; Docs: https://git.andros.dev/andros/vterm-editor.el
;; ===
(use-package vterm-editor
  :ensure t
  :vc (:url "https://git.andros.dev/andros/vterm-editor.el")
  :after vterm
  :bind (:map vterm-mode-map
         ("C-c e" . vterm-editor-open)))

Each element is isolated in its own context. Using use-package lets me have an isolated and organized configuration block for each package.

I also have other satellite files for third-party packages, such as "plugins/feeds.el" for my RSS subscriptions, "plugins/erc.el" for IRC, "plugins/mu4e-config.el" for email, "plugins/gnus.el", etc. When they grow large and specific, I split them into individual files.

And why break it into areas instead of a single giant init.el? Because I want to know where to look without stopping to think. The brain appreciates each thing having its place, especially when you come back to it after months. Besides, each file is a piece I understand fully, because I wrote it myself. Over time your configuration stops being an editor and becomes a workstation tailored to you. It is my interface with the machine, and it should be as finely tuned as possible.

I am not saying this is the best way, but it makes it easy for me to know where to look and where to put things. Simplicity is also a feature.


Help me keep writing Every coffee gives me a push toward the next article. Sure, it's on me!

Send an email to comment+article-dc7db35a@andros.dev to leave a comment. The subject will be ignored.

-1:-- How I organize my Emacs configuration (Post Andros Fenollosa)--L0--C0--2026-08-28T13:28:41.000Z

Chris Maiorana: Don’t go bankrupt, go local

I saw an amusing Reddit post recently with an Emacs user thinking about declaring “Emacs bankruptcy” with a config file falling in the 800-900 line range.

Many users in the replies were happy to point out that even 10-12K lines is nothing if you understand what’s going on, and it’s well-documented.

This reminded me of something I’ve been experimenting with lately. Instead of updating my config, I’m trying out directory-local variables per project.

Having local configurations in a project avoids the extra cogitation of worrying about conflicts with other file types or projects down the line. I like this. Also makes settings easier to transfer to a similar project in the future.

Table of Contents

Keeping it local

The bedrock of my init file has not changed much over the years. I don’t even use a theme for very long before succumbing to the default colors. Most of the time the editing I do in the config involves tweaking wrap and fill settings or adjusting export functions for different writing projects.

I’ve made use of various hooks that trigger slightly different settings for writing and programming modes. But now I’m keeping it local.

Creating a dir-locals.el file right in your project’s working directory lets you set local variables and custom functions that will apply to all files in that directory tree but nowhere else in Emacs.

I’ve found this incredibly helpful for managing compilation settings for different writing projects with slightly different deliverable formats. Instead of configuring multiple export options as needed, I can just edit that build file once per project.

Evaluate on entry – juggle settings with ease

The “dir-locals” file evaluates when you open that project directory. So if you don’t visit that directory today, none of those settings will take effect.

I like to keep to keep my init file pretty lean anyway. I have a habit of throwing things in as a trial and then letting them linger past usefulness.

The nice thing about having these directory-local functions is that they stay with the project forever, and I can tweak and adjust without having to touch my minimalist config.

Over the years I’ve had to oscillate needlessly between hard vs soft line wrapping. In some cases I needed soft wrapping in org documents used for my blog, but preferred hard wrapping in plain .txt files. Likewise, I might prefer to have Olivetti mode ON for a fiction project, but OFF for a technical writing project.

These minor problems no longer vex me. With directory local, I can easily have one project with auto-fill-mode set at 60 characters per line and jump right to a project with visual-line-mode soft-wrapping with no sweat.

Put the right stuff where it needs to go

In most cases the big, unmovable landmasses of your editor configuration will go in your init file. But directory-local is there for settings that should only apply to that particular project.

The separation might look something like this:

Init file Directory local
Theme, font, style Compilation/build settings
Modeline Linters
Editor defaults (auto save, backups) Fill-column or wrap settings
Org agenda stuff Indentation styles

I like having this directory-local option in the back pocket so I can focus on one project at a time. Also, I think it’s nice to have a quick and dirty way to try a certain variable on before blasting it out globally.


Update on the hacker novel beta reading call: big BIG THANK YOU to everyone who reached out and offered their time and talent. I have received some great feedback and still waiting on some more. Then it’s off to a final draft run.

Here are some other nice items for you to check out:

As always, thanks for reading, see you next time.

The post Don’t go bankrupt, go local appeared first on Chris Maiorana.

-1:-- Don’t go bankrupt, go local (Post Chris Maiorana)--L0--C0--2026-08-27T19:32:08.000Z

Charlie Holland: Hyperbole HyRolo: Search, Retrieve and Insert Records, Not Lines

1. TLDR

hyperbole-hyrolo-banner.webp

HyRolo, the full-text search layer of GNU Hyperbole, is a grep-like retrieval tool for records in your knowledge base instead of lines. Rather than retrieving single lines, it retrieves the full hierarchical record surrounding your match, descendants included.

HyRolo can search your existing knowledge bases in place (org-roam, Denote, Obsidian, Logseq, etc.), even if you use more than one of these. You can point hyrolo-file-list at any container of files (directories and wildcards) containing text in any of the following formats: Org, Markdown, Koutline (Hyperbole's own outline format), or Emacs outline format. Commands like hyrolo-grep will then display every matching record for your search query in a single navigable *HyRolo* buffer, which you can treat like an outline.

HyRolo uses full-text search to find any matching terms across your whole knowledge graph, largely eliminating the need for any graphical views of the connections. Similarly, it eliminates or reduces the need for backlink commands since it finds all references in each search.

In this post we give a better idea of what HyRolo really is and explain why it is a uniquely powerful search and retrieval tool in Emacs. We then demonstrate its use on my own highly fragmented, format-agnostic knowledge base. You will see Hyperbole's philosophy in action throughout this post: HyRolo adheres to the same principles of incremental adoption and in-place data management that make its implicit buttons and HyWiki so useful.

2. About   emacs hyperbole hyrolo knowledgeManagement

This is the third post in my series on Hyperbole. The first post introduced HyWiki, the zero-markup personal wiki, and the second covered implicit buttons, the pattern recognizers that turn your Emacs into a navigable hyperverse.

Those two posts were about traversal, using Hyperbole to navigate the connective tissue that already exists in your free text. This post is about retrieval, the complementary capability of a knowledge/information management system.

I should contextualize my requirements for a retrieval system with an explanation of what my knowledge base looks like:

  • My notes are highly fragmented. My idea of a 'knowledge base' is more abstract than most, as I don't have a single directory into which I deposit my notes. Instead, I consider any information-bearing piece of text on my file system as a potential component of my overall knowledge base. In that way, my notes are spread across local files, a synced notes directory, Dropbox directories, my blog, and the various READMEs within repositories and projects that I might install on my machine. A knowledge management system must be able to search, edit and retrieve information from all of these sources.
  • The notes I write myself and the notes I read from others are ubiquitously organized in hierarchical outline format, and oftentimes, the length of a line in these documents is quite small (typically <80 characters) as most writers wrap their text for ergonomics. Oftentimes, the 'thing' I'm searching for appears in an awkward place in these ~80 character chunks. A knowledge management system needs to analyze the associated context here to retrieve the proper records.
  • My notes are also written in a variety of file formats, including Org, Markdown, and Koutline. Based on the above requirement, I can't control the format in which the 'records' in my notes get stored. I mostly use Org, but most people write README documents in Markdown form, and I share files with folks who like to use Koutline. I need a largely file-format agnostic retrieval mechanism that recognizes the hierarchical structure across all these outline formats.
  • I don't manually link notes together, with extremely rare exceptions. I mostly write in free text, and I avoid the bookkeeping of connecting notes that other PKMSs (like org-roam, Logseq, Obsidian, etc.) typically prescribe. A good knowledge management system should let me dynamically see the associations I want.

So when I'm searching and retrieving information for a given 'thing' from this multi-format, fragmented expanse of hierarchical notes files, how can I collect everything my system knows about it into one navigable place? This is what I use HyRolo for; it satisfies the search and retrieval requirements of my PKMS-agnostic setup: there is no notes silo, no prescribed file or data format, and no bookkeeping asked of me to enable traversal, or in HyRolo's case, retrieval.

Before HyRolo, I was using consult-ripgrep and embark-export to accomplish some form of record retrieval, but in this Consult+Embark use case the 'records' lack context. They are too fragmented, given the line-matching nature of grep. HyRolo goes one step further and retrieves the tree context around the text I'm searching for, and can retrieve from any location in-place that I specify in my hyrolo-file-list. That's the special trick for me. In this way, HyRolo lets me see the forest for the trees.

3. Point It at Anything   hyrolo knowledgeManagement

Like HyWiki, HyRolo refuses to lock you into a siloed notes vault or directory.

Retrieval scope is defined by one variable, hyrolo-file-list, which accepts individual files, whole directories, and wildcard patterns:

(setq hyrolo-file-list
      '("~/.rolo.org"             ;; the classic: personal contacts
        "~/org/"                  ;; all standard Org files
        "~/org-roam/"             ;; all standard org-roam files
        "~/Documents/notes/"      ;; all standard denote files
        "~/vault/*.md"            ;; an Obsidian vault, via wildcard
        "~/projects/*.kotl"))     ;; Koutliner project files

Although this list seems simple, I hope you recognize HyRolo's two game-changers:

  1. Look at the middle entries: those are the stock locations for org-roam, Denote, and an Obsidian vault, and each of those tools ships its own siloed search over its own directory. You can continue using those if you like, and HyRolo gives you a single, unified retrieval scope across all of them. That is a major deal for me, as I not only use multiple PKMSs, but also want to include the READMEs scattered through my file system in my retrieval scope.
  2. HyRolo does not prescribe a file type. It searches any hierarchically organized text it recognizes, currently the four formats: Org, Markdown1, Hyperbole's own Koutline, and classic Emacs outline files (.otl).

Beyond files, HyRolo can also fold your Google Contacts and BBDB records into the same searches2. There is also a standalone Python-based command-line version, hyrolo.py, that lets you specify both the search term and the paths to search on the command line. It ships with Hyperbole for retrieval outside Emacs3.

I want to belabor the point here because it is really important. Your Obsidian vault, your org-roam directory, a Denote folder, and a stray project README are already valid HyRolo sources, simultaneously, with zero migration or symlinking required to unify them under a search and retrieval interface. Where other tools ask you to migrate your notes into their containers before they will search them, HyRolo adds a retrieval layer over the containers you already have. This is the same philosophy that makes HyWiki a tool for sharing and traversing knowledge, applied here to the search and retrieval use case. There is an 'anywhere, anything' mindset to these tools that makes them unique in the knowledge management space.

4. Records as Trees, Not Lines   hyrolo retrieval

HyRolo's retrieval features are unique in their treatment of what your query matches.

Think about a plain grep. When grep (or consult-grep, or any of their descendants) finds a match for your query, it outputs the matching line, perhaps with a few lines of mechanical context via -A and -B flags. That context is measured in lines, but lines are not the unit in which you store meaning.

HyRolo instead matches on the entity that does store the meaning, treating your files as sets of hierarchical records when it retrieves them. As the HyRolo documentation mentions:

Any search done on a [HyRolo file] scans the full text of each entry. Whenever an entry matches, it and all of its descendant entries are retrieved.

This Hyperbole manual example makes the consequence clear. Given these entries:

*    Company
**     Manager
***      Staffer

searching for Company retrieves all listed employees, and searching for Manager turns up all Staffer entries. Context is important, and the hierarchy of data beneath the record is the context. When your query matches data in a record, it displays the record's heading, its body, and its descendants in the *HyRolo* match buffer with each match term highlighted.

Here's a clear distinction between the line-matching and tree-matching approaches. With simple grep (here consult-ripgrep for "mitochondria" followed by embark-collect), we get single line matches for each term.

hyrolo-fig1-records-vs-lines1.webp

Figure 1: grep returns matching lines

With HyRolo, we see the full context (the tree) around each match.

hyrolo-fig1-records-vs-lines2.webp

Figure 2: hyrolo-grep returns the full hierarchical records around each match

The *HyRolo* match buffer is itself a first-class, navigable outline. You can jump between matches, fold and unfold entries, and get an overview of just the headings. And because this is Hyperbole, the Action Key works in the *HyRolo* buffer. Press M-RET on any retrieved entry and it is displayed for editing in its source file buffer.

5. The Search Commands   hyrolo retrieval

All of the retrieval commands live on the Rolo menu (C-h h r), or can be called directly:

Command Key Finds entries containing…
hyrolo-fgrep C-h h r s a string, or a boolean match expression
hyrolo-grep C-h h r r a regular expression
hyrolo-word C-h h r w whole-word matches only
hyrolo-tags-view C-h h r t matching Org tags across your rolo files

The last letter of each key selects the item from the Rolo menu by its label's initial: S​tringFind, R​egexFind, W​ordFind, and T​agFind.

hyrolo-word matches whole words, so searching for product won't drown you in mentions of production. And hyrolo-fgrep accepts full Lisp-like boolean expressions with and, or, xor, and not:

(and postgres (not migration))

The above query with hyrolo-fgrep retrieves every record that mentions postgres but not migration, across every file in your list. This can be a useful query language over your entire note corpus.

6. A Knowledge Graph Without the Graph   hyrolo knowledgeManagement

This is how I use HyRolo most. This is also how I think about HyRolo abstractly: as a more practical alternative for knowledge-graph-style tasks.

Some quick terminology first: knowledge graphs are graphs in the data-structure sense, and they show up as ball-and-stick models. The balls are the 'nodes' and the sticks are the 'edges'. It's a simple yet powerful model, and most PKMSs use it.

The canonical knowledge-graph question: "show me everything connected to X". What's the answer?

Graph-oriented PKMSs provide this answer with a backlinks pane or a graph view, but only if you did the up-front manual labor of hand-authoring every 'node' as a hard-coded link, inside the tool's own vault. This answer is also quite sparse, usually just a list of node names or file names, stripped of the surrounding prose that actually defines the connection. I think of that surrounding prose as the 'edge type', an indispensable part of the knowledge graph.

hyrolo-grep answers the question with more detail and fewer requirements. Searching for a term retrieves every record it occurs in, across every file in hyrolo-file-list, into one *HyRolo* buffer, with the meaningful surrounding context right there. In this way, I get every piece of the knowledge graph I need (the node is the ad hoc input query, the connected nodes are the records in the *HyRolo* buffer, and the edge type is the surrounding prose in the record).

You can think of the *HyRolo* buffer as a sophisticated (but more practical) backlinks buffer, with the added benefit that you never need to hardcode the nodes or edges, as HyRolo displays these on the fly based on your input query. To emphasize, HyRolo can materialize "backlinks" for anything that can be matched with a regular expression.

This is the Hyperbole philosophy showing up again with HyRolo, and I'm glad to keep highlighting it in this series. Other systems make you author relationships before you can use them, whereas Hyperbole infers relationships when you ask for them. Implicit buttons infer them at the point of traversal, and HyRolo infers them at the point of retrieval. With HyRolo, mentioning a term in a note is the linking.

hyrolo-fig4-buffer.webp

Figure 3: The *HyRolo* buffer behaves like a knowledge graph

7. Yes, the Name Means Rolodex   hyrolo

The name HyRolo is important to address because it can be somewhat misleading.

HyRolo was originally written as a digital Rolodex™ for managing contact records, and it still supports that quite well. The default ~/.rolo.org file, automatic datestamps, alphabetized insertion, and a hyrolo-mail-to command all remain as features from when HyRolo functioned primarily as a digital contact management tool.

But it's important to emphasize here that contact management is just one application of HyRolo. It is a generic tool. The Hyperbole manual makes this clear:

Hyperbole includes HyRolo, a complete, advanced system for convenient management of hierarchical, record-oriented information. Most often this is used for contact management but it can quickly be adapted to most any record-oriented lookup task requiring fast retrieval. For example, you can look up glossary entries listed in Org file headlines or legal-numbered requirements from a Koutline file.

If you replace the word "contact" with "record", you get a better sense of HyRolo's reach. It is a generic hierarchical record retriever for any outline-structured text. A record is simply a heading plus its descendants (body text and recursive subsections).

Consider what instances of data can fit into this flexible type 'record':

  • Notes
  • READMEs
  • Project logs
  • Architecture Decision Records (ADRs)
  • Glossary entries
  • Recipes
  • And, of course, contacts

These are all naturally hierarchical records. The vast majority of the Emacs community uses org-mode, and so the culture of writing everything down in record form is baked deeply into the philosophy of the modern Emacs user. You may use Markdown over org-mode, and HyRolo supports that as well. In a flexible, generic way, it can retrieve records from any hierarchical format.

8. Consult Previews, Embark Comparisons   hyrolo consult

Usefully, HyRolo integrates with consult. hyrolo-consult-grep (and hyrolo-consult-fgrep) run your search through consult's live-updating minibuffer, so you can preview matches in place and refine your query before displaying a *HyRolo* buffer. Although the consult buffer highlights one selected candidate, all remaining candidates and their associated records show up in the Hyperbole *HyRolo* match buffer when you press RET.

hyrolo-fig3-consult.webp

Figure 4: hyrolo-consult-grep: previewing matches through consult's live minibuffer before committing to a materialized *HyRolo* buffer

For embark users, here is the mental model I'd offer: hyrolo-grep feels like a consult-grep followed by an embark-export, giving you a persistent, navigable buffer of results. The key difference is that HyRolo is aware of hierarchical context. An embark export gives you a grep-mode buffer of matching lines, while HyRolo materializes the full records around the matches, nested structure intact, foldable as an outline, in their original formats. This doesn't obviate embark-export or embark-collect, by the way. I still use both frequently (for example, I use Embark for interactive 'find and replace' via consult-ripgrep -> embark-export -> wgrep-change-to-wgrep-mode).

9. Try It   hyperbole

The install snippet from the first post is all the setup you need. Then tell HyRolo where your records live:

(setq hyrolo-file-list '("~/.rolo.org" "~/notes/" "~/org/*.org"))

Run M-x hyrolo-grep (or explore the Rolo menu with C-h h r ?) and search for a term you know is scattered across your notes (I bet "Emacs" would turn up a ton of matching records!). Fold the results, jump to a source with M-RET, then try a boolean query with hyrolo-fgrep.

10. Further Reading

The HyRolo chapter of the Hyperbole manual covers the concepts, menu, search commands, keys, and settings in full. The first two posts in this series, on HyWiki and implicit buttons, cover the traversal side of the hyperverse that HyRolo's retrieval completes.

Footnotes:

1

HyRolo recognizes the full family of Markdown suffixes: .md, .markdown, .mkd, .mdown, .mkdn, and .mdwn (see hyrolo-file-suffix-regexp in hyrolo.el).

2

Google Contacts are searched on each query when the google-contacts package is loaded (controlled by hyrolo-google-contacts-flag), and BBDB databases are searchable via hyrolo-bbdb-grep and hyrolo-bbdb-fgrep.

3

hyrolo.py ships in the Hyperbole package directory and provides a command-line version of HyRolo search, useful for scripting retrieval outside of Emacs.

-1:-- Hyperbole HyRolo: Search, Retrieve and Insert Records, Not Lines (Post Charlie Holland)--L0--C0--2026-08-27T16:44:42.000Z

James Cherti: Eglot for Python Development in Emacs: Integrating python-lsp-server (pylsp) with Linters and Formatters

Eglot provides built-in LSP support in modern Emacs, giving developers a native interface for autocompletion, linting, and formatting. When paired with python-lsp-server (pylsp), creating a Python development environment comes down to managing the LSP server configuration properly.

Historically, setting up this toolchain required wiring together a stack of individual utilities such as flake8 and isort. Now, ruff stands entirely apart from that legacy ecosystem by consolidating all of those separate checks into a single, high-performance Rust binary. This article demonstrates how to configure Eglot to detect and use Ruff dynamically when it is present on your system, while retaining a clean fallback to the older stack of tools like Flake8, pycodestyle, and pydocstyle when Ruff is missing.

The configuration strategy

The configuration provided in this article begins by checking for the presence of ruff and flake8 in your system path. Based on these checks:

  1. If Ruff is installed, the configuration relies on the python-lsp-ruff plugin. Ruff consolidates linting, formatting, and import sorting.
  2. If Ruff is missing but Flake8 is present, Flake8 takes over linting duties alongside Pylint.
  3. If neither is installed, the system falls back to the individual pylsp plugins (pyflakes, pycodestyle, mccabe, etc.).

Dependencies

The dependencies can be installed locally pip or directly via your system's package manager.

The complete configuration

Below is the complete Emacs Lisp code to achieve this dynamic configuration. You can place this snippet in one of your init files:

;; Use ruff when it is available because it is fast (written in Rust). When ruff
;; is not available, fall back to flake8 and its individual underlying tools.
;;
;; URL: https://www.jamescherti.com/emacs-python-dev-using-eglot-pylsp-ruff-pylint-flake8/
;;
;; To sup up:
;; - When ruff is available: Ruff, and Pylint.
;; - When Ruff is not available: Flake8, isort, and Pylint.
;;
;; Documentation:
;; https://github.com/python-lsp/python-lsp-server/blob/develop/CONFIGURATION.md
;; https://github.com/python-lsp/python-lsp-ruff
;; https://github.com/chantera/python-lsp-isort
(with-eval-after-load 'eglot
  (let* ((has-ruff (executable-find "ruff"))
         (has-flake8 (executable-find "flake8")))
    ;; Target ONLY the 'pylsp key in the global configuration alist safely
    (setf (alist-get 'pylsp (default-value 'eglot-workspace-configuration))
          `(:pylsp
            (:plugins
             (;; Plugin: https://github.com/python-lsp/python-lsp-ruff
              :ruff (;; Ruff configuration
                     :enabled ,(if has-ruff t :json-false)

                     :formatEnabled ,(if has-ruff t :json-false)

                     ;; Add 'W' (pycodestyle warnings), 'UP' (pyupgrade),
                     ;; and 'D' (pydocstyle).
                     :extendSelect ["W" "UP" "D"]

                     ;; Ignore specific rules
                     ;;   D213: Multi-line docstring summary should start on
                     ;;         the second line.
                     ;;   D202: No blank lines allowed after function
                     ;;         docstring.
                     ;; :ignore ["D213" "D202"]
                     )

              ;; Pylint remains enabled regardless of whether Ruff
              ;; or Flake8 is active because it serves
              ;; complementary role.
              :pylint (:enabled t)

              ;; Flake8 is a wrapper tool that bundles pyflakes,
              ;; pycodestyle, and mccabe.
              :flake8 (:enabled ,(if (and (not has-ruff) has-flake8)
                                     t
                                   :json-false))

              ;; When Flake8 or Ruff runs, they execute these under
              ;; the hood. If we enable either, we must explicitly
              ;; disable the individual pylsp plugins for them,
              ;; otherwise the language server will run the exact
              ;; same checks twice and duplicate all editor
              ;; diagnostics.
              :mccabe (:enabled ,(if (or has-ruff has-flake8)
                                     :json-false
                                   t))
              :pyflakes (;; pyflakes catches logical errors
                         ;; (unused imports, undefined names...)
                         :enabled ,(if (or has-ruff has-flake8)
                                       :json-false
                                     t))

              :pycodestyle (;; pycodestyle catches style/formatting
                            ;; violations (PEP 8)
                            :enabled ,(if (or has-ruff has-flake8)
                                          :json-false
                                        t)

                            ;; Ignore specific rules
                            ;; :ignore ["W293"]
                            )

              :pydocstyle (;; pydocstyle enforces PEP 257 docstring
                           ;; conventions
                           :enabled ,(if (or has-ruff has-flake8)
                                         ;; Use flake8-docstrings
                                         ;; https://github.com/pycqa/flake8-docstrings
                                         :json-false
                                       t)

                           ;; Ignore specific rules
                           ;;   D213 Multi-line docstring summary should start on
                           ;;        the second line.
                           ;;   D202 No blank lines allowed after function
                           ;;        docstring.
                           ;; :ignore ["D213" "D202"]
                           )

              ;; Formatting: isort
              ;; https://github.com/chantera/python-lsp-isort
              :isort (:enabled ,(if has-ruff :json-false t))

              ;; Formatting: autopep8
              :autopep8 (:enabled ,(if has-ruff :json-false t))
              :yapf (:enabled :json-false)

              ;; Code completion
              :jedi_completion (;; jedi configuration
                                :enabled t

                                ;; Disable resolving documentation details eagerly
                                ;; :eager t

                                ;; Add class objects as a separate completion item
                                ;; :include_class_objects t

                                ;; Add function objects as a separate completion item
                                ;; :include_function_objects t

                                ;; Auto-complete methods and classes for each parameter
                                ;; :include_params t

                                ;; Fuzzy matching for typos/abbreviations
                                ;; :fuzzy t

                                ;; Modules for which labels and snippets should be cached.
                                ;; :cache_for ["pandas", "numpy", "tensorflow", "matplotlib"]

                                ;; How many labels and snippets should be resolved?
                                ;; :resolve_at_most 25
                                )))))))

This Eglot configuration prioritizes Ruff to handle linting and formatting when it is available in the environment. If Ruff is missing, it falls back to Flake8 and the default pylsp plugins. This keeps Emacs Eglot adaptable across different machines, guaranteeing consistent diagnostics and autocompletion without demanding a strict set of dependencies on every system you use.

-1:-- Eglot for Python Development in Emacs: Integrating python-lsp-server (pylsp) with Linters and Formatters (Post James Cherti)--L0--C0--2026-08-27T16:03:21.000Z

Irreal: Tweaking Emacs Scrolling Behavior

For me, one of the most difficult Emacs behaviors—or at least “normal” Emacs behaviors—to understand is scrolling. The default behavior is so unintuitive and jarring that it kept me from embracing Emacs for a long time. It was only after I took the plunge that I discovered that that behavior, like everything else in Emacs, is configurable.

That was almost 20 years ago when I was just starting and knew next to nothing about Emacs configuration but I was able find the solution on the Internet. Here’s what I’ve had in my init.el almost from the beginning:

(setq    ;set reasonable scrolling
 scroll-margin 0
 scroll-conservatively 100000
 scroll-preserve-screen-position 1)

Back then, I had no idea what any of it meant, only that it made Emacs scrolling rational by my standards. It’s been sitting there undisturbed ever since.

Now James Cherti has a really excellent post that expains all the fine points of configuring scrolling. He explains the above lines, which most people probably have in their configuration. If you want Emacs scrolling to behave as it does in every other application, you can leave them as is but you can tweak the behavior a bit if you like. Cherti has all the details.

But wait. There’s more. There are similar settings for horizontal scrolling that you might like. I’ve never had any problem with horizontal scrolling—probably because I’ve always wrapped lines in one way or another—but they’re there if you need them.

There are some other tweaks that you can apply to delay fontification during scrolling to speed things up a bit. Again, this is not something that I use but if you deal with large files, you may find it useful.

In Emacs 29 and later you can set pixel-precise scrolling to make scrolling over images more pleasant. There are a few other tweaks in Cherti’s post so you should definitely take a look at it. There’s a lot of good content and even if you use only a bit of it, you will find his post worthwhile.

-1:-- Tweaking Emacs Scrolling Behavior (Post Irreal)--L0--C0--2026-08-27T14:15:48.000Z

This is now fixed: GCC drive error on macOS: This seems to show up with almost every new Emacs package I’ve been trying recently on my Mac. I remember trying to dig into it, and I went in circles trying to install something from Homebrew. Has anyone fixed this? I’m on GNU Emacs plus 30.2.

A terminal window displays repeated error messages indicating a failure to invoke the GCC driver while compiling with native-compiler in Emacs.
-1:--  (Post TAONAW - Emacs and Org Mode)--L0--C0--2026-08-27T12:51:50.000Z

Rahul Juliato: An unofficial guide to markdown-ts-mode on Emacs 31

Intro

So, Emacs 31 has been released, and a lot of shiny new stuff is there, ready for us to play with.

You probably heard of this new markdown-ts-mode and decided to check it out. And guess what? On Emacs version 31, this is marked as an experimental mode. What does this mean? Should you use it or not? Is this ready? Is this just a sketch of a mode?

Treat this post as a quick guide to getting this mode up and running and helping yourself find answers to these questions.

Where is it in terms of features?

This is an experimental mode, right? You need to opt in, so probably not everything will work flawlessly yet, and it needs more testing and feedback.

That said, don't let this title mislead you. This does not mean the mode is premature in terms of features. As you will see, this is a very feature-rich mode. This mode already covers all of the https://commonmark.org/ spec, as well as most of https://github.github.com/gfm/, with some extras like code blocks even for non-ts-modes, like elisp, table of contents utilities, and interfaces with external converters, such as pandoc and gfm.

Before deep diving into it yourself, you may need some help simply turning this mode on. Tree-sitter is tricky. It might even be your first time with tree-sitter, so a quick "install guide" is on our agenda.

Where is it? Do I need to install the mode?

Experimental means Emacs does not enable the mode by default so it is not there waiting for you to simply open a .md file or call it with M-x markdown-ts-mode RET. You need to load this library.

As always on Emacs, there's more than one way of doing everything, I am a big fan of use-package so I tend to use it to organize my init file. Here is my suggested initial setup:

(use-package markdown-ts-mode
  :ensure nil
  :mode ("\\.md\\'" "\\.mdx\\'" "\\.markdown\\'")
  :config
  (require 'markdown-ts-mode-x))

Or if you keep use-package out of your tool belt:

(autoload 'markdown-ts-mode "markdown-ts-mode" nil t)

(dolist (re '("\\.md\\'" "\\.mdx\\'" "\\.markdown\\'"))
  (add-to-list 'auto-mode-alist (cons re 'markdown-ts-mode)))

(with-eval-after-load 'markdown-ts-mode
  (require 'markdown-ts-mode-x))

Now both the mode and the x (nice extra goodies) libraries are loaded, and you can simply visit your Markdown files using it.

If you want to experiment with it without touching your own configuration, do the following:

  1. Save the above content in a file like testing.el.
  2. Call emacs with emacs -Q --load 'testing.el'.

And there you have it, a bare Emacs session with your testing ground set up. This is what I will use for the rest of this guide.

IMPORTANT: there's NO NEED to download or add this package to your package manager. The (now very old and archived) MELPA Repository will refuse to install on Emacs version 31 onward and is very, very poor in terms of features. If you are using this, you're not using the new built-in markdown-ts-mode. Right? Let's continue.

Opening our first markdown file

In order for you to "see what I see", we need some pictures. If it is the first time you're using a tree-sitter-based mode, let me warn you: although tree-sitter is wonderful, fast, and feature-rich, it comes with its own set of tasks to complete and perhaps debugging skills if it needs help. I will try to cover some here; I will forget others for sure.

For this guide, I will be using this test file.

The repository where it is hosted is our laboratory. No code lives there, remember, all code is in Emacs itself.

Now go ahead and open the test.md file.

IMPORTANT: At this point, many things can happen. If you have the grammar for markdown installed in your system, the file is already opened. You could, though, be prompted, as I am here, with this:

emacs_markdown_31_demo step 01

It means Emacs hasn't found a grammar for markdown in my system, in this case in ~/.emacs.d/tree-sitter/ (which is the default when I start Emacs with emacs -Q ...). Emacs will offer to install it, which means downloading and compiling it from a repository already defined in markdown-ts-mode's source code. Let's install it with y. Emacs will clone the grammar repository, compile it, and continue to the second grammar. Yes, markdown uses two grammars: the main one and one for inline parsing. I will allow Emacs to install the second one with y.

Success!

What you should be seeing:

emacs_markdown_31_demo step 02

If not, here is what you should check if something went wrong:

  1. Is Emacs compiled with the tree-sitter flag? Use M-: (featurep 'treesit) RET and check if it returns t.

  2. Do you have the tooling used for "compiling" grammars, like make, gcc, and others?

  3. Tree-sitter needs a package in your distro, usually named tree-sitter-cli which provides a tree-sitter binary, you can check you have it with tree-sitter --version.

This is a common headache for all tree-sitter modes. Many people like NOT to compile their own grammars, but instead use some compiled file from a place they trust, like their own distro repository, or packages with hundreds of pre-compiled grammars. I will not dive into it; there are many ways of acquiring grammars, and I will stick with "build it yourself" for this guide.

See, I kind of tricked you there. I told you that you should be seeing that, but actually, the "do you see what I see" should look like this:

emacs_markdown_31_demo step 03

We provide the full file in here, with several default themes so you can compare whether your setup is complete.

So, what happened?

This is part of the reason markdown-ts-mode is very special.

This mode can work not only with markdown, but with all other -ts-modes available! Keep this in mind; we will talk about code blocks in a while. For now, we need to understand a few things.

In your test.md file, we have a special header. It is very common to have toml or yaml as headers of markdown files.

This little guy here:

---
title: The Official 'markdown-ts-mode.el' Feature Test File
author: Rahul Martim Juliato
date: 2026-03-18
version: 0.1.0
parsers needed: markdown, markdown-inline, yaml, toml, html, c, javascript, python, ruby, rust
---

Needs something else to fontify (aka be painted with colors by Emacs). Can you figure out what is missing? If your answer is "we need a grammar for YAML!", kudos!

Whenever something does not fontify correctly in -ts-modes, you're probably missing a grammar. And as markdown-ts-mode is made to work with all available ts-modes, this is no exception.

Let's install our yaml grammar with our trusty M-x treesit-install-language-grammar RET yaml.

You might see now what I am seeing:

emacs_markdown_31_demo step 04

Let's agree to it with y. Hmm, it looks like this time, something went wrong with yaml-ts-mode trying to register its preferred grammar with treesit-install, as there are no suggestions. We could provide it manually. But let's check something first. Taking a look at yaml-ts-mode.el, we can check which grammar it expects in its source code:

;; from yaml-ts-mode.el
(add-to-list
 'treesit-language-source-alist
 '(yaml "https://github.com/tree-sitter-grammars/tree-sitter-yaml"
		:commit "b733d3f5f5005890f324333dd57e1f0badec5c87")
 t)

Awesome! Let's simply evaluate that block and try to install the grammar again. Or manually provide the source https://github.com/tree-sitter-grammars/tree-sitter-yaml to our already-started interactive session, as I did this time:

emacs_markdown_31_demo step 05

We then keep going with the defaults with RET RET RET... until the library is installed.

After that, reload markdown-ts-mode, or use C-x x g, or re-open the file you're visiting.

What we did here by visiting the source code is pretty rare, and most -ts-modes will automatically suggest the repository from which they are going to compile. It was nice that this happened, so I can show you what to do.

Now what? We need to do the same M-x treesit-install-language-grammar for every block without fontification that we encounter. If you'd like, for our test file we could use C-x x f to force fontification and be prompted for every missing grammar used by this file.

By now, you should see the entire document fontified as in here. Same as previous image:

emacs_markdown_31_demo step 03

A note on grammars

A -ts-mode is only as good as the tree-sitter grammar behind it. This means every -ts-mode needs to constantly keep up with improvements to the grammar, which is shared by any editor or program wanting to use tree-sitter to parse the language.

This also means we are, at some point, dependent on the grammar for certain constraints and features. Almost all -ts-mode code in Emacs is filled with notes on limitations and the reasoning behind why and how something obscure is treated the way it is.

Emacs mode authors and maintainers always try to suggest the grammar and the SHA commit the ts-mode is prepared to use, either in comments or in the code inside the mode, which is the same as you saw for the yaml suggestion. Part of maintaining ts-modes is keeping up with newer grammar version changes. We try our best to keep it updated with the latest versions, but the one we tested against and that should work as expected is the one in the source file of the mode.

This is why I think compiling it yourself interactively with Emacs is the best possible way to guarantee a nice experience.

Specifically for markdown-ts-mode, we're using the grammars provided by https://github.com/tree-sitter-grammars/tree-sitter-markdown, as this is the most complete, maintained, and broadly adopted one, both by code editors and programs in general. This doesn't mean it is free of bugs or limitations. Again, we do our best to work around these limitations and even contribute issues to the grammar and to the core tree-sitter library.

I can finally open a markdown file!

Congrats! Now what? How often do I need to do all of this? Only once, the first time you use a -ts-mode, or never if you already have grammars installed by some other method.

Now let's see what markdown-ts-mode already provides.

A quick look at markdown-ts-mode features

We (BTW, this mode is authored by me and Stéphane Marks) provided an easy-menu feature for quick discoverability of functionalities.

You can access it by clicking on Markdown in the mode-line, or, if you have menu-bar-mode enabled, on the menu bar, or even Ctrl + Right click (whatever Emacs maps your OS input to) on a buffer using markdown-ts-mode.

emacs_markdown_31_demo step 06

This is actually this guide's TL;DR, if you want to stop now and explore it yourself (spoilers ahead).

Editing

The fastest way to learn the mode is to type a little of everything. Below is a speed run: what you write, what key does it for you.

Marks (emphasis)

Markdown is plain text, so you can always type the markers yourself:

When you want You write
bold **bold**
bold, alt __bold__
italic *italic*
italic, alt _italic_
bold + italic ***both***
strikethrough ~~gone~~
inline code `code`

Or let the mode do it: C-c C-x C-f (markdown-ts-emphasize) then a single key:

  • b bold, B bold with underscores
  • i italic, I italic with underscores
  • a bold + italic
  • s strikethrough
  • c inline code
  • SPC remove emphasis at point

If a region is active, the formatting wraps the region. With no region, it wraps the word at point, or inserts the pair and drops point in the middle.

emacs_markdown_31_demo step 07

Tip: C-c C-x RET (markdown-ts-toggle-hide-markup) hides the markers themselves, so **bold** shows as bold. Very nice for reading while editing, like default org-mode.

emacs_markdown_31_demo step 08

Another tip: M-q fills correctly even inside lists and quotes.

Headings

Type them: #, ##, ... up to ######. Setext headings (=== and --- underlines) are recognized, too.

Promote and demote without retyping the hashes:

  • M-<left> promote (markdown-ts-promote)
  • M-<right> demote (markdown-ts-demote)

And move a whole section, body and children included:

  • M-<up> (markdown-ts-move-subtree-up)
  • M-<down> (markdown-ts-move-subtree-down)

TAB on a heading cycles its visibility (outline folding). The mode is an outline-minor-mode citizen, so folding just works. S-TAB on a heading will cycle the visibility of all headings.

emacs_markdown_31_demo step 09

IMPORTANT: By now, you can see this mode tries, when possible, to draw parallels with org-mode, so Emacs users used to it can have fewer problems adapting to markdown. If these bindings don't suit you, everything can be customized.

Listings (lists and checkboxes)

Type - item, + item, * item or 1. item.

  • M-RET new list item (markdown-ts-insert-list-item)
  • RET is smart: markdown-ts-newline continues the list for you
  • M-<left> / M-<right> promote/demote the item
  • C-c C-r renumber an ordered list (markdown-ts-renumber-list)
  • C-c C-c toggle a task checkbox (markdown-ts-toggle-checkbox)
  • M-q fills correctly inside an item

Task lists are the GFM ones:

- [ ] not done
- [x] done

Raw mode:

emacs_markdown_31_demo step 10

With markup hidden:

emacs_markdown_31_demo step 11

Note the bullets and boxes you see if you toggled C-c C-x RET are display only. The buffer still holds - and [x]. See markdown-ts-unordered-list-marker, markdown-ts-checked-checkbox and markdown-ts-unchecked-checkbox.

Blocks

C-c C-, (markdown-ts-insert-structure) then one key:

  • ` fenced code block, prompts for the language
  • ~ tilde fenced code block
  • q block quote
  • d divider (thematic break)
  • t table

If a region is active, it wraps the region instead of inserting an empty block.

emacs_markdown_31_demo step 12

With markup hidden:

emacs_markdown_31_demo step 13

Code blocks

This is the party trick. A fenced block tagged with a language is fontified by that language's own mode:

```python
def hello():
	return "world"
```

Missing colors typically means a missing grammar, same story as the yaml header earlier.

Better than colors: put point inside the block and you are in markdown-ts-code-block-in-context-mode (lighter [code] in the mode-line). Inside it:

  • TAB indents like the language does
  • RET newline and indent like the language does
  • M-q fills like the language does
  • M-. jumps to definition via xref

Move to the next/previous blocks with C-c C-v n and C-c C-v p.

Non tree-sitter modes work too, elisp included. Knobs: markdown-ts-code-block-modes, markdown-ts-default-code-block-mode, markdown-ts-fontify-code-blocks-natively.

An example raw:

emacs_markdown_31_demo step 14

With markup hidden:

emacs_markdown_31_demo step 15

Tables

Insert one with C-c C-, t or M-x markdown-ts-table-insert-table, which asks you to specify the number of rows and columns to insert.

| Column 1 | Column 2 |
|----------|:---------|
| a        |        1 |

Inside a table you are in markdown-ts-in-table-mode (lighter [table]) and the keys change:

  • TAB / S-TAB next / previous cell (also formats your table)
  • RET / S-RET next / previous row
  • M-RET insert row below
  • M-<up> / M-<down> move row
  • M-<left> / M-<right> move column
  • M-S-<up> insert row above, M-S-<down> delete row
  • M-S-<right> insert column left, M-S-<left> delete column
  • C-c C-c align the whole table
  • C-c C-t a set column alignment (left, center, right)
  • C-c C-t t transpose the table

Plus, from the menu: clone rows and columns, CSV/TSV import of a region and CSV/TSV export of the table.

emacs_markdown_31_demo step 16

NOTE: There are some limitations when working with tables at the moment, mostly due to how the grammar parses them, so you may bump into unfontified stuff while typing. All valid tables according to the GFM spec should be good to use, though.

Links and images

Links are the usual [text](url) and [text][ref]. Fragment links like [intro](#intro) are clickable and jump to the heading in the buffer, using GitHub style slugs by default.

Images render inline. C-c C-x C-v toggles them (markdown-ts-toggle-inline-images). See markdown-ts-image-max-width and markdown-ts-display-remote-inline-images for how big and whether remote URLs are fetched.

Markdown: emacs_markdown_31_demo step 17

After C-c C-x C-v: emacs_markdown_31_demo step 18

After C-c C-x RET: emacs_markdown_31_demo step 19

Moving around

  • TAB cycle folding at point
  • C-c C-n / C-c C-p next / previous heading
  • C-c C-u up to parent heading
  • C-c C-f / C-c C-b next / previous heading, same level
  • M-x imenu jump to any heading or named code block by completion
  • C-c C-v n / C-c C-v p next / previous code block

markdown-ts-default-folding decides how a file opens: everything shown, or folded.

markdown-ts-view-mode

M-x markdown-ts-view-mode read-only mode with a single key navigation: n, p, u, f, b, TAB. Good for reading a README without fear of typing into it.

emacs_markdown_31_demo step 20

Extras

Everything below lives in markdown-ts-mode-x.el, which is why we loaded it back in the setup.

TOC

A table of contents is delimited by HTML comments, so it survives rendering anywhere:

<!-- markdown-ts-toc: -->
<!-- markdown-ts-toc-end: -->
  • M-x markdown-ts-toc-insert-template inserts those markers, basic or complete (the complete one lists every parameter with its default)
  • M-x markdown-ts-toc-generate fills them in, and refills on every call
  • M-x markdown-ts-toc-clear empties, markdown-ts-toc-clear-and-remove also removes the markers
  • M-x markdown-ts-toc-update-before-save-mode regenerates on save

Parameters go inline in the opening comment: min-depth, max-depth, candidates, from, style, indent, no-link, relative-depth, ignore. A buffer can hold more than one table with different parameters. Candidates are not only headings, list items, setext headers and named code blocks can feed a table too.

Raw:

emacs_markdown_31_demo step 21

With markup hidden:

emacs_markdown_31_demo step 22

Exporting

M-x markdown-ts-convert converts the buffer, markdown-ts-convert-file a file. You get asked for the format and the converter, unless you set markdown-ts-default-converter. Supported out of the box:

  • PDF via pandoc
  • HTML via pandoc, cmark, cmark-gfm, markdown, markdown.pl

With a prefix argument the result is displayed, by default with eww. See markdown-ts-convert-display-function to open in a browser instead. That is your somewhat 'live' preview. Converting is not (yet) automatically when you make changes, maybe in the future.

Example using eww, split manually made for this demo:

emacs_markdown_31_demo step 23

Spec at hand

M-x markdown-ts-browse-commonmark-spec and M-x markdown-ts-browse-gfm-spec open the specs, for when you need to settle an argument.

Experiment with eglot and eldoc

This is still experimental within the experimental, so don't blame eglot's author if something goes wrong. Send a bug report to markdown-ts-mode instead.

If you set this:

(setopt eglot-documentation-renderer #'markdown-ts-view-mode)

Eglot will try to render documentation (usually Markdown provided by the LSP server) using markdown-ts-mode.

emacs_markdown_31_demo step 24

Again, we are still shaving off some rough edges here, and results may vary. Please do help us test this, though.

Play with options

M-x customize-group RET markdown-ts RET and go through it. Some of the customs worth a look at first:

  • markdown-ts for display: markup hiding, ellipsis, bullets, checkboxes, thematic break and hard line break characters, inline images, folding on open
  • code blocks: markdown-ts-code-block-modes, markdown-ts-default-code-block-mode, markdown-ts-enable-code-block-context-mode
  • tables: markdown-ts-enable-table-mode, markdown-ts-table-auto-align, markdown-ts-table-default-column-width
  • markdown-ts-convert for exporting
  • markdown-ts-toc for tables of contents

Faces are customizable too, one per Markdown element.

How you can help

The best way you can help is simply by using it. Try it with your Markdown files, play with the different features, and see what needs improvement or what breaks.

If you find something that doesn't work as expected, please report it as a bug from Emacs itself with M-x report-emacs-bug RET. Include a small example that reproduces the problem whenever possible. This is especially useful for issues involving fontification, tree-sitter grammars, tables, code blocks, or interactions with other modes.

We're still polishing the rough edges, so bug reports, feedback, and real-world testing are very welcome.

I found a bug, is it because markdown-ts-mode is buggy?

Some of the surprises you may hit while using markdown-ts-mode might be the mode, some might be the grammar, some might come from how tree-sitter is integrated into Emacs, or from the tree-sitter ecosystem as a whole. Knowing about this upfront helps understanding that debugging is challenging.

Grammars are a shared, external asset

A grammar is not written for Emacs. The very same tree-sitter-markdown is consumed by other editors and tools, so any change to it is negotiated among all of its users. That is great for the ecosystem, and it also means a fix we would like to see may take a while to land, or may never land in the shape we would prefer. When that happens, we work around it inside the mode as best we can, and report the issue upstream.

So, if you find something that looks like a mode bug and the answer turns out to be "the grammar parses it this way", now you know where that answer comes from. Please do report it anyway, we would rather hear about it twice than not at all.

Building grammars has its own quirks too. Not every grammar builds with make and a C compiler alone: several are generated from a JavaScript definition, so their build path expects the tree-sitter CLI, and sometimes a Node.js installation, to be available. This is a good part of why pre-compiled grammar bundles and distro packages are so popular. As said before, I still prefer compiling them interactively from Emacs, but now you know why your distro may be pulling in more than you expected.

Indirect buffers

This one deserves an explicit warning, because it surprises people: tree-sitter and indirect buffers do not get along.

  1. Parsers are not shared with indirect buffers. They belong to the base buffer, and an indirect buffer starts with none. You either copy them over manually, or re-instantiate them by enabling a major mode in the indirect buffer.

  2. Font-lock in indirect buffers is not supported at all. This is a limitation in Emacs itself.

The practical consequence is that (at least at the moment of this writing) if you use a package that clones a region into an indirect buffer, expect no fontification there. This is not specific to markdown-ts-mode, it applies to every -ts-mode, and it is not something we can fix from the mode's side.

Further reading

If this guide got you interested, there is a lot of good material out there about writing and using tree-sitter modes. Stéphane Marks, my partner in crime on this mode, put together the list below, and it is too good to keep to ourselves. Some of it may be a little stale by now, tree-sitter moves fast, but the reasoning in these articles holds up:

And, of course, the notes from the people who built all of this into Emacs, Yuan Fu and Juri Linkov, which are the closest thing we have to a canonical reference:

Is this going to be out of the experimental tag on next Emacs release?

In this post beginning I wrote:

What this means? Should you use it or not? Is this ready? Is this just a sketch of a mode?

Now you probably have a better answer.

experimental does not mean markdown-ts-mode is just a sketch or that it is missing the basic features you would expect from a Markdown mode. It means the mode is still evolving, and we are not yet ready to promise that its API, behavior, or some of its features won't change.

So, should you use it? Yes! If you are comfortable with the experimental label, please give it a try. The more people using it with different Markdown files, configurations, and workflows, the easier it is for us to find issues and fix it.

Will it be out of experimental in the next Emacs release? Maybe, we sure are working towards it! We will see. There are still things to polish, limitations to work around, and feedback to process before we can make that call.

For now, consider this your invitation to play with it. And if you find something weird, don't just work around it, let us know. That's how we get it ready.

-1:-- An unofficial guide to markdown-ts-mode on Emacs 31 (Post Rahul Juliato)--L0--C0--2026-08-26T23:00:00.000Z

James Cherti: Configuring Emacs Scrolling for Better Usability

By default, scrolling in Emacs recenters the window when point moves off-screen, and rapid scrolling through large, heavily fontified files can introduce noticeable input lag. This article outlines configurations that make scrolling more predictable and responsive.

Customizing scroll recentering

Adjusting the automatic scrolling behavior can prevent Emacs from making large jumps when point moves beyond the visible portion of the window:

;; Scroll by up to 20 lines to bring point back into view before falling back to
;; the normal automatic scrolling behavior.
(setq scroll-conservatively 20)

When point moves off-screen or into the scroll margin, setting scroll-conservatively to a moderate value like 20 allows Emacs to scroll the text by up to 20 lines in either direction to bring point back into view.

Note: Setting scroll-conservatively to a value above 100 prevents automatic scrolling from centering point, regardless of how far point moves. Emacs instead scrolls only far enough to bring point into view, placing it at the top or bottom of the window depending on the direction of scrolling.

Maintaining vertical context

While scroll-conservatively controls how Emacs reacts when point leaves the window, you can also define a boundary to force scrolling before point reaches the absolute edge:

;; Keep 3 lines of context visible above and below point.
(setq scroll-margin 3)

Setting scroll-margin to 3 establishes a boundary of three lines at both the top and bottom of the Emacs window, forcing the buffer to scroll automatically as soon as your cursor enters this area instead of waiting for it to reach the absolute edge of the screen.

This keeps point away from the top and bottom edges of the window whenever automatic scrolling can move it out of the margin.

Note: Leaving scroll-margin at the default of 0 ensures the cursor can sit directly on the top or bottom line before triggering a scroll. Many users prefer this default setting because it maximizes usable vertical screen space and mirrors the edge-scrolling behavior found in most other modern text editors.

Horizontal scrolling

When line truncation is enabled (truncate-lines is non-nil), Emacs automatically scrolls horizontally when point approaches the left or right edge of the window. hscroll-margin controls how close point can get to an edge, while hscroll-step controls how far the window moves when automatic horizontal scrolling occurs.

;; Horizontal scrolling
(setq hscroll-margin 2
      hscroll-step 1)

Setting hscroll-margin to 2 causes horizontal scrolling to trigger when point comes within two columns of the left or right edge, while setting hscroll-step to 1 forces the window to pan exactly one column at a time.

This transforms horizontal movement into a column-by-column scroll, replacing the default behavior where Emacs jumps the view by half a screen horizontally and forces you to visually search for your cursor.

Deferring fontification during input

Fontification (syntax highlighting) requires CPU time to parse and colorize text. In large or complex buffers, this process can block the main thread, leading to input latency. Setting redisplay-skip-fontification-on-input to t causes Emacs to prioritize user input over immediate syntax highlighting:

;; Skip some fontification when input is pending.
(setq redisplay-skip-fontification-on-input t)

The tradeoff is that syntax highlighting may temporarily lag behind the underlying text while your input is actively being processed. When scrolling rapidly into an unseen section of a file or pasting a large block of code, the text might appear uncolored or incorrectly colored for a fraction of a second. However, the delay is virtually unnoticeable, and the correct colors will render as soon as the input queue clears.

Preserving screen position

Setting scroll-preserve-screen-position to t fixes a common visual annoyance when paging up or down through a file (using C-v or M-v). If your cursor is in the middle of the screen and you hit Page Down, the cursor stays exactly in the middle of your monitor:

;; Preserve point's vertical screen position when scrolling.
(setq scroll-preserve-screen-position t)

Note: There is one exception involving next-screen-context-lines (default 2), which controls how many lines of text are repeated from your previous screen when you scroll by a full window. If your cursor is resting on one of these repeated lines at the edge of the window when you jump, Emacs does not preserve its vertical screen position. Instead, it moves the cursor into the newly revealed text. This prevents the cursor from getting stranded at the extreme edge of the window and ensures you have enough surrounding text to read comfortably.

Disabling automatic vertical scrolling

Setting auto-window-vscroll to nil prevents movement and scrolling functions from automatically modifying the window's vertical scroll position when they encounter display rows taller than the window:

;; Do not automatically adjust vertical scrolling through tall display rows.
(setq auto-window-vscroll nil)

This makes vertical movement through buffers containing large elements, such as inline images, much more predictable by avoiding sudden partial-screen shifts. The tradeoff is that tall display rows become more cumbersome to navigate because Emacs no longer automatically scrolls through their partially visible portions to reveal the rest of the element.

Top and bottom scroll errors

By default, Emacs immediately signals an error if you attempt to scroll past the top or bottom of a buffer. Setting scroll-error-top-bottom to t changes this behavior so that your first attempt to scroll past the boundary safely moves the cursor to the exact beginning or end of the document:

;; Move point to the buffer boundary before signaling a scrolling error.
(setq scroll-error-top-bottom t)

Instead of throwing an error immediately, it moves your cursor directly to the first or last character of the buffer. If you attempt to scroll again while your cursor is already resting on that final position, Emacs will then signal the standard scrolling error.

Enabling faster scrolling

Rapid scrolling can become sluggish when Emacs encounters previously unfontified text. Setting fast-but-imprecise-scrolling to t prevents Emacs from becoming unresponsive when you move rapidly through large buffers:

;; Avoid fontifying unfontified text while scrolling rapidly.
(setq fast-but-imprecise-scrolling t)

The primary tradeoff is that scrolling can become visually imprecise. However, this behavior is generally nothing to worry about. For typical programming tasks using a standard monospaced font, line heights remain consistent, meaning you will likely never experience this imprecision at all. Even in modes that do use variable font sizes, the visual jump is minor and temporary. Once you stop scrolling and the input queue clears, Emacs finishes fontifying the visible text and corrects the layout, ensuring your final view is accurate.

Scroll aggressiveness

By default, when point moves beyond the top or bottom of the window, Emacs scrolls the buffer and places point around the middle of the screen. Setting scroll-up-aggressively and scroll-down-aggressively to 0.01 keeps point near the edge of the screen instead:

;; Provide a "stick-to-edge" scrolling experience.
(setq-default scroll-up-aggressively 0.01
              scroll-down-aggressively 0.01)

This results in minimal, predictable scrolling increments.

Permitting scrolling during search

By default, attempting to scroll the window while in an active isearch session (using C-s or C-r) cancels the search. You can allow scrolling without losing your search context.

;; Allow scrolling actions while remaining inside a search block.
(setq isearch-allow-scroll 'unlimited)

This allows you to scroll away from your current match to check another part of the file for reference, and then resume your search by pressing C-s to jump to the next match.

Shell and compilation buffers

When executing long-running processes in interactive shells or REPLs (comint-mode), new output arriving at the bottom of the buffer will frequently scroll your view downward. Setting comint-scroll-to-bottom-on-input to t and comint-scroll-to-bottom-on-output to nil causes Emacs to snap point to the bottom of the buffer only when you supply keyboard input:

;; Auto-scroll to bottom only when you type, not when background output arrives.
(setq-default comint-scroll-to-bottom-on-input t
              comint-scroll-to-bottom-on-output nil)

This allows you to scroll up through compilation logs or terminal history to read errors without the screen aggressively jumping to the bottom every time a new line is printed.

The mouse wheel scrolling

The default mouse wheel behavior in Emacs can feel jumpy, as it scrolls multiple lines per click and accelerates based on scroll speed. You can easily modify this and assign modifier keys to perform horizontal scrolling or text scaling:

;; Scroll one line at a time and map modifier keys to specific actions.
(setq mouse-wheel-scroll-amount
      '(1
        ((shift) . hscroll) ((meta))
        ((control meta) . global-text-scale)
        ((control) . text-scale)))

;; Disable acceleration of scrolling.
(setq mouse-wheel-progressive-speed nil)

Setting mouse-wheel-scroll-amount to 1 configures the mouse wheel to scroll exactly one line vertically, while mapping modifier keys like Shift for horizontal scrolling and Control for text scaling turns your mouse into a navigation multi-tool. Paired with setting mouse-wheel-progressive-speed to nil, this prevents rapid wheel movements from dynamically accelerating the scroll distance.

The result is a predictable, line-by-line scrolling experience that stops you from losing your place, with the tradeoff being that navigating very long files with a physical scroll wheel requires more physical effort since the view will not jump in large increments.

Note: If you prefer a keyboard-driven workflow and want to disable mouse input entirely, check out the inhibit-mouse package.

Modern pixel-precise scrolling

Emacs 29 introduced pixel-precise scrolling for pointing devices that support suitable high-resolution scrolling events.

;; Enable pixel-precise scrolling for supported pointing devices.
(pixel-scroll-precision-mode 1)

;; (setq pixel-scroll-precision-use-momentum nil) ; Optional: disable momentum

Enabling pixel-scroll-precision-mode activates pixel-resolution scrolling for supported mouse and touchpad input, removing the traditional limitation of scrolling by whole text lines.

-1:-- Configuring Emacs Scrolling for Better Usability (Post James Cherti)--L0--C0--2026-08-26T16:18:54.000Z

Irreal: Fixing Define-word

My go to Emacs in-line dictionary is abo-abo’s define-word. It pops up a definition of the word at point in the minibuffer. I use it several times a day. Lately, though, it has been returning “zero definitions found” on every invocation. This problem predated my update to Emacs 31.1 so it’s not related to the version of Emacs.

Today (Tuesday) I finally got fed up and decided to track down the problem. It wasn’t too hard. It turns out that the problem is that define-word calls the on-line dictionaries with HTTP rather than HTTPS and the sites are rejecting the connections. There’s already a pull request (2026-08-22) for the fix but as of today (2026-08-25) it hasn’t been merged or uploaded to MELPA.

I messed around for a while but couldn’t get any of the obvious solutions to work so I gave up and added

  :init
  ;; Until define-word is updated in MELPA
  (defcustom define-word-services
    '((wordnik "https://wordnik.com/words/%s" define-word--parse-wordnik)
      (openthesaurus "https://www.openthesaurus.de/synonyme/%s" define-word--parse-openthesaurus)
      (webster "https://webstersdictionary1828.com/Dictionary/%s" define-word--parse-webster)
      (offline-wikitionary define-word--get-offline-wikitionary nil))
    "Services for define-word, A list of lists of the
  format (symbol url function-for-parsing).
Instead of an url string, url can be a custom function for retrieving results."
    :type '(alist
            :key-type (symbol :tag "Name of service")
            :value-type (group
                         (string :tag "Url (%s denotes search word)")
                         (function :tag "Parsing function"))))

to the use-package for define-word. That’s just a copy of the definition of define-word-services from the define-word source. Note that it’s important that it goes in the :init section so that the definition gets established before define-word is loaded.

It’s a messy solution but it will do until the fix is merged and percolates up to MELPA.

-1:-- Fixing Define-word (Post Irreal)--L0--C0--2026-08-26T15:08:06.000Z

Emacs Redux: Meet Utterson, my Jekyll blogging helper

Back in 2019 I wrote about dealing with Jekyll post URLs, where I shared a tiny command that spared me from having to remember the exact file name of every article I wanted to link to. That command has been in my config ever since, and over the years it quietly grew a few siblings. Recently I finally gathered them all in one place and turned them into a small package - utterson.

The common Jekyll tasks

I guess anyone who blogs with Jekyll knows the drill. Jekyll is great and I have no intention of switching, but a few of its conventions generate a steady trickle of manual work:

  • posts live in _posts and their file names have to start with the publication date, as in 2026-08-26-some-post.md
  • every article needs a bit of YAML front matter at the top - a layout, a title, a date, some tags
  • linking to another post means using a Liquid tag that wants that date-prefixed file name, which nobody remembers
  • images and other assets need URLs relative to the site root, not to the article you happen to be editing

None of this is hard. All of it is annoying, and all of it is exactly the sort of mechanical work an editor should be doing on your behalf. So over the years I wrote a command for each chore, dropped it in my init.el and moved on with my life.

Why I finally cleaned this up

The tipping point was the last command I added - promoting a draft to a post.

I write most articles in _drafts, where jekyll serve --drafts shows them but a deploy doesn’t. Publishing one used to mean renaming the file by hand to add today’s date, then editing the date in the front matter to match, then convincing git that this was a rename rather than a delete plus a brand new file. Three chances to get something wrong, and I certainly got it wrong more than once.

Once that was automated I took a look at what had accumulated in my config and realized it wasn’t a bag of snippets anymore - it was a workflow. And since every person blogging with Jekyll from Emacs deals with exactly the same chores, there was no good reason to keep it to myself. So I spent an evening extracting the lot, wrote some tests, and gave it a name.1

There are other Jekyll packages for Emacs, of course. Both hyde and easy-jekyll aim to be something like an IDE for your site, with dashboards, deployment and preview buffers. That’s a perfectly reasonable thing to want, it’s just never been what I wanted. My site lives in git, I deploy it with a push, and I’ve already got Magit and a browser. All I ever wanted was for the bookkeeping to stop being my problem.

So utterson is deliberately small. It knows that a Jekyll site is a folder of Markdown files with YAML front matter, and that’s about the extent of its ambitions. Renames go through vc, so git records them as renames and the history of an article follows it around. Nothing in there ever deploys anything.

Taking it for a spin

The package is not on MELPA (yet?), so for the time being it’s package-vc territory:

(use-package utterson
  :vc (:url "https://github.com/bbatsov/utterson" :rev :newest)
  :custom
  ;; where to look for sites, when you invoke a command outside one
  (utterson-search-path '("~/projects/"))
  :config
  ;; the commands live in a keymap that you bind wherever you please
  (keymap-set utterson-mode-map "C-c j" 'utterson-command-map)
  (global-utterson-mode +1))

global-utterson-mode enables the minor mode in the buffers of any folder that has a _config.yml in it, so the commands are around while you’re working on a site and nowhere else.

C-c j p starts a post. It asks for a title, offers a file name slug derived from it (which you’re free to shorten right there, and I usually do), then asks for tags, completing against every tag the site already uses. That last bit is how you avoid ending up with both Emacs and emacs in your tag cloud. Finally it writes the front matter, saves the file and leaves the cursor exactly where you’re about to start writing:

---
layout: post
title: Meet Utterson, my Jekyll blogging helper
date: 2026-08-26 15:01 +0300
tags:
- Jekyll
- Blogs
- Packages
---

That block, as it happens, is the one this very article started with. Here’s the whole thing in action:

Creating a new post with utterson: the title prompt, the slug derived from it, tag completion, and the finished front matter

C-c j d does the same in _drafts and C-c j t moves an article between the two, dating it on the way in and staging the rename with git. C-c j r is for the article you wrote a couple of weeks ago and never got around to publishing - it sets the date to now and renames the file to match. Both of them take a prefix argument if you’d rather type a date than accept the current time, which is how you schedule something for next Monday morning.

Publishing a draft looks like this - notice that the file name, the date in the front matter and git’s idea of what happened all stay in sync:

Publishing a draft with utterson: the file is renamed with today's date, the front matter date is updated, and git records a rename

The linking commands are the descendants of that 2019 snippet, all grown up. C-c j u inserts a post_url tag, completing over your posts newest first and showing you each post’s title next to its file name, and C-c j l inserts the entire Markdown link, already titled after the post it points to:

Inserting a link to another post: the completion list shows every post newest first, with its title next to the file name

C-c j i inserts an image, C-c j I copies a file into your images folder first (perfect for a screenshot that’s still sitting on your desktop under a name like Screenshot 2026-08-26 at 15.01.12.png), and C-c j a links to anything else under assets. And C-c j s runs jekyll serve in a buffer, because sooner or later you do want to look at the thing.

Note: There’s a menu as well, in case you’d rather click your way around while the keybindings are still settling in.

Epilogue

It’s early days for the package, even though most of the code behind it has been in daily use for years. If you blog with Jekyll from Emacs, I’d love to hear which of your own little chores it doesn’t cover yet - that’s the fastest way for it to get better.

And if you don’t blog with Jekyll - what does your setup look like? I’ve got the feeling that quite a few of you are writing your posts in org-mode and I’m always curious about the workflows people come up with.

That’s all I have for you today! Keep hacking!

  1. Gabriel John Utterson is Doctor Jekyll’s loyal friend and lawyer - the man who keeps his affairs in order. Naming things is hard, but every now and then you get lucky. 

-1:-- Meet Utterson, my Jekyll blogging helper (Post Emacs Redux)--L0--C0--2026-08-26T14:24:00.000Z

Meta Redux: Smarter Form Targeting Is Coming to CIDER

If I had a dollar for every time someone asked on the Clojurians Slack in #cider why C-x C-e evaluated “the wrong thing”, I’d probably be writing this post from a yacht. The answer was always the same: the cursor wasn’t where CIDER expected it to be. The upcoming CIDER 2.1 release changes that - the evaluation commands now figure out which form you mean from where your cursor actually is.

NOTE: Update: this didn’t survive contact with its users, and CIDER 2.1 ships with the classic behaviour after all. See /posts/2026/08/29/smarter-form-targeting-is-not-coming-to-cider.html for what the feedback was, what replaced it, and the rather nasty bug the detour turned up.

A bit of history

Emacs has a very particular tradition when it comes to evaluating code: eval-last-sexp (the venerable C-x C-e) acts on the expression before the cursor. Not the one you’re looking at, not the one you’re inside of - the one that ends exactly where your cursor stands. SLIME follows this tradition, Emacs Lisp itself follows it, and for the past 15+ years CIDER has followed it too.1 If you grew up in Emacs, this rule is in your fingers and you’ve never once thought about it.

Here’s the thing, though - most people using CIDER didn’t grow up in Emacs. And many of them never programmed in Emacs Lisp and Common Lisp with SLIME. They came to Emacs because of CIDER (or Clojure in general), and for them the rule is invisible, arbitrary and mildly hostile. You put your cursor on a form, you press the eval key, and CIDER cheerfully evaluates… something else. Meanwhile every other modern Clojure environment - Calva, Conjure, the various vim plugins - resolves the form from the cursor position and just does what you meant.2

For a long time I resisted changing this, mostly out of respect for the Emacs tradition (and my own muscle memory). But at some point I had to admit that I was optimizing for the wrong audience. CIDER’s users are mostly casual Clojure hackers who happen to use Emacs, not Emacs experts who happen to write Clojure. The tradition was serving me, and confusing them.

What’s actually changing

The evaluation commands (and their macroexpansion, inspection and tapping siblings) now resolve “the form the cursor indicates”. Concretely, with | marking the cursor:

(map inc |(range 10))

Pressing C-c C-e here used to evaluate inc - the form before the cursor, which is almost never what you wanted. Now it evaluates (range 10) - the form your cursor is pointing at.

(str "hello" " " "world"|)

This one used to evaluate "world" (really!), because the last complete expression before a cursor sitting on the closing paren is the final string. Now it evaluates the whole (str ...) call.

Macroexpansion benefits too:

(when tru|e (launch-missiles))

C-c C-m here used to complain that true is not a macro. Now it expands the enclosing (when ...) call, because expanding a bare symbol is never what anyone means.

And my favorite one - the rich comment workflow is now consistent everywhere:

(comment
  (calculate-all-the-things|))

Every defun-level command - eval, pretty-print, inspect, debug - now treats the form inside the (comment ...) as the top-level one. Evaluating a whole comment form returns nil by definition, which has exactly zero uses, so CIDER no longer does that no matter which command you reach for.

Why you probably won’t notice

Here’s the part I’m most pleased with: the new behavior agrees with the old one at every position where “the form before the cursor” made sense. Cursor right after a form? Same result as always. Cursor in the whitespace after a form? Same. Cursor in the middle of a symbol? Same. The two behaviors only diverge where the classic answer was something nobody ever wanted - a previous sibling, a lone trailing atom.

So if your muscle memory follows the Emacs tradition, nothing changes for you. If it doesn’t - CIDER stops punishing you for it. That’s the whole change.

Reverting to the classic behavior (for now)

If you do want the traditional rules - maybe you genuinely use “evaluate the previous sibling while standing on an opening paren” - one setting restores them exactly:

(setq cider-form-targeting 'preceding)

There’s also a per-session toggle in the eval menu (C-c C-v T) that shows the active mode in the mode line while you experiment.

This option is probably living on borrowed time, though. I added it back when I planned to keep the classic behavior as the default and offer smart targeting as an opt-in. Now that the roles are reversed, an option whose only job is restoring rules almost nobody deliberately relied on doesn’t really make much sense, and lately I’ve been trying to trim that kind of clutter from CIDER, not add to it. Don’t be surprised if the option quietly disappears - possibly even before the release ships. Which is one more reason to speak up now if the classic behavior genuinely matters to you.

Farewell, “last sexp”

This change forced my hand on something I’d been putting off for years - the command names. cider-eval-last-sexp is a fine name for a command that evaluates the last sexp. It’s a lie for a command that evaluates the form your cursor indicates. So the commands got honest names:

  • cider-eval-last-sexp is now cider-eval-form
  • cider-eval-defun-at-point is now cider-eval-defun (the -at-point never carried information)
  • likewise for the pprint/tap/inspect/insert variants

Every old name keeps working as an alias, so your config and your M-x habits are safe. But why “form” and not “sexp”? Beyond the targeting change, there’s a Clojure-specific reason: in Clojure a form isn’t always a single sexp. ^:private x is two sexps but one form; so is #inst "2024-01-01". The commands operate on forms - the reader’s unit of evaluation - and now they say so.3 The manual’s evaluation docs got a proper glossary explaining all of this.

Closing thoughts

All of this is on master and in the MELPA snapshots today, ahead of the next stable release. I’d really love for people to play with it before the release ships - especially if you’re an Emacs veteran whose fingers disagree with my reasoning, or a newcomer for whom this was supposed to just work. Did we get the resolution rules right? Does anything still surprise you?

Share your feedback on the CIDER discussions board, in #cider on the Clojurians Slack, or just file an issue. This is exactly the kind of change that’s easy to adjust before a release and painful after - and the fate of the compatibility option depends on what I hear.

That’s all I have for you today. Keep hacking!

  1. CIDER started its life as a SLIME “clone” for Clojure, after all - the tradition runs deep. 

  2. Interestingly, Cursive is the only major non-Emacs Clojure environment that kept the classic “form before the caret” model. 

  3. This also explains a subtlety Emacs veterans might appreciate: plain forward-sexp movement doesn’t know that Clojure metadata belongs to the form it annotates, which is why clojure-mode has always needed its own “logical sexp” movement functions. The new targeting is built on those, so metadata is never silently dropped from what you evaluate. 

-1:-- Smarter Form Targeting Is Coming to CIDER (Post Meta Redux)--L0--C0--2026-08-26T09:18:00.000Z

James Cherti: Configuring Emacs Eglot for Better Performance and Latency

Eglot ships with Emacs as a built-in, lightweight LSP client, and its default configuration is sufficient for most projects. However, working with large codebases can cause latency. When Eglot becomes sluggish, the problem may involve work performed by Eglot and Emacs as well as the language server itself. Event logging, filesystem watching, diagnostics, completion, and other editor activity can all contribute to latency.

This article covers practical changes for keeping Eglot responsive when working with large projects.

Auto-shutting down idle Eglot servers

This reduces resource usage when switching between multiple projects. The eglot-autoshutdown variable shuts down the LSP server after the last managed buffer has been killed.

(setq eglot-autoshutdown t)

This is useful when many separate language servers would otherwise remain running after their buffers have been closed.

(If you find yourself routinely leaving unused buffers open, the buffer-terminator package can automate this cleanup. It quietly monitors your buffer activity and terminates idle file buffers, ensuring eglot-autoshutdown can trigger reliably without requiring you to manually execute kill-buffer.)

Preventing Eglot from blocking the Emacs UI on connection

Opening a project can cause Emacs to block while waiting for the LSP server to initialize. Setting eglot-sync-connect to nil prevents Eglot from blocking on the initial connection, allowing the connection to continue in the background:

(setq eglot-sync-connect nil)

This prevents the connection attempt from unnecessarily delaying interactive editing.

Disabling LSP event logging

Eglot maintains an events buffer containing JSON-RPC activity. Large event logs consume memory and add string allocation costs, especially when retaining complete JSON payloads is not needed during normal editing.

Configuring eglot-events-buffer-config (or eglot-events-buffer-size on Emacs 29 and earlier) reduces the volume of retained data:

;; Disable event logging completely (Emacs >= 30)
(setq eglot-events-buffer-config '(:size 0 :format short))

;; For Emacs <= 29
;; (setq eglot-events-buffer-size 0)

Setting :size 0 disables the events buffer.

Reducing file watchers

Allowing a language server to monitor large portions of a repository can consume OS file descriptors, increase memory usage, and add startup delay. To reduce these resource costs, limit the number of file that Eglot watches using:

(setq eglot-max-file-watches 5000)

In addition, where supported by the language server, configure it to ignore large generated directories such as node_modules, .git, or build output folders. Because these exclusion rules are specific to the language server, they must be passed through the eglot-workspace-configuration variable. (The standard method is to define these rules per-project using a .dir-locals.el file at the root of your repository.)

Disabling progress reporting

Whenever a server reports progress (e.g., during indexing or builds), this can trigger mode-line redisplays. The following suppress mode-line progress reports:

;; Suppress mode-line progress animations
(setq eglot-report-progress nil)

Disabling automatic code action probing

By default, Eglot can asynchronously query the language server for available code actions at point when the cursor is idle. Disabling these automatic indications reduces continuous process communication and background activity during editing:

;; Disable automatic code action indicators to reduce background polling
(setq eglot-code-action-indications nil)

Note: Disabling this feature only removes the automatic UI indicators. You retain the ability to manually request and execute code actions at any time by invoking M-x eglot-code-actions.

Disabling unneeded LSP server capabilities

Note: You do not need to disable all of these capabilities, as some of them are very useful. Only disable the ones you do not use.

LSP servers can provide optional capabilities. Disabling features that are not needed can reduce processing and visual clutter, although the actual performance benefit depends on the language server and project.

Eglot handles on-type formatting by evaluating every single keystroke to check if the connected language server supports the feature and if the typed character is a registered trigger character. If a match occurs, Eglot issues an RPC request and applies the asynchronous edits, which can conflict with external formatters and introduce typing latency. To prevent these background requests and bypass the per-keystroke evaluation entirely, use:

(with-eval-after-load 'eglot
  (add-to-list 'eglot-ignored-server-capabilities :documentOnTypeFormattingProvider))

Inlay hints (eglot-inlay-hints-mode) display automatically determined types and parameter names as annotations in the buffer. Disabling them removes the associated visual annotations and any work required to maintain them:

(with-eval-after-load 'eglot
  (add-to-list 'eglot-ignored-server-capabilities :inlayHintProvider))

When the cursor rests on a symbol, the LSP server can highlight other occurrences of that symbol (eglot-highlight-eldoc-function). Disabling it will stop Eglot from highlighting other occurrences of the symbol under the cursor:

(with-eval-after-load 'eglot
  (add-to-list 'eglot-ignored-server-capabilities :documentHighlightProvider))

For major modes with Tree-sitter support, Tree-sitter already provides syntax-aware fontification locally and can be sufficient for many users. When the language server supports semantic tokens, Eglot can request semantic-token information over LSP and use it for additional syntax highlighting. Processing these responses requires JSON-RPC communication, decoding the token data, and applying the corresponding fontification. On large or frequently changing buffers, this can add CPU overhead. Ignoring the server's semantic-token capability prevents Eglot from using this feature:

(with-eval-after-load 'eglot
  (add-to-list 'eglot-ignored-server-capabilities :semanticTokensProvider))

Note: The tradeoff is that some semantic highlighting supplied by the language server may be lost. Tree-sitter fontification and LSP semantic tokens are not identical, so disabling semantic tokens can result in less precise or less detailed highlighting for some languages.

Garbage collection, native compilation, and read process output max

  • The Emacs garbage collector can cause pauses during heavy workloads. LSP activity can increase allocation through diagnostics, completion, JSON processing, and other editor integrations. GC tuning can therefore help in some workloads, but the effect is workload-dependent. Increasing gc-cons-threshold permanently can reduce garbage collection frequency:
    (setq gc-cons-threshold (* 100 1024 1024))
    (Alternatively, several users rely on the gcmh package, which raises the GC threshold during active editing and forces a collection when Emacs becomes idle.)
  • Enable Native Compilation: Ensure that Emacs is built with native compilation support enabled. Native compilation can improve the execution speed of Emacs Lisp code, including code used by Eglot and other packages. (Recommendation: Use the compile-angel package to ensure that all packages are natively compiled.)
  • read-process-output-max: read-process-output-max controls the maximum amount of data Emacs reads from a subprocess in a single operation. Since Eglot communicates with language servers through subprocesses, increasing this value can improve performance when a server sends large bursts of JSON-RPC data, such as during initialization, workspace indexing, or large file updates, by reducing the number of read operations required to consume the output. Raising it can help workloads that regularly receive large bursts of process output:
    (setq read-process-output-max (* 1024 1024))
    (This raises the limit to 1 MiB. It does not reserve 1 MiB of memory for each process or increase the amount of data a server can send; it only allows Emacs to consume more output per read operation. On GNU/Linux systems, the value should not exceed /proc/sys/fs/pipe-max-size)

Freeing up the main Emacs thread with tree-sitter

Emacs executes Lisp, handles asynchronous process output, and updates the UI on a single main thread. In traditional major modes, typing triggers complex regular expression evaluations for syntax highlighting (font-locking). This CPU-bound work competes directly with Eglot, which relies on the exact same thread to deserialize incoming JSON-RPC payloads and render diagnostics. If the main thread is busy computing regexes, Eglot is forced to wait in the queue, resulting in input lag even if the external language server responds instantly.

Major modes built on Tree-sitter (the *-ts-mode variants, such as c-ts-mode, python-ts-mode, and rust-ts-mode) use the Tree-Sitter C library for incremental syntax parsing. If a stable *-ts-mode exists for your programming language, enabling it can improve Eglot's responsiveness.

-1:-- Configuring Emacs Eglot for Better Performance and Latency (Post James Cherti)--L0--C0--2026-08-25T16:56:08.000Z

A quick update for my Emacs config Gems series:

I am working on part 5, and I’m trying to finalize a few things first. Currently it’s Ispell (I’ve not been using it to its full capacity) and completion… more details soon.

-1:--  (Post TAONAW - Emacs and Org Mode)--L0--C0--2026-08-25T13:56:10.000Z

Raymond Zeitler: Emacs Numbered Backup Strategy

Emacs produces a backup of each file by default. It does this by appending a tilde to the file's name. For example, Myfile.txt is backed up to Myfile.txt~. It's a reasonable strategy, assuming that a Version Control system is in place.

But in the absence of version control, you're limited to restoring just the previous version; older versions need to be restored from backup.

I use git for coding projects that I share to the public. But I didn't create a repository for my home directory. So when C-c a m TAG1 generated an error instead of a list of TAGged items, I hoped fervently that the latest init file change was to blame.

After finding and fixing the problem quickly, (and realizing I was lucky this time), I resolved to be more diligent about disaster recovery. I looked into what backup schemes Emacs already provides.

Emacs can create unique backups for a file rather than overwriting the previous backup. When Emacs sees a numbered backup for a given file, it will make a new, sequentially-named file for each backup of that file, while continuing to use the "append tilde" method for other files.

Once I renamed (or copied) .emacs~ to .emacs.~1~, Emacs created backups named .emacs.~2~, .emacs.~3~ (and so on). I didn't even need to change a variable -- when Emacs detected .emacs.~1~, it knew that backups of .emacs needed to be numeric, so it acted accordingly. Emacs continues to use bills.org~ as the backup for bills.org. Until I tell it otherwise.

A list of file names in light grey text on a blue background: .emacs; .emacs~1~; .emacs~2~; .emacs~3~;

Read the Emacs Manual to learn more about numbered backups.


1 C-c a m invokes org-tags-view

The default values for the pertinent backup variables are:

  • make-backup-files is t
  • backup-by-copying is nil
  • version-control is nil

-1:-- Emacs Numbered Backup Strategy (Post Raymond Zeitler)--L0--C0--2026-08-12T23:01:35.675Z

Raymond Zeitler: How Much Management Does Knowledge Need?

The topic of this month's Emacs Carnival interests me, even though I "dump ... notes into a directory and simply grep for information ...."0 Org does not work the way my brain works,1 or rather, my brain doesn't work the way Org works.

I do use Org. At first I created a few Org files (work.org and home.org, and maybe todo.org) thinking I could rigidly dedicate all my topics to them.

Over time I had about dozen files, one for nearly every major facet of my life until I gave up. Why? Because it really doesn't matter where I put Org TODOs and their notes. When I need to locate something, Org provides a way to search entries for a regular expression, which is what grep does, sans all that Unixy CLI stuff.

So faced with the task of paying one of my wife's medical bills, I could agonize over whether to put the task into wife.org or bills.org or medical.org.2 Or I could just:

  • establish the task somewhere,
  • act on it
  • link to a receipt
  • mark it done3

I created a special template for org-capture, which places these one-off tasks into todo.org. And it clocks into the task, too.

A topic with even greater complexity is "education." I'm a lifelong learner; my edu.org file has (only) 29 headings, all of which have at least a few subheadings. If I learn about an Emacs feature, I might want to write about it, so I'll put a task into blog.org that links to it.

Occasionally a post will span multiple topics in edu.org, topics such as Emacs, Vivaldi, LibreOffice, HTML or LaTeX. In a year from now, which file should I look for it in? The keystroke C-c a s comes to the rescue.4 I might search for "Sacha" because all I remember is that I discovered an Emacs feature on Sacha's website.5 Incidentally, I get 13 hits when I search for "Sacha," one of which was the one I had in mind.

The question I think all Emacs users face is, "At what point does tweaking Emacs (and self-quantifying tools in general) tip the scales from aiding our progress to impeding it?" I dread going down a Knowledge Management rabbit hole,6 but I'm sure I'll enjoy the leap.

0 Charlie Holland's Search for Knowledge

1 A tree works like your brain.

2 Don't even get me started on insurance.

3 All of this occurs while clocked in to the task.

4 In this case, C-c a is bound to org-agenda.

5 Sacha Chua's blog

6 Can one gain knowledge of Knowledge Management Systems without having a Knowledge Management System in which to record that knowledge? That may be a Zen koan for someone.

-1:-- How Much Management Does Knowledge Need? (Post Raymond Zeitler)--L0--C0--2026-08-10T22:19:40.478Z

Raymond Zeitler: Vakana -- Sneak a Peek

I installed Donovan Ratefison's brand-new Vakana app1 on my Android device this morning. After following the tutorial, I exported the thread as an Org-mode-compatible package in a ZIP and emailed it to myself in order to transfer it to my desktop.

After opening the Org file that Vakana generated, I exported the entire buffer to HTML. The result is shown below. The only change I wanted to make was to scale the bead of each heading 25%.2

It's fortuitous that Donovan Ratefison released Vakana in a month whose Emacs Carnival theme is "The Search for Knowledge," because Vakana provides a unique way to manage one's knowledge. I'll need to become proficient in it before adding it to my workflow. So my eventual Emacs Carnival submission will be more pedestrian.


1 Learn about Vakana here or download here.

2 The images used for beads are SVG files. However this blogging platform does not support that file type, so I converted them to GIF for this post. This should explain why they don't look awesome.

Your first thread

Your first thread
A 90-second tour. Walk it bead by bead, then make it yours — delete it, or reload it anytime from Settings → Pro Tips.

1. The thread

2. Start here · a 90-second tour

[2026-08-09 Sun]

First Bead, Marked 0
Welcome — and if this is all new, that’s perfectly fine. We’ll go gently.

Vakana is the Malagasy word for beads — there’s no other word for them in the language, and it’s where this app takes its name. The idea is old and simple: you turn a moment into a small bead — a little coloured token — and beads you keep together form a thread you can walk back along, like beads on a string. (These six notes are already a thread — “Your first thread.”)

That’s the whole shape of it. What a bead means is always yours — the app never decides that for you.

This is a short, hands-on tour: five steps, each one a thing you try yourself.

Next → 1/5 · Tap the bead.

3. 1/5 · Tap the bead

[2026-08-08 Sat]

Second Bead, Marked 1
,*Tap the bead* at the top of this note — the composer opens. That’s where you pick a bead’s shape, pattern, mood-colour and a tiny glyph (this one’s is the number 1️⃣).

Eight shapes, eight patterns, eight moods — small enough to learn, wide enough for anything. There’s no legend: a circle isn’t “an encounter” unless you decide it is.

How does the user navigate the beads? Now I see "< Prev" and "Next >" so it's obvious.

Next → 2/5 · Open the thread.

4. 2/5 · Open the thread

[2026-08-07 Fri]

Third Bead, Marked 2
You’re on a thread right now — a chain of beads strung together on purpose. Open “Your first thread” (tap its chip here, or find it in the Threads view) and you’ll see all six beads in a row you can walk, bead by bead.

The app also keeps a quiet thread for every day you write, under Days in the Threads view — built for you, no effort.

Next → 3/5 · Comment.

5. 3/5 · Comment — swipe me right

[2026-08-06 Thu]

Some thoughts arrive after the moment. Instead of rewriting a note, leave a comment in its margin.

Try it: tap the 💬 at the top of this note — or, back in your list, swipe this row to the right. Either opens the margin, where you can jot a thought or even set it to remind you later.

(The next bead, 4️⃣, already has a comment — spot the 💬 on its row.)

Next → 4/5 · Long-press the bead.

5.1. Comments

5.1.1. Comment — [2026-08-09 Sun 18:50]

The tutorial shows an icon for comment as a white-filled rectangular balloon with three dots. However the actual icon is a balloon that's an empty circle

6. 4/5 · Long-press a bead in the list

[2026-08-05 Wed]

Fifth Bead, Marked 4
Beads repeat, and you’ll want to find a bead’s kin. This is a thing you do from your list, not from inside a note — so head back to the list first.

There, press and hold any bead: the list instantly filters to every bead like it. (Long-press is the move most people miss.)

Want finer control? Tap the funnel at the top of the list to filter by shape, mood, pattern or glyph, then gather the results into a brand-new thread.

Next → 5/5 · Make it yours.

6.1. Comments

6.1.1. Comment — [2026-08-09 Sun 18:22]

I keep coming back to this one — long-pressing a bead is the move most people miss.

6.1.2. Comment — [2026-08-09 Sun 19:14]

The alarm clock icon next to the note field is for a reminder, not a timestamp. It puts it on the next business day at 9:00 a.m., perhaps. I pressed it at 3:00 p.m. on Sunday August 9th, and then entered the reminder for the next day at 9:00 a.m..

7. 5/5 · Close it, then make it yours

[2026-08-04 Tue]

Sixth Bead, Marked 5
This thread is closed — notice the clasp. Closing marks a loop that finished; you can reopen it anytime.

A finished thread can leave the app: open it and Export to turn it into a web page or a slide deck.

That’s the whole grammar — bead, thread, comment, close, export. Now make it yours:

Delete this tour — swipe any note left, or remove it in one tap from Settings → Pro Tips → Interactive tutorial. • Want it back later? Reload from the same place.

Now write your first real moment, and bead it however you like.

Created: 2026-08-09 Sun 16:42

Validate

-1:-- Vakana -- Sneak a Peek (Post Raymond Zeitler)--L0--C0--2026-08-09T23:55:27.267Z

Raymond Zeitler: Dedicate an Emacs Window to Its Buffer

Before I start writing in Org, I dedicate the window. This "protects" the content, preventing it from being replaced by something else. It ensures I can stay focused on my writing no matter what other buffers Emacs opens.

My usual workflow is to display help on the functions I write about. Emacs opens the help buffer in another window. That's good. But then if I click on the link to the source code that help references, that file will open in my writing window, and I lose focus. Dedicating the window prevents this.

I could dedicate a window manually by invoking toggle-window-dedicated (bound to C-x w d) every time I start writing. But because I clock in to anything I'm serious about, I let org-clock-in-hook to do it for me as shown below. Note that the hook calls set-window-dedicated-p rather than toggle-window-dedicated because the latter will un-dedicate a window that's already dedicated. I could configure org-clock-out-hook to un-dedicate the window; I'm debating whether it's necessary.

(add-hook 'org-clock-in-hook (lambda () (set-window-dedicated-p nil t)))

Interestingly, while I was writing this, JTR published his "Gems" article in which he writes about setting help-window-keep-selected to true "to keep help in its own dedicated window, so it won’t open in a separate window once we follow a link."1 Perhaps that’s his approach to the same issue.


1 Emacs Config Gems - Part 3

-1:-- Dedicate an Emacs Window to Its Buffer (Post Raymond Zeitler)--L0--C0--2026-08-06T17:43:24.706Z

Raymond Zeitler: One Space or Two?

HTML enforces the use of a single space to separate sentences. If you want to add another space, you need to use &nbsp;. I still remember how annoyed I was about that. Back in the century in which I took my technical writing course, two spaces were required. Of course, we weren't using computers very often; hand-written sentences needed to appear more separated than the words within.

I'm definitely not here to convince anyone to switch. Instead, I just want to share that I paid close attention to this detail when I was formulating my style guide for this blog. Should I continue with my out-of-date two-space habit? Or should I adopt the current convention?

I turned to Emacs itself for guidance. It turns out that the individuals who wrote Emacs comments, docstrings and help text used two spaces. Although many times they started each sentence on a new line.

It turns out that the content here mostly is one-space. I start out using two spaces in Org where most of this originates. But when I export to HTML, that second space vaporizes. That would seem like a bug to me.

The only place where you'll see evidence of my two-space style is in the comments and docstrings of my source code.

Am I the only person who nit picks about such details?


Thanks to Irreal for highlighting a customization from macosguru

-1:-- One Space or Two? (Post Raymond Zeitler)--L0--C0--2026-08-01T23:39:31.648Z

Raymond Zeitler: How to Copy the Link at Point -- an Undocumented Feature in EWW

If you've ever written an Emacs function only to find out that it was there all along, masquerading as something else, this post is for you.

I just finished writing an Emacs function that copies the link at point to the kill ring in EWW. It will remain unpublished. Why? Because I discovered that shr-maybe-probe-and-copy-url (bound to u and w in eww-mode) does the same thing. Just position point on a link and invoke it.

It's not obvious that pressing w will do this. In fact the major mode help for eww-mode suggests that w will not copy the link because it's bound to eww-copy-page-url. But it turns out that if point is on a link, w will invoke shr-maybe-probe-and-copy-url, "which copies this link's URL to the kill ring." The manual goes on to say, "If point is not on a link, pressing w calls eww-copy-page-url, which will copy the current page's URL to the kill ring instead."1

Before writing my function I had been looking through the mode help (C-h m from within a EWW buffer) and the EWW source code for any function that looked like copy-link-at-point.

So how did I find out about this? I wanted to ensure that I was following the standard for capitalizing EWW, so I searched the web. This took me to the GNU Emacs manual, which has a section on EWW and shows it in all caps. But then my distractibility encouraged me to read a great deal more.

I've known that keybindings can change depending on the major mode. But it seems a keybinding can change based on context (i.e. the position of point). This idea is new to me. Maybe it's common for a function to "hand off" to another based on context. But in this case it seems irresponsible to omit documentation for the alternate behavior of a w key press. Suppose a user expects to copy the page URL and gets the URL of a link, instead?

While this was a frustrating experience, it is yet another reminder to RTFM. WDYT?

1 GNU Emacs EWW Basic Usage
-1:-- How to Copy the Link at Point -- an Undocumented Feature in EWW (Post Raymond Zeitler)--L0--C0--2026-07-24T19:27:24.920Z

Raymond Zeitler: Modeling Browser History in Emacs

If there were an underappreciated web browser feature, it would be this: history.

On many browsers, history is invoked with C-h (Ctrl+h). Vivaldi shows history as a calendar. The user can choose from time intervals of Day, Week or Month and search for a keyword within that time interval.1

I tend to write about a topic after I've forgotten where I read about it. Suppose I recall reading about "nslookup" but can't remember when or where. Simple. Vivaldi remembered that I visited a page with "nslookup" on Windows Central on July 3 at 21:26. Being able to find such references helps.

History helped me track my hours when my employer required bi-weekly timesheets. I would combine browser history with Org's clock report to get a result I was actually proud to submit.

I still track my time with Org, which stores start and stop times in a drawer called LOGBOOK. But I tend to forget to start or stop the clock, so the times are unreliable. Sometimes a file's timestamp can help me reconstruct the LOGBOOK. But at times like these I'd wish that Emacs had a history feature.

Actually Emacs does have a few history-like features. One example is savehist-mode,2 which "save[s] the values of minibuffer history variables." Unfortunately, timestamps are not included.

So I configured Emacs to write a timestamp to the *Messages* buffer every time it loads or saves a file. This frees me from switching to a file manager to check a file's timestamp. Using Customize, I added functions to find-file-hook and after-save-hook:3

(find-file-hook
 '(lambda nil
     (message "%s: Loaded %s"
              (format-time-string "%Y-%m-%d %H:%M:%S" (current-time))
              (buffer-file-name))))
(after-save-hook
 '(lambda nil
    (message "%s: Saved %s"
             (format-time-string "%Y-%m-%d %H:%M:%S" (current-time))
             (buffer-file-name))))

Whenever I realize I'm not clocked in, I visit the *Messages* buffer and look for the first occurrence of something like this:

2026-07-20 12:32:33: Saved c:/Users/RayZ/AppData/Roaming/HOME/blog.org

Then I clock in and change the start time to 12:33.4

Similarly, if I've been away from the computer and realize I'm still clocked in, I'll look for the last occurrence of the message, clock out, and change the time accordingly.

The *Messages* buffer is temporary; timestamps from an earlier Emacs session are lost. But this simple system does what I need.

What methods do you use to track project time?


1 The user also can view history as a simple list in reverse chronological order. And the search can span the entire history, if desired.

2 https://doc.endlessparentheses.com/Fun/savehist-mode.html

3 Note that these expressions are arguments to custom-set-variables.

4 Timesheets required time entries in units of hours rounded to one decimal. A tenth hour is 6 minutes. So I configured Org to round clock times to 3 minutes by setting org-clock-rounding-minutes to 3. This affords more granularity. With several clock entries per heading, an even number of 0:03 clock entries would produce the desired tenth hour accuracy.

-1:-- Modeling Browser History in Emacs (Post Raymond Zeitler)--L0--C0--2026-07-21T21:36:54.454Z

Raymond Zeitler: Emacs Tip Of The Day in a Popup Frame

The Emacs commands that create a new frame disappoint me. They create a frame that shares the same set of buffers as the main frame. I don't think a Tip Of The Day (TOTD) popup should have access to all the buffers in a session. I want a popup to display some help and be easily dismissed.1

That's why I designed my Tip Of The Day to run in a separate Emacs instance. Instead of invoking the TOTD function directly, Emacs asks the operating system to launch a second Emacs process. One of the command line switches loads the Lisp file in which the TOTD function is defined, and another runs the function. This is achieved by invoking call-process-shell-command to run a one-line batch file2 that contains this:

"path_to_emacs\emacs.exe" -Q -g 80x42-2+2 -l totd.el --eval (ztotd)

The -l switch loads totd.el, defining both totd and ztotd. The --eval switch then invokes ztotd. (The other function, totd is not used; it's retained for reference.) The -Q switch starts Emacs "quietly." -g defines window geometries:3 WxH+X+Y, where

  • W and H specify the frame's width and height in character units.
  • X and Y, if ≥0, specify the pixel coordinates of the upper left corner of the window. If <0, specify the distance from the right and bottom of the screen.

This is the statement in my init file that runs the batch file:

(call-process-shell-command "cmd.exe /Q /D /C invoke-emacs-totd.bat" nil 0)

Here's the listing for totd.el. The first function, totd, is based on code that Dave Pearson posted on EmacsWiki.4 To make it work with modern Emacs, I replaced (require 'cl) with (require 'cl-lib) and (loop for s...) with (cl-loop for s...).

;; Time-stamp: "2026-07-19 17:00:17 RayZ"
;; totd.el by DavePearson
;; This small code snippet displays a “tip of the day”
;;
;; 2026-07-14 Downloaded from https://www.emacswiki.org/emacs/TipOfTheDay
;; 2026-07-19 Derive ztotd to run in separate session

(require 'cl-lib)

(defun totd ()
 (interactive)
 (with-output-to-temp-buffer "*Tip of the day*"
   (let* ((commands (cl-loop for s being the symbols
                          when (commandp s) collect s))
          (command (nth (random (length commands)) commands)))
     (princ
      (concat "Your tip for the day is:\n========================\n\n"
              (describe-function command)
              "\n\nInvoke with:\n\n"
              (with-temp-buffer
                (where-is command t)
                (buffer-string)))))))

(defun ztotd ()
  "Display documentation of a random Emacs function to provide
a Tip Of The Day.  It is intended to run in a separate
session of Emacs where it modifies the frame to resemble a
minimalist popup.  Invoke at the command line with (for example):

  emacs.exe -Q -g 80x42-2+2 -l totd.el --eval (ztotd)"
 (interactive)
 (set-buffer (generate-new-buffer "Tip of the day"))
   (let* ((commands (cl-loop for s being the symbols
                          when (commandp s) collect s))
          (command (nth (random (length commands)) commands)))
     (insert
      (concat (describe-function command)
              "\n\nInvoke with:\n\n"
              (with-temp-buffer
                (where-is command t)
                (buffer-string))))
     (tool-bar-mode 0)
     (menu-bar-mode 0)
     (setq frame-title-format '("Gnu Emacs Tip of the day"))
     (pop-to-buffer (current-buffer))
     (goto-char (point-min))
     (delete-other-windows)
     (message "Press C-x C-c to dismiss")))

totd is very intrusive by design -- it's supposed to put a help page "in your face." But I wanted a "clean" look. So I eliminated the toolbar and menu from the frame, moved "Tip of the Day" from the buffer to the title, and got rid of other windows. Plus I call it with call-process-shell-command instead of shell-command to prevent a shell output window from appearing in the main frame.5

The batch file allows the OS itself to initiate the display of TOTD. It can be set up to run right after logon, or combined with Tea Timer or Scheduler for a periodic display.

I like how this performs. However, I hardly ever close and restart Emacs. As a result, I may go a week or two without seeing a tip. Is that why Emacs has never included a built-in Tip Of The Day? Or is it just so easy to implement that "the proof has been left as an exercise to the user?" Or maybe the help that's shown is too specific. For example, "What does image-dired-delete-tag actually do?" you might wonder. I think that's the whole point of a TOTD -- to inspire wonder!

If you try this on a non-Windows system, please let me know whether it works.


1 I tried display-buffer-pop-up-frame. Somehow the popup lost focus and got buried behind the main frame. When I closed the main frame, I thought that all my files were saved and Emacs was closed. Only then did I realize the popup was still running. When I tried to close it, Emacs warned about open files. So nervously I maximized the small popup frame to review the unsaved buffers.

2 In fact the batch file contains several lines of comments, too, as well as the cherished @echo off statement at the very beginning. @echo off prevents the many lines of comments from getting dumped to standard output; otherwise those comments would end up in a shell buffer.

3 For more information on command line switches for Emacs, see: https://www.gnu.org/software/emacs/manual/html_node/emacs/Emacs-Invocation.html

4 Please see https://www.emacswiki.org/emacs/TipOfTheDay

5 Thanks to Jackson Ray Hamilton for this suggestion. https://stackoverflow.com/a/22982525

-1:-- Emacs Tip Of The Day in a Popup Frame (Post Raymond Zeitler)--L0--C0--2026-07-19T22:29:30.872Z

Raymond Zeitler: One Hundred Times the Highest Priority

Please note that org‑get‑cust‑priority has been updated. The code that was published on 2026-07-10 causes org‑tags‑list to crash.

In the previous post,1 I was disappointed to learn that valid priority characters are limited to 0–9 and A–Z. I thought it would be neat if the exclamation mark ("!") could represent the ultimate priority, a sort of "drop everything and do this immediately" kind of priority.

It turns out that you can use "!" for this purpose, regardless of which priority characters you've configured. The trick is to write a function and assign it to org-priority-get-priority-function.

The function (which I named org-get-cust-priority) simply looks for "!" in the priority cookie and returns one hundred times the value that would be returned by the highest valid priority. If it doesn't find "!" it simply calls a modified version of the usual org-get-property function,2 which I named org-get-std-priority. The two functions are shown below.3

(defun org-get-cust-priority (s)
  "Return 100 times the highest priority when S contains
a priority cookie with `!'.  Otherwise call the usual
`org-get-property' function. Intended to be the function
`org-priority-get-priority-function' is set to."
  (interactive)
  (if (not (functionp org-priority-get-priority-function))
      (org-get-std-priority s)
    (string-match ".*?\\(\\[#\\(!\\)\\] ?\\)" s)
    (if (and (string-match ".*?\\(\\[#\\(!\\)\\] ?\\)" s) (string-equal (match-string 2 s) "!"))
        (* 100000 (abs (- org-priority-lowest org-priority-highest)))
      (org-get-std-priority s))))

(defun org-get-std-priority (s)
  "Find priority cookie and return priority.
S is a string against which you can match `org-priority-regexp'.
Same function as `org-get-priority' sans test for a custom
function in `org-priority-get-priority-function'."
  (save-match-data
    (if (not (string-match org-priority-regexp s))
        (* 1000 (- org-priority-lowest org-priority-default))
      (* 1000 (- org-priority-lowest
                 (org-priority-to-value (match-string 2 s)))))))

The number that Org uses to sort on priority depends on the values of org-priority-lowest and org-priority-highest,4 which are "C" and "A" by default. In the default configuration, the highest priority has a numeric value of 2000, while the lowest has a value of 0. If the highest and lowest priority characters are 1 and 9, respectively, the highest priority has a value of 8000.

You know you need this if you have about a dozen items on your agenda and half of them are prioritized A.


2026-08-13 Update org-get-cust-priority. Previous version breaks org-tags-list.

1 https://ray-on-emacs.blogspot.com/2026/07/numeric-priorities-in-org-mode.html
2 Modified just to prevent recursion.
3 I know this code isn't particuarly elegant. I could have made custom variables for "!" and the priority multiplier, for example. I'd like to use it for a while before deciding whether it's worth investing any more effort. I do welcome suggestions on how to improve.
4 Or rather, the difference between the highest and lowest priorities.

-1:-- One Hundred Times the Highest Priority (Post Raymond Zeitler)--L0--C0--2026-07-10T18:45:12.923Z

Raymond Zeitler: Numeric Priorities in Org Mode

Org mode is configured "out-of-the-box" with these three levels of priority: A, B, C, where A is the highest priority. However, this can be changed. You can use numbers to indicate priority, such as 1 (highest) to 10 (lowest) as shown in the documentation.1

If you specify priorities from 65 to 90, Org will interpret the numbers as ordinal values of ASCII characters and give you "A" to "Z." If you try to be clever and specify priorities as 65 to 91 (91 is the decimal ASCII code for left square bracket character) you'll be disappointed that when you lower the priority from "Z," you don't get "[." (I was disappointed, anyway.)

You can define priorities from 10 to 99, for example. But if you try to get to a priority higher than 10 using org-priority-up, Org will clear the priority cookie. However, you can manually enter a priority of 0 to ensure the item appears above all other items. (Please see Figure 1, below.) Thus, a priority value of 0 can be your "stat" item. It would be super neat if Org could understand that ASCII characters below 0 (such as the exclamation mark) are an even higher priority than 0. Unfortunately, it doesn't even recognize [#!] as a priority cookie.

A large priority span seems like it would be useful. For example, you might have all utility bills set from 50 to 59, and shopping for birthday or anniversary gifts from 10 to 19. However, in order to go from a priority of 50 to 10, you'd have to press the Up Arrow key forty times. If you think you can invoke M-4 0 M-x org-priority-up to go quickly from 50 to 10, you'll be disappointed (again). The function org-priority-up doesn't accept a prefix argument.2

How do you use priority in Org?


1 Org Manual -- Priorities

2 But you can record a macro that consists of a single S-<up> key press and then play it back with a prefix argument of 39.

-1:-- Numeric Priorities in Org Mode (Post Raymond Zeitler)--L0--C0--2026-07-09T20:08:26.862Z

Raymond Zeitler: Configure Web Browser to be More Emacs-like

Emacs, LibreOffice Writer and Vivaldi are the productivity tools I use most often. So I like to incorporate a feature from one into the others. One example is the ability to initiate a search from selected text. I added this feature to LibreOffice as described here. Another nice feature is the ability to switch between the current buffer and the next (or previous) buffer with C-<TAB> and C-S-<TAB> that I added to Emacs1.

But I've also configured Vivaldi with a useful Emacs feature. Now it responds to C-S-k by invoking "Close tabs to the Right." Emacs devotees know that C-k is bound to kill-line, which "Kill[s] the rest of the current line...."2 If you imagine that the row of tabs is a line of text, it's natural to expect C-k (or C-S-k) would remove all the tabs to the right of the current tab.

Why did I choose the key combination C-S-k instead of C-k? I think at the time C-k performed another function. Or perhaps I felt that it was more appropriate to use two modifier keys for such a significant operation. I rarely need to delete tabs to the left; if I did, I'd bind C-M-S-k to "Close tabs to the Left."

You might utter the following complaint, "But C-S-k doesn't work for Gmail; it brings up a virtual keyboard instead!" Correct. So while we're on the topic of Vivaldi key configurations, I'd like to point out that it's possible to tell Vivaldi to ignore a website's key assignment, allowing the key to pass to Vivaldi. Just search for the setting "Browser Priority Shortcuts," click on one of the existing key combinations and press C-S-k to add it to the others.

I rely on the web browser's tab history a great deal, too. I'm working on implementing something vaguely similar for Emacs."


1 To get the C-<TAB> and C-S-<TAB> to switch buffers, just add this to your init file:

  (keymap-global-set "C-<tab>" 'next-buffer)
  (keymap-global-set "C-S-<tab>" 'previous-buffer)

2 https://www.gnu.org/software/emacs/manual/html_node/emacs/Killing-by-Lines.html

-1:-- Configure Web Browser to be More Emacs-like (Post Raymond Zeitler)--L0--C0--2026-07-08T20:35:47.102Z

Raymond Zeitler: Emacs Carnival: diary, Part 2

As I wrote earlier, I consider diary to be an underappreciated Emacs built-in.

I got interested in it again when I wanted to schedule a recurring meeting in Org for the third Tuesday of every month. I saw on Reddit that some folks use diary-float in place of the usual active timestamp to achieve this. So I added this after the meeting header to make it work:

  * Third Tuesday of the Month Club
  SCHEDULED: <(%%diary-float t 2 3) 19:00-20:00>

I have five meetings that use this method of automatic scheduling. I like this approach because of its simplicity and elegance. Unfortunately, it doesn't behave the same way as a recurring task.

When I mark a recurring task as complete, Org reschedules it for the next date on which it should occur. And it changes DONE to TODO to ensure the event shows up on the agenda for the next occurrence.

This doesn't happen for a task that's scheduled with diary-float. When marked complete, the headline remains at DONE; the next event won't show up on the agenda.

And so Fengyuan Chen wrote next-day-spec1 to solve this issue. Unfortunately it doesn't work with the latest versions of Emacs.

One of the many recognized cognitive biases is called the Sunk Cost Fallacy2, which impels an individual to favor an inferior (or incorrect) solution because a significant amount time, money, and/or effort has been invested in the solution. Well, I've invested time and effort in both diary-float and next-day-spec, so I've continued to endorse it as a solution.

But even if rescheduling did work, I think it's better to have each meeting scheduled as a subheading of the overall meeting topic, like this:

  * Third Tuesday of the Month Club
  ** DONE May 2026: <2026-05-19 Tue>
    :LOGBOOK:
    CLOCK: [2026-05-19 Tue 18:54]--[2026-05-19 Tue 20:12] =>  1:18
    :END:
    - Note taken on [2026-05-19 Tue 21:20] \\
    We ate pizza.  Again.
    ** TODO June 2026: <2026-06-23 Tue>
      ** TODO July 2026: <2026-07-21 Tue>

The advantage to this is that clocked time and notes are organized neatly within each individual subtopic, not aggregated into one logbook and a series of notes. If the aggregated time is desired, one could insert the log report. I'll review this in another post.

But it's fun to schedule events that occur on irregular dates, such as National Engineers Week (US), which is the week in which George Washington's birth anniversary occurs3. Here's how you can add it to an Org file:

  * TODO Celebrate Engineers Week!
  SCHEDULED: <%%(equal (calendar-gregorian-from-absolute (calendar-dayname-on-or-before 0 (calendar-absolute-from-gregorian (list 2 22 (calendar-extract-year date))))) date)>

Do you have a favorite use for dairy?

1 https://github.com/chenfengyuan/elisp/blob/master/next-spec-day.el
2 Sunk cost - Wikipedia
3 https://www.holidayscalendar.com/event/national-engineers-week/
-1:-- Emacs Carnival: diary, Part 2 (Post Raymond Zeitler)--L0--C0--2026-06-22T21:25:47.055Z

Raymond Zeitler: Emacs Carnival: diary, Part 1

When I adopted Emacs in July of 2000, I hunkered down to learn the keybindings. But after I learned the basics1 I started to RTFM (C-h r), and I was instantly drawn to the Calendar/Diary2 node, which I consider to be an underappreciated Emacs built-in, and, therefore, the topic of Emacs Carnival for June 2026.

Back then I mistook "diary" to mean a blank-page-confidant into which we write our thoughts, fears, ambitions or even just what we had for dinner last night. At that time I'd been writing in a journal for about 24 years (yes, since 1976, on and off), so my head swelled with ideas of using Emacs for a revamped computerized version. I imagined being able to forward-search-regexp in order to find, for example, that weird dream I wrote about in which I caught a toad that was hopping around the kitchen and then turned into a hot coal sizzling in my hand...

I used diary-block functions to date my entries and organize the content. I ended up making four such entries from 2003 to 2004. But each day following the entry, M-x diary would display a nearly blank buffer, showing only the day's date at the top followed by a line of equal signs and perhaps a holiday. Where did yesterday's entry go?! It was disconcerting that those words "vanished" into thin air magnetic film or silicon.

Today, however, I appreciate the simplicity of that approach -- a clean slate sans distractions. But back then I was accustomed to seeing a vast amount of my writing, which fed my ego.

But that's not all there is to appreciate. There are several functions that can be used for "an entry" in addition to diary-block, such as diary-anniversary, diary-cyclic, diary-float or even just a simple line that begins with a date and a brief note. This is not an all-inclusive list.

Every fancy diary buffer can show local time of sunrise and sunset; to do this, include diary-sunrise-sunset in the diary file. Include diary-lunar-phases to show one of the four phases of the moon when one is active on that day; the local time of that moon phase will be included.

What follows is an abbreviated and edited listing of my diary files. Note how I've structured diary into a hierarchy using include statements.

2026-06-17 02:25 GMT Important updates: First, you'll need to modify two hook variables in order for the include statements to work. Please see the help for Fancy Diary Display. I also "use the normal hook diary-list-entries-hook to sort each day's diary entries by their time of day," which is described at the top of that page. Second, specify the full path to the included files. I've modified the two include statements to add the "~/" path. This is needed only if you want to press TAB on the item in the Agenda in order to focus the diary entry.

The beauty of this is that these events will show up in Org Agenda at the appropriate times when org-agenda-include-diary is non-nil.

Four examples are shown below. Here are some things to note:

  • The content that's derived from the diary file has "Diary" for value of CATEGORY.
  • The Sunrise / Sunset lines are supposed to show the time of each occurance. However, the time for Sunrise isn't displayed; rather, it's indicated by its position on the time grid. The times for both the New Moon and the Solar Eclipse are indicated by the time grid, as well, and are expected to peak at 13:38 in my area. That's something to look faroward to!
  • Sometimes I include links in my headings. In this case I can follow a link to the credit card website to pay the Visa card.
  • Even a link in the dairy file will be rendered in the Agenda properly, as shown in the reference for Richard Stallman's birthday.
file listing: diary
#    -*- mode: diary -*-
#include "~/diary-anniversaries-property" #include "~/diary-birthdays-friends" %%(diary-sunrise-sunset) %%(diary-lunar-phases) %%(diary-remind '(diary-anniversary 3 16 1953) 14) [[https://html.duckduckgo.com/html/?q=Richard+Stallman+birthday][Richard Stallman's birthday]] in 14 days %%(diary-block 6 21 2004 6 21 2004) The summer solstice. I always take time to observe the position of the sun on this day, especially in the morning and evening. Sunrise was at 5:16am and sunset will be at 8:29pm. From today onward, the days will be getting shorter. At first it will be imperceptible, but in September, it'll be quite noticeable.
file listing: diary-anniversaries-property
#    -*- mode: diary -*-
# Recurring Property events or records
%%(diary-remind '(diary-date 7 3 '(2023 2026 2029)) 30) Car Registration is due
%%(diary-anniversary 8 12 2022) Kitchen Cabinets Painted %d Years Ago
file listing: diary-birthdays-friends
#    -*- mode: diary -*-
# Birthdays of friends
%%(diary-anniversary 8 13) Bucky Thorndike Miller's Birthday (Does Dave still have his guitar amp?)
%%(diary-anniversary 2 13 1966) Ben's %d%s Birthday
Day-agenda (W11):
Tuesday    16 March 2027
  Diary:       7:03 ┄┄┄┄┄ Sunrise (EDT), sunset 18:58 (EDT) at Home (11:54 hrs daylight)
               8:00 ┄┄┄┄┄ ┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄
  BILLS:       9:00 ┄┄┄┄┄ Deadline:   TODO Visa Card LINK            :Bills::Credit:
              10:00 ┄┄┄┄┄ ┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄
              12:00 ┄┄┄┄┄ ┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄
              14:00 ┄┄┄┄┄ ┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄
              16:00 ┄┄┄┄┄ ┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄
              18:00 ┄┄┄┄┄ ┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄
              20:00 ┄┄┄┄┄ ┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄
  Diary:      Richard Stallman's birthday in 14 days

Day-agenda (W23):
Wednesday   3 June 2026
  Diary:       5:19 ┄┄┄┄┄ Sunrise (EDT), sunset 20:20 (EDT) at Home (15:00 hrs daylight)
               8:00 ┄┄┄┄┄ ┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄
              10:00 ┄┄┄┄┄ ┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄
              12:00 ┄┄┄┄┄ ┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄
              14:00 ┄┄┄┄┄ ┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄
              16:00 ┄┄┄┄┄ ┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄
              18:00 ┄┄┄┄┄ ┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄
              20:00 ┄┄┄┄┄ ┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄
  Diary:      Reminder: Only 30 days until Car Registration is due

Day-agenda (W33):
Wednesday  12 August 2026
  Diary:       5:58 ┄┄┄┄┄ Sunrise (EDT), sunset 19:54 (EDT) at Home (13:56 hrs daylight)
               8:00 ┄┄┄┄┄ ┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄
               9:00 ┄┄┄┄┄ ┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄
              10:00 ┄┄┄┄┄ ┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄
              12:00 ┄┄┄┄┄ ┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄
  Diary:      13:38 ┄┄┄┄┄ New Moon (EDT) ** Solar Eclipse **
              14:00 ┄┄┄┄┄ ┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄
              16:00 ┄┄┄┄┄ ┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄
              18:00 ┄┄┄┄┄ ┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄
              20:00 ┄┄┄┄┄ ┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄
  Diary:      Kitchen Cabinets Painted 4 Years Ago

Day-agenda (W32):
Saturday   13 August 2022
  Diary:       5:59 ┄┄┄┄┄ Sunrise (EDT), sunset 19:53 (EDT) at Home (13:53 hrs daylight)
               8:00 ┄┄┄┄┄ ┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄
              10:00 ┄┄┄┄┄ ┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄
              12:00 ┄┄┄┄┄ ┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄
              14:00 ┄┄┄┄┄ ┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄
              16:00 ┄┄┄┄┄ ┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄
              18:00 ┄┄┄┄┄ ┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄
              20:00 ┄┄┄┄┄ ┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄
  Diary:      Bucky Thorndike Miller's Birthday (Does Dave still have his guitar amp?)

1My requirements (the basics) for a text editor were:
  • column editing
  • keystroke recording and playback
  • regular expression search and replace
  • undo
  • support multiple files in multiple windows
2Calendar/Diary in the GNU Emacs Manual

-1:-- Emacs Carnival: diary, Part 1 (Post Raymond Zeitler)--L0--C0--2026-06-16T03:38:44.377Z

Raymond Zeitler: Creating a Reference to a Webpage in Org

I was asked recently, "Do you use Org Mode protocol ... for browser to Emacs interaction? If so, were there any complications to set it up on Windows? Is there like bookmarklets for capturing or doing things?"1

At the time I wasn't doing anything too fancy to incorporate Web Content into Org. I'd copy the URL from Vivaldi's address bar to the clipboard and (if applicable) the Web Content I'm interested in.2 Then in Org, I'd yank the Web Content (if applicable), mark it and then do org-insert-link (C-c C-l) to turn it into a hyperlink. The marked text becomes what you see in the document. But I usually just specify the link Description simply as "LINK." In fact I created a macro (bound to the Insert key) to do this.

But I've started to use eww as my web browser for this. If I write something that I need to verify, I'll mark it and invoke eww-search-words (M-s M-w), which brings me to a DuckDuckGo page of search results. If I follow a search result that I like, I'll mark a small section and invoke org-store-link (C-c l) and create the link with org-insert-link, with the marked content as the Description. Thus org-store-link captures both the location and the context in one magical swoop. Here's an animated GIF that illustrates the process. Note that the video shows me capturing and referring to part of Sacha Chua's website that shows some neat solutions to launching a web browser from Emacs.3


1 Sacha Chua's Emacs Chat with Raymond Zeitler transcript. Please scroll to 35:50

2 I use a clipboard manager so that I can copy content to the clipboard multiple times without clobbering all but the most recent item.

3 https://sachachua.com/blog/2025/07/emacs-open-urls-or-search-the-web-plus-browse-url-handlers/
-1:-- Creating a Reference to a Webpage in Org (Post Raymond Zeitler)--L0--C0--2026-06-11T15:42:30.885Z

Raymond Zeitler: Emacs and the Numeric Keypad

If you have a numeric keypad1, try this in the *scratch* buffer: enable numlock and press the zero on the numeric keyboard (Num-0). Then press the zero located between the alpha keys and the function keys (0). Okay, you see 00, no big deal.

Why do I bring this up? Emacs interprets Num-0 keypress as <kp-0> while it interprets 0 as 0. This means that you could bind Num-0 to a function without affecting the other 0. You could configure Num-0 to insert "zero" by entering and evaluating this in the *scratch* buffer:

(keymap-local-set "<kp-0>" #'(lambda () (interactive) (insert "zero")))

That's a trivial example, of course. But it implies that you can have ten more keys to play with. Similarly, the arithmetic operator keys on the numeric keyboard differ from the "regular ones" that are grouped with the alpha keys. They can be bound to functions, referenced as <kp-add>, <kp-subtract>, <kp-multiply>, <kp-divide>. But these also accept the standard C- M- S- modifier keys, which gives you twelve more possibilities. This is true also for the Insert key <kp-insert> and the Delete key <kp-delete> that double as Num-0 and Num-., respectively.

I have a "Calc" key above the numeric keypad. Unfortunately I can't seem to use it for anything in Emacs -- it opens the OS's calc.exe program regardless of what modifiers I use with it. This is a shame because it would be the ideal mapping for M-x calc. Perhaps this can be altered in BIOS.

But I do use my Win key as a modifier (sometimes). And I've heard that the Caps Lock key can be repurposed. These are topics for other posts.


https://en.wikipedia.org/wiki/Numeric_keypad
-1:-- Emacs and the Numeric Keypad (Post Raymond Zeitler)--L0--C0--2026-06-08T15:51:22.851Z

Alex Ott: One more time about Cedet

In latest versions of Cedet support of GNU Global was introduced, and very useful command - semantic-symref, was implemented. It allows to find places in source code (for C & C++ now) where given function is used. And if GTAGS database wasn't found, then this command tries to find occurrences with find-grep command.
As result, user gets something like this...


-1:-- One more time about Cedet (Post Alex Ott)--L0--C0--2008-12-11T13:00:00.002Z

Please note that planet.emacslife.com aggregates blogs, and blog authors might mention or link to nonfree things. To add a feed to this page, please e-mail the RSS or ATOM feed URL to sacha@sachachua.com . Thank you!