James Cherti: Why Your Emacs Terminal is Slow and How to Fix It (vterm, eat, ghostel, term, and ansi-term)

Emacs terminal buffers like eat, vterm, ghostel, term, and ansi-term can become slow when processing large volumes of standard output. Watching Emacs freeze while a build script dumps thousands of lines is frustrating, but terminal latency is not an unavoidable cost of living inside Emacs. Applying a few targeted configuration changes will eliminate scroll lag and restore immediate responsiveness. This article provides a configuration that speed up terminal buffers.

Before we begin, please consider sharing this article on your website, blog, Mastodon, Reddit, X, LinkedIn, Hacker News, or other social media platforms. Sharing it will help other Emacs users discover how to improve the performance of their terminal emulators.

Complete configuration for high-performance terminal buffers

The following Elisp code speeds up Emacs terminals by applying settings and disabling minor modes that are unnecessary in terminal buffers:

;; Description: Speed up Emacs terminals (vterm, eat, ghostel, term, and ansi-term)
;; Author: James Cherti
;; License: MIT
;; URL: https://www.jamescherti.com/emacs-terminal-performance-vterm-eat-ansi-term-ghostel/
(setq vterm-timer-delay 0.01)
(setq ghostel-timer-delay 0.01)
(setq eat-minimum-latency 0.01)
(setq eat-maximum-latency 0.05)

(setq vterm-max-scrollback 500)
(setq ghostel-max-scrollback (* 1024 1024))
(setq eat-term-scrollback-size (* 64 1024))

(setq eat-enable-shell-prompt-annotation nil)

;; Uncomment -DUSE_SYSTEM_LIBVTERM if you prefer system libvterm
(setq vterm-module-cmake-args
      (concat "-DCMAKE_C_FLAGS='-O3 -march=native -mtune=native' "
              "-DCMAKE_SHARED_LINKER_FLAGS='-Wl,-O2 -Wl,--as-needed' "
              ;; "-DUSE_SYSTEM_LIBVTERM=yes"
              ))

(defun my-speed-up-terminal-buffer ()
  "Reduce unnecessary Emacs features in terminal buffers."
  (let ((ghostel-buffer (derived-mode-p 'ghostel-mode)))
    (setq-local font-lock-defaults '(nil t))

    (setq-local fast-but-imprecise-scrolling t)
    (setq-local redisplay-skip-fontification-on-input t)
    (setq-local scroll-conservatively most-positive-fixnum)
    (setq-local hscroll-margin 0)
    (setq-local scroll-margin 0)
    (setq-local scroll-step 0)
    (setq-local hscroll-step 0)
    (setq-local auto-hscroll-mode nil)

    ;; Uncomment to disable scroll bars to save redisplay cycles
    ;; (setq-local vertical-scroll-bar nil)
    ;; (setq-local horizontal-scroll-bar nil)

    (setq-local truncate-lines t)
    (setq-local nobreak-char-display nil)
    (setq-local bidi-paragraph-direction 'left-to-right)
    (setq-local bidi-inhibit-bpa t)

    ;; Ghostel coordinates row height calculations via ghostel-line-spacing
    (unless ghostel-buffer
      (setq-local line-spacing 0)
      (setq-local mode-line-format nil))

    (setq-local echo-keystrokes 0)

    (setq-local process-adaptive-read-buffering nil)
    (let ((output-max (* 1024 1024)))
      (when (< read-process-output-max output-max)
        (setq-local read-process-output-max output-max)))

    (buffer-disable-undo)

    ;; Evil users
    (remove-hook 'pre-command-hook 'evil--jump-hook t)
    (remove-hook 'post-command-hook 'evil--jump-handle-buffer-crossing t)

    ;; Disable modes
    (let ((inhibit-redisplay t)
          (inhibit-message t)
          (modes '(electric-pair-local-mode
                   electric-indent-local-mode
                   display-line-numbers-mode
                   display-fill-column-indicator-mode
                   hl-line-mode
                   show-paren-local-mode
                   flymake-mode
                   ;; Third-party packages
                   ;; NOTE: Add more modes here
                   flycheck-mode
                   evil-surround-mode
                   evil-snipe-local-mode
                   yas-minor-mode
                   company-mode
                   corfu-mode)))
      ;; ghostel-comint, ghostel-compile, and ghostel-links register a function
      ;; in eldoc-documentation-functions to display target URLs and file under
      ;; point.
      ;;
      ;; Ghostel uses auto-composition-mode in the sync tty composition
      ;; function.
      (unless ghostel-buffer
        (push 'eldoc-mode modes)
        (push 'auto-composition-mode modes))

      (dolist (mode modes)
        (when (and (boundp mode)
                   (symbol-value mode)
                   (fboundp mode))
          (ignore-errors
            (funcall mode -1)))))))

(add-hook 'term-mode-hook 'my-speed-up-terminal-buffer t)
(add-hook 'vterm-mode-hook 'my-speed-up-terminal-buffer t)
(add-hook 'eat-mode-hook 'my-speed-up-terminal-buffer t)
(add-hook 'ghostel-mode-hook 'my-speed-up-terminal-buffer t)

Note: Some of the setq-local settings in this function, excluding the settings that disable minor modes, are provided by modern terminal emulators such as vterm, Eat, and Ghostel. However, each emulator exposes a different set of variables, so this function sets the relevant settings for all supported emulators in one place for consistency and simplicity.

Why these settings help

The code snippet above reduces display, fontification, undo, and minor-mode performance cost in terminal buffers.

Terminal-specific configuration

(setq vterm-timer-delay 0.01)
(setq ghostel-timer-delay 0.01)
(setq eat-minimum-latency 0.01)
(setq eat-maximum-latency 0.05)

(setq vterm-max-scrollback 500)
(setq ghostel-max-scrollback (* 1024 1024))
(setq eat-term-scrollback-size (* 64 1024))

(setq eat-enable-shell-prompt-annotation nil)

;; Uncomment -DUSE_SYSTEM_LIBVTERM if you prefer system libvterm
(setq vterm-module-cmake-args
      (concat "-DCMAKE_C_FLAGS='-O3 -march=native -mtune=native' "
              "-DCMAKE_SHARED_LINKER_FLAGS='-Wl,-O2 -Wl,--as-needed' "
              ;; "-DUSE_SYSTEM_LIBVTERM=yes"
              ))
  • vterm-timer-delay, eat-minimum-latency, eat-maximum-latency, and ghostel-timer-delay: These control how terminal output is batched before redisplay. Lowering these values reduces the delay before output appears, at the cost of more frequent redraws during heavy output. These settings are worth it because they make terminal emulator feel less sluggish.
  • eat-enable-shell-prompt-annotation: Disabling shell prompt annotations avoids the processing associated with maintaining them.
  • vterm-max-scrollback, eat-term-scrollback-size, and ghostel-max-scrollback: These set the amount of history retained by each emulator. ghostel-max-scrollback is measured in bytes, vterm-max-scrollback in lines, and eat-term-scrollback-size in characters.
  • vterm-module-cmake-args: Passes additional CMake arguments when compiling the vterm C module. The -O3, -march=native, and -mtune=native flags request aggressive compiler optimization and CPU-specific tuning. (Building with -march=native optimizes the binary specifically for your current CPU architecture. You can only run this module on machines with the same CPU architecture.)

Line rendering, wrapping, and UI

(setq-local truncate-lines t)
(setq-local nobreak-char-display nil)
(setq-local bidi-paragraph-direction 'left-to-right)
(setq-local bidi-inhibit-bpa t)

;; Ghostel coordinates row height calculations via ghostel-line-spacing
(unless ghostel-buffer
  (setq-local line-spacing 0)
  (setq-local mode-line-format nil))

(setq-local echo-keystrokes 0)
  • truncate-lines: Setting this to t stops long lines from wrapping. This bypasses expensive line-wrapping calculations, allowing the display engine to render text sequentially.
  • nobreak-char-display: Emacs highlights non-breaking spaces and soft hyphens by default. Setting this to nil stops Emacs from overlaying highlight boxes on top of terminal output.
  • line-spacing: TUI applications use box-drawing characters to create borders. Setting this to 0 removes vertical pixel gaps between lines, ensuring pixel-perfect vertical alignment for TUI borders and progress bars.
  • bidi-paragraph-direction and bidi-inhibit-bpa: Emacs scans text by default to determine if it should be rendered right-to-left. Setting bidi-paragraph-direction to 'left-to-right and bidi-inhibit-bpa to t forces left-to-right rendering. This bypasses expensive text scanning. Right-to-left languages will render incorrectly in the shell.
  • echo-keystrokes: Setting this to 0 disables the echoing of unfinished keystrokes in the minibuffer.
  • mode-line-format: Setting this to nil hides the mode-line. This stops Emacs from constantly re-evaluating mode-line functions (which check Git status or active modes) on terminal buffers.

Accelerating display and scrolling

(setq-local fast-but-imprecise-scrolling t)
(setq-local redisplay-skip-fontification-on-input t)
(setq-local scroll-conservatively most-positive-fixnum)
(setq-local hscroll-margin 0)
(setq-local scroll-margin 0)
(setq-local scroll-step 0)
(setq-local hscroll-step 0)
(setq-local auto-hscroll-mode nil)


;; Uncomment to disable scroll bars to save redisplay cycles
;; (setq-local vertical-scroll-bar nil)
;; (setq-local horizontal-scroll-bar nil)
  • fast-but-imprecise-scrolling: Accelerates scrolling by allowing Emacs to skip fontification during rapid scroll events.
  • redisplay-skip-fontification-on-input: Causes the redisplay engine to skip the fontification_functions pass when pending input exists. This improves responsiveness when typing.
  • scroll-conservatively: Setting this variable to most-positive-fixnum makes Emacs scrolls only enough to bring point into view rather than recentering.
  • hscroll-margin and scroll-margin: Define the horizontal and vertical padding around point before scrolling occurs. Setting them to 0 disables this padding, preventing the display engine from performing recentering calculations when point nears the window edge.
  • scroll-step and hscroll-step: Setting these to 0 means the normal centering behavior.
  • auto-hscroll-mode: Disallows automatic horizontal scrolling of windows.
  • vertical-scroll-bar and horizontal-scroll-bar: Disables the scroll bars. Every time new text is inserted, the redisplay engine recalculates the size and position of the scroll bar thumb based on the new buffer size. Disabling scroll bars removes the scroll-bar-related redisplay and UI work for the terminal buffer.

Process I/O and buffering

(setq-local process-adaptive-read-buffering nil)
(let ((output-max (* 1024 1024)))
  (when (< read-process-output-max output-max)
    (setq-local read-process-output-max output-max)))

Increasing read-process-output-max allows Emacs to read up to (* 1024 1024) amounts of output in a single system call. However, a larger chunk size alone is not enough because the default process-adaptive-read-buffering algorithm will still inject micro-sleeps, assuming rapid bursts of escape sequences are caused by a background process that needs throttling.

Ensuring process-adaptive-read-buffering is disabled forces the event loop to stop second-guessing the data stream and immediately consume those chunks as fast as the operating system provides them. (Adaptive read buffering is disabled by default. This is included in case the global value has been modified in your Emacs configuration.)

Disabling syntactic fontification

(setq-local font-lock-defaults '(nil t))

Setting font-lock-defaults to (nil t) turns off automatic fontification for the buffer. The first element specifies that there are no font-lock keywords, while the second sets font-lock-keywords-only, which prevents syntactic fontification.

This is useful for terminal buffers because terminal emulators do not generally need Emacs to apply programming-language fontification to their contents. Terminal applications manage their own colors through terminal escape sequences.

Disabling undo history

(buffer-disable-undo)

Disables recording undo data for the buffer.

Managing Evil mode hooks (Evil-mode users)

(remove-hook 'pre-command-hook 'evil--jump-hook t)
(remove-hook 'post-command-hook 'evil--jump-handle-buffer-crossing t)
  • evil--jump-hook and evil--jump-handle-buffer-crossing: These hooks are used by Evil mode to calculate and store jump list entries on every command. Removing them bypasses the computation and storage of jump list entries for every single command executed in the terminal. The downside is that Evil mode users cannot use C-o or C-i to return to a previous cursor position in terminal buffers.

Disabling intrusive minor modes

(let ((inhibit-redisplay t)
      (inhibit-message t)
      (modes '(electric-pair-local-mode
               electric-indent-local-mode
               display-line-numbers-mode
               display-fill-column-indicator-mode
               hl-line-mode
               show-paren-local-mode
               flymake-mode
               ;; Third-party packages
               ;; NOTE: Add more modes here
               flycheck-mode
               evil-surround-mode
               evil-snipe-local-mode
               yas-minor-mode
               company-mode
               corfu-mode)))
  ;; ghostel-comint, ghostel-compile, and ghostel-links register a function
  ;; in eldoc-documentation-functions to display target URLs and file under
  ;; point.
  ;;
  ;; Ghostel uses auto-composition-mode in the sync tty composition
  ;; function.
  (unless ghostel-buffer
    (push 'eldoc-mode modes)
    (push 'auto-composition-mode modes))

  (dolist (mode modes)
    (when (and (boundp mode)
               (symbol-value mode)
               (fboundp mode))
      (ignore-errors
        (funcall mode -1)))))
  • yas-minor-mode, evil-snipe-local-mode, evil-surround-mode: These modes intercept keystrokes for snippets or character searches. Disabling them bypasses extra keymap lookups and event loop interceptions, reducing latency between keystroke and terminal input.
  • hl-line-mode, display-line-numbers-mode: These modes alter the visual representation of lines. Disabling them prevents the display engine from removing and recreating overlays on every cursor movement or calculating margin widths for every visible line.
  • electric-pair-local-mode, electric-indent-local-mode, show-paren-local-mode, auto-composition-mode: These modes analyze syntax to insert quotes, indent lines, or highlight brackets. Disabling them stops Emacs from running complex regex, syntax table lookups, and text-shaping on every typed character or newline, which speeds up text rendering.
  • company-mode, corfu-mode, flymake-mode, flycheck-mode, eldoc-mode: These modes manage completion, linting, and documentation lookups. Disabling these modes removes their associated hooks, redisplay work, buffer analysis, completion, diagnostics, or other processing from terminal buffers.
-1:-- Why Your Emacs Terminal is Slow and How to Fix It (vterm, eat, ghostel, term, and ansi-term) (Post James Cherti)--L0--C0--2026-09-14T15:00:45.000Z

Irreal: PSA: Homebrew Updated To Version 7.0.0

If you’re a macOS user, you probably know about Homebrew, a repository of easy to install apps and libraries. I use it all the time because it’s so much easier than hunting up all the dependencies required for compiling them yourself.

If you’re a Linux user, you may or may not be familiar with Homebrew. It provides the same services for Linux but my—possible wrong—impression is that its Linux take-up is not as great as it is in macOS.

Regardless, if you’re on Linux or macOS, you should definitely check it out. The TL;DR of this post is that Homebrew has just released version 7.0.0, a major release. That probably wouldn’t be worth an Irreal post except for the “major release” part. On the macOS side, macOS 10.5 and earlier are no longer supported. Being a Mac-head, I didn’t check discontinued versions on Linux so if you’re using Homebrew on Linux you should check that out.

The other big change for Mac-heads is that Intel Macs are moved to Tier 3. You can check out the announcement for exactly what that means but basically it indicates that support is limited and end-of-life is approaching (in September of 2027).

Homebrew is an excellent repository and well worth investigating if you’re not familiar with it. I still have an Intel Mac so it’s a bit of a pain that support is diminishing but I need to upgrade anyway so this is probably the push I need.

-1:-- PSA: Homebrew Updated To Version 7.0.0 (Post Irreal)--L0--C0--2026-09-14T14:55:23.000Z

Sacha Chua: 2026-09-14 Emacs news

Not much commentary this week, but plenty of cool stuff to explore!

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-14 Emacs news (Post Sacha Chua)--L0--C0--2026-09-14T14:09:11.000Z

Lars Ingebrigtsen: Emacs, movies and SQLite

Hey, it’s the… *counts on fingers*… 22nd anniversary of me using Emacs to watch TV and movies!

And in the start, it was a snappy, focused package that just listed directory contents and let me play the files… but then it grew to take care of resuming where I left off, and fixing the frame rate of the TV to fit the video file, and lately it’s also automatically choosing the correct subtitles and audio track.

This means that it’s now calling mediainfo before playing the file to get all that data. And since it’s accessing the files over nfs, and mediainfo apparently has to load the entire file (or large parts) to cough up that data, things have gotten slower:

Look at that! It’s horrible! I have to wait, er, two seconds before I can watch Saturday Night Live?! How can anybody live like this?

Now, sensible video player interfaces compute all this stuff when they incorporate a file. But I didn’t want to do that, because just having some directories of random files that I can move around without doing any synchronisation is just so convenient.

And it worked without that delay until I added the auto-subtitle selection thing.

But… one thing I’ve had to precompute anyway are the thumbnails, and I’ve just plopped them into the directories, which is icky:

Having the video player mangle the source directories like this is not ideal at all. I mean, I don’t look at the mess myself — the Emacs interface hides all this junk — but I know it’s there! *gasp*

So… taking all that into account (the new slowness and this yuckiness) I thought it was time, after 22 years, to bite the bullet and do things the traditional way. I.e., prescan the files and stash everything in an SQLite database. (And besides, the “use no database” thing fell apart very quickly — the file for keeping track of progress was a database file anyway, just very primitive.)

Tada!

Look how fast? Ish. Fastish. mpv still has to load the start of the file over nfs, so it’s not instantaneous, but it’s a fraction of a second, so that’s OK.

And look how pretty:

Putting everything into an SQLite database also means that I can keep track of more data. Previously I only kept track of whether I’d seen something, and how far I’d watched something (so it could resume automatically), but now I can also keep track of how many seconds I’ve watched something, for instance. (Skipping to the ten minute point and then abandoning isn’t the same as watching for ten minutes and then abandoning.)

And I think I still can keep my “just move files around” (to a “rainy day” directory, for instance) workflow — I store file hashes, so even if they move around, they’re recognised when they’re re-encountered.

But now for the real test — my kitchen computer. I use it to watch Star Trek while cooking, and it mounts the /tv/ directory over sshfs over wifi! (The TV computer mounts the directory over nfs over Ethernet, so it has much less latency.) Will it be able to access the SQLite database in a reasonable amount of time?

Yowza! That’s speedy! The only system, which ran mediainfo on the file before starting to play, was very, very slow — it took multiple seconds. The new thing is, like, a million percent faster! That’s bigly faster!

And with all the data stashed in a more convenient way, it’s easier to whip up stuff like this, which answers “when did I last watch DS9, and for how long a time?” (The answer is — “now”, and “not very”.)

The code is on Microsoft Github, for those that are curious.

-1:-- Emacs, movies and SQLite (Post Lars Ingebrigtsen)--L0--C0--2026-09-14T13:16:14.000Z

James Dyer: Emacs 31 Does Git Worktrees Natively: How My vc Setup Compares

Well this is awkward timing. No sooner had I finished bolting worktree management onto built-in vc than I installed the bleeding edge and found Emacs 31 went and did it properly. Right there in the 31 NEWS: a whole C-x v w working-tree map. So before I get too attached to my own code I thought I had better do an honest head-to-head. Verdict up front: I am keeping mine, with one key moved. Here is why.

20260907140012-emacs--Emacs-31-Native-Git-Worktrees-vs-My-vc-Setup.jpg

First, what 31 actually ships under C-x v w:

  • w c (vc-add-working-tree) prompts for a directory, then a branch, but you have to create a branch for yourself, where as my version and also Magit ask for the name of the branch to create, as when creating a new worktree, wouldn't you always want to create a branch? Also with the built-in there is no placeholder initial name, with something involving the branch name, so this command currently feels a little disjointed.
  • w w (vc-switch-working-tree) jumps to the same file in another tree. Lovely idea, completely different from my switch-to-vc-dir and I'm not quite sure if I really care about switching file contexts, I think I just really want to jump to the top level vc-dir
  • w k (vc-kill-other-working-tree-buffers) kills buffers visiting this file elsewhere. Read that twice: it does not delete the tree, despite the letter. So these commands are really very much file-centric. Again I would rather this delete the worktree.
  • w s (vc-working-tree-switch-project) is project-switch-project limited to trees sharing the same backing repo, with a matching project-find-matching-buffer helper for hopping between same-named files, for this I would probably generally be more specific.
  • w a and w A (vc-apply-to-other-working-tree and vc-apply-root-to-other-working-tree) copy a fileset's changes - or the whole tree's - into another tree, and with a prefix argument move them. This is the Transplant mechanic and it is genuinely something mine cannot do, do I need it, not sure, but I have logged it into my noggin.
  • w x (vc-delete-working-tree) deletes the directory outright (to trash) and prunes. No --force dance because there is no git worktree remove involved at all, which also means it works on ancient gits.
  • w R (vc-move-working-tree) relocates, and the implementation is cleverer than mine: on recent git it renames the directory and runs worktree repair, which means it can even move the main tree, something plain git worktree move flatly refuses. Older gits fall back through worktree move to a clean version error. A little on git worktree repair, the .git file contains a repo reference and is backtraced through the original repo, the repair just reset both aspects which is very useful when moving worktrees around and also in that small case where you are running a shared folder repo over SMB, to get the worktree to work in both linux and maybe a host on windows!!

Now the head-to-head, feature by feature. Creating: theirs prompts bare directory then branch to use, but the branch must already be created, mine offers a project-branch sibling default and lands you in vc-dir.

Switching: theirs is file-grained, mine is status-buffer-grained - complementary, not competing. Listing: theirs has no list command at all beyond completion prompts (though the backend exposes vc-git-known-other-working-trees for lisp), while my Z l buffer stays.

Pruning: no built-in command, mine stays. Moving: theirs is strictly better, full stop.

Deleting: theirs is safer on old gits and trashes instead of destroying; mine understands --force and rescues a vc-dir sitting inside the dead tree.

So much for behaviour; now keys, because this is where it bit me. My maps live on Z (worktrees, Magit-style). Against 31's C-x v w map: w k kills buffers where mine removes trees, and w s switches projects where mine opens vc-dir - same letters, different continents. That settled it: the Z prefix stays, precisely because w is now taken with conflicting semantics, and note vc-dir itself has no built-in worktree keys at all, so Z there is uncontested territory.

What I am stealing, or at least circling: the prune-before-listing habit is obviously right, the trash-first delete is kinder than --force bravado, and the repair-based move handling the main tree is something git worktree move simply cannot do. The changeset transplant (w a) is the one I may just use as-is rather than reimplement - some things are better borrowed than rebuilt. What I am not giving up: the vc-dir-first workflow, the sibling defaults, the list buffer.

-1:-- Emacs 31 Does Git Worktrees Natively: How My vc Setup Compares (Post James Dyer)--L0--C0--2026-09-14T12:42:00.000Z

Irreal: Optimizing Line Numbering

James Cherti has been busy producing a lot of posts about optimizing various aspects of Emacs behavior. I’ve just come across his post that discusses optimizing buffer line numbering. It explains the different line numbering schemes and their relative costs.

I not a big user of line numbering. I usually turn it on for the Elfeed index but hardly every use it otherwise unless I have a specific need. In particular, I hardly ever use them in code buffers because I prefer to jump to a particular construct rather than a line number, which, irrationally, always seem to me to be ephemeral.

When I do use line numbers, I like absolute line numbers rather than the two types of relative numbers. That turns out to the right choice as far as efficiency is concerned but many people—including Cherti—prefer relative numbering. That can be expensive, especially for long lines, but there are some settings that can speed things up.

The descriptions of those settings don’t really capture what they do and why you might prefer one value over another so Cherti’s post is especially helpful. If you’re one of the people who like having your buffers numbered in one way or another, take a look at Cherti’s post to see how to get the best possible performance for your choice of representation.

-1:-- Optimizing Line Numbering (Post Irreal)--L0--C0--2026-09-13T15:57:59.000Z

Einar Mostad: An apology to Protesilaos Stavrou and David Wilson

A while back, I read an Emacs News post by Sacha Chua and saw that the Emacs carnival for the month was about mistakes done early in one's Emacs journey. I read this one of the last days of the month, late in the evening, and thought I would chime in before the next theme in the carnival. So I wrote and posted a blog post fast, late at night.

Later when I reread what I had written, I realised it did not say what I was trying to say and instead insulted two brilliant contributers to the Emacs community, so I deleted the post. I have felt bad about it ever since, and I feel that I owe the people I insulted an apology.

What I was trying to say was that I should have spent more time early on to read the built-in documentation in Emacs. If I looked more into the built-in documentation of variables, functions, keyboard shortcuts and read a bit more in the manual, it would have given me a more comprehensive understanding of how Emacs works. I learned a lot by watching videos, but in retrospect, I wish I had also found the time to read up more.

Unfortunately, the blog post read as if I was saying that I should not have watched as many videos by Protesilaos Stavrou and David Wilson as I did, which wasn't really what I meant. I apologise to David Wilson and Protesilaous Stavrou!

Before I started using Emacs, I watched a video by Protesilaos Stavrou who told me that Emacs is an integrated environment that can be tailored to the user's need with less context switching than using [Neo]Vim plus CLI and TUI programs which was what I did at the time. Emacs seemed like something I would like to try, and I have Protesilaos to thank for getting the right frame of mind of what Emacs is even before starting to try it out!

When I started trying out Emacs, I did the Emacs tutorial and then I watched videos by Protesilaos Stavrou and David Wilson that guided me forward in my journey. David Wilson's "Emacs from scratch" series taught me a lot about configuring Emacs early on and Prot's videos on dired, eww, elfeed, diary and calendar taught me about specific modes. These videos made the confusing early days of using Emacs a lot less confusing and a lot more enjoyable!

I have continued to watch their videos and they have given me a lot of useful information and lots of ideas of workflows, modes to explore and settings to use to make Emacs both more efficient and more pleasant to use. I have also enjoyed David Wilson's SystemCrafters videos on Guix. I have learned a lot by watching their videos and I am very grateful for their work!

-1:-- An apology to Protesilaos Stavrou and David Wilson (Post Einar Mostad)--L0--C0--2026-09-13T14:53:00.000Z

Irreal: Bedrock 2.0

Three years ago, I wrote about the Bedrock starter kit for Emacs by Ashton Wiersdorf. Now Wiersdorf has issued a new major release of the kit. It’s mainly an update for Emacs 31 and requires that version to run. I liked his announcement and description of Bedrock 2.0 and decided to write about it on Irreal. To be honest, I’d forgotten that I’d already written about Bedrock and didn’t discover that I had until I chose the obvious name for the post source file (bedrock.org) and found that such a file already existed.

You should read what I wrote before; everything from that post applies to the new version. The TL;DR is that although I don’t generally care for starter kits, I do like Wiersdorf’s because it mainly concerns itself with simple variable settings rather than configuring a bunch of packages that a user may or may not want1. His init.el is well commented to explain what everything does and some items are commented out with an explanation of why you might want to enable them. You can see the file here.

If you’re looking for a simple, minimal configuration for Emacs as a starting point or you know a n00b who is, take a look at Bedrock.

Footnotes:

1

There are, as with the original version, some auxiliary files that bring in some packages that Wiersdorf finds particularly useful.

-1:-- Bedrock 2.0 (Post Irreal)--L0--C0--2026-09-12T15:34:45.000Z

James Cherti: Fixing Slow Scrolling in Emacs display-line-numbers-mode

The display-line-numbers-mode and global-display-line-numbers-mode modes render line numbers in the margin of an Emacs window. Line-number display is implemented in the core C display engine, avoiding the performance cost of legacy Elisp overlays like linum-mode. Despite the native C implementation, and even after configuring Emacs scrolling for better usability, specific configurations can still trigger expensive Lisp evaluations during the interactive command loop, vertical scrolling, and buffer initialization.

These settings avoid unnecessary width calculations and initialization scans.

Speed comparison across line-number types

The display-line-numbers-type variable specifies the type of line numbers activated by display-line-numbers-mode and global-display-line-numbers-mode. It accepts three values:

  • Fastest: t (Absolute): Displays the absolute line number corresponding to the physical buffer line. This is the fastest rendering method because the values are static and do not require recalculation when the cursor moves.
  • Fast: 'relative: Displays the line number relative to the current cursor position (point). This is slightly slower than absolute line numbering because the display engine must recalculate the distance for every visible line on each vertical cursor movement.
  • Slow: 'visual: Displays relative numbers based on visual screen lines, accounting for wrapped text. This is the slowest rendering method because it forces the display engine to compute line wrapping layout before assigning numbers.

Performance also depends on the buffer contents and display configuration. For example, using 'visual numbering can degrade scrolling performance in buffers with long logical lines and word wrapping enabled (when truncate-lines is set to nil), because the display engine must calculate layout metrics to determine screen-line boundaries.

I recommend choosing the numbering type based primarily on the desired behavior, not just performance. For instance, even though absolute line numbering is the fastest, I prefer 'relative numbering. Relative numbers show the distance from the current line, which can make vertical navigation easier, especially with numeric prefixes such as C-u 15 C-n in vanilla Emacs or 15j in Evil mode.

;; Enable relative line numbers globally
(setq display-line-numbers-type 'relative)

Disabling display-line-numbers-grow-only

Avoid setting display-line-numbers-grow-only to a non-nil value, as doing so adds display-line-numbers-update-width to the buffer-local pre-command-hook. (The display-line-numbers-update-width function checks the required line number width before each command and increases the display-line-numbers-width variable when necessary.)

Leaving this variable nil avoids that additional Lisp function call before each command:

;; Do not attach the line number width update function to pre-command-hook
(setq-default display-line-numbers-grow-only nil)

Adjusting the display line numbers width

The display-line-numbers-width variable specifies the minimum width of the line-number area. If the required width exceeds the specified value, Emacs can allocate more space.

(setq-default display-line-numbers-width 3)

This sets a minimum width for the line-number area. Without a predefined width, the display engine can adjust the margin width as line numbers grow during vertical scrolling, such as when the line number changes from 99 to 100.

Disabling the initial line-count scan

The display-line-numbers-width-start variable controls whether Emacs calculates an initial width based on the number of lines in the buffer.

Avoid setting display-line-numbers-width-start to a non-nil value because Emacs then scans the entire buffer when initializing the mode.. When enabled, this setting calls a synchronous (count-lines (point-min) (point-max)) evaluation to compute the total line count before establishing the initial margin width.

For large buffers, keeping this disabled avoids that initialization-time count-lines operation:

;; Do not calculate the initial line number width from the buffer's line count
(setq-default display-line-numbers-width-start nil)
-1:-- Fixing Slow Scrolling in Emacs display-line-numbers-mode (Post James Cherti)--L0--C0--2026-09-12T15:27:23.000Z

Sacha Chua: Experimenting with Orukeet for speech recognition

This is a quick demonstration of using Orukeet as a speech recognition model inside Emacs. I'm recording this in real time on a Lenovo P52 laptop using only CPU. I can replay just one part of my recording. I can switch languages in the middle of recording too. Par exemple, maintenant je parle français. Seems promising.

Links/notes:

View Org source for this post

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

-1:-- Experimenting with Orukeet for speech recognition (Post Sacha Chua)--L0--C0--2026-09-12T13:39:22.000Z

Marcin Borkowski: Duplicating lines and other Emacs miscellanea

A few weeks ago I wrote about how I remapped yank to my command which allows to transform the yanked text. The whole setup – where I press C-y twice to trigger the transformation transient – has one drawback: I sometimes legitimately want to call yank twice in a row. This happens when I need to copy the current line and modify the copy so that I have two similar lines in a row. (I often need this when editing Ledger transactions, for example.) What I sometimes do in such a situation is basically a dance of C-a C-1 C-k C-y C-y. It turns out I don’t need to.
-1:-- Duplicating lines and other Emacs miscellanea (Post Marcin Borkowski)--L0--C0--2026-09-12T06:11:40.000Z

Lars Ingebrigtsen: Spellchecking Total Eclipse with an AI

I’ve spent a day test-driving that Emacs minor mode for spellchecking using an LLM that I wrote the other day. I went through all the posts on the Total Eclipse blog (all 260 of them) to see whether the thing works or not.

And, yes, it works now, but it took some tweaking. Well, I rewrote the mode.

Twice.

My original approach was to give the LLM the entire text, and then ask it to return it to me verbatim, but with changed words tagged up specially. This worked 90% of the time, but it would randomly decide to do something else… and I was never totally sure that it actually returned the entire text. That approach was a bit too nerve-racking.

But Sacha Chua has done something along similar lines, and her approach is to ask the LLM to return just the changed parts:

That sounds like a much more responsible approach, so I redid my package to do pretty much the same… but no matter how I phrased things in the prompt, I couldn’t get the LLM to follow my instructions more than 50% of the time, really.

The hard thing to make it (I’m using Sonnet 5) understand is that it should return a regexp that identifies the fragment uniquely, and that the word to be replaced should be inside a \(…\) group. But:

Some regexps would have zero grouping operators, and some would have several. And sometimes it would wrap the JSON in Markdown:

And worst of all, I could not get it to understand even once that it shouldn’t do this:

I.e., it seems like the LLM did the “regexps” by slapping a (…) around the term to be replaced, and then it put two backslashes before every parenthesis in the string. This is wrong.

So I redid the thing once again, but without regexps. Instead I asked it to return some JSON of the offending phrase (with enough context to uniquely identify it), and what it wants to replace it with:

And then I compute myself the actual difference between the phrases for markup purposes.

After this change, the LLM hasn’t screwed up the format once.

I guess what this really drives home for me is that the apparent enormous strides LLMs have taken the last six months for programming are mostly an illusion: You can now ask an LLM to whip you up, say, a Python script, and it chugs away at it, and then gives you a Python script that’s syntactically correct and mostly does what you asked.

But this isn’t because the LLMs now can generate correct syntax flawlessly. No, this is because the LLMs now have a sandbox they test-run all scripts before handing them over. So they discover that what they’ve generated is junk, and then iterate rapidly over it until it works.

But when I’m using the API here, it can’t check whether the regexps I asked it to generate are valid. It spits out something that’s close, but if it had its normal interactive harness, it would have tested the regexps and seen that they were invalid, and then fixed them.

So… it’s the LLM tooling that tricks us into believing that LLMs are pretty smart at computer stuff now. They aren’t, really.

Anyway, the Total Eclipse blog should now be 100% without typos! Woohoo! And since I’ve eyeballed every change, hopefully the LLM hasn’t changed too many instances of “important” to “load-bearing” without me noticing.

(One amusing thing I’ve noticed is that you can run the same article through the LLM several times, accepting the edits each time. As it finds fewer and fewer real things to complain about, it gets more and more nitpicky. But eventually it gives up and says “nothing to be changed”.)

-1:-- Spellchecking Total Eclipse with an AI (Post Lars Ingebrigtsen)--L0--C0--2026-09-11T20:46:21.000Z

Bicycle for Your Mind: Traverse Through Outlines

traverse icontraverse icon

Traverse | OPML Outliner for macOS

Price: Traverse Lifetime: $17.99. Annual Subscription: $9.99. Monthly Subscription: 0.99

TL;DR::

Traverse is a competent outliner which deals in OPML, Markdown and Fountain files. It is well-designed with a column view which is addictive.

OPML All The Way

Traverse saves its files in the OPML format. “OPML (Outline Processor Markup Language) is an XML format for outlines (defined as”a tree, where each node contains a set of named attributes with string values”). Originally developed by UserLand Software as a native file format for the outliner application in its Radio UserLand product, it has since been adopted for other uses, the most common being to exchange lists of web feeds between web feed aggregators.”[^https://en.wikipedia.org/wiki/OPML]

What that means to the user is “no lock in.” Every outliner in the market place should be able to open and work with OPML files and most do. OmniOutliner, Kosshi, Bike, and Opal are all able to work with OPML files. If at any point, you stop using Traverse, you can use a different outlining program and you have complete access to the files produced by Traverse.

An Unique Outliner

traversetraverse

Traverse is unique in the marketplace. It has a mix of features which are not shared by any outliner in the marketplace. These are some of the ways in which Traverse is different:

  1. It gives you four views into the same outline. There is a regular outline view. This is the one most outlining programs have implemented. There is a Writer view, where those coming from a text editor background will find most familiar. Columns Auto view, which is what the users of the outliner Tree (this seems to have disappeared from both the Apple store and the Internet), might be used to. A variation of that is the Columns Manual view. Four views into the same outline. What that means for the user is that depending on the kind of outline you are making, or the kind of writing you are doing, you have the right environment for the task. I am writing this review in the Writer view.
  2. Traverse does not have its own file format. It saves everything in the OPML format. That is an industry-standard format for outlines and easily dealt with by other outlining programs.
  3. It incorporates a whole system of templates and snippets. Templates to start a new document with a predetermined structure which you have defined. Snippets to add a proven piece of structure to sections in your documents.
  4. Traverse imports, writes and exports Fountain, the screen-writing format. I don’t know any other outliner which does that.

Preferences

Traverse is being worked on at a rapid pace and the screenshots might be out of date as the developer tweaks the product. This is a general idea of what preferences you can set for Traverse.

traverse appearancetraverse appearance

This is the way you can customize the look of the program. It doesn’t have themes but it does have a light and dark mode. The only problem I have with it is the font size. Why 20 point? Why not give the user the ability to set an actual font size.

traverse writingtraverse writing

There is a nice formatting toolbar when you select text. These are my settings. You can change them to suit your way of working.

traverse keyboardtraverse keyboard

I am fond of this panel. I have years of experience with outliners, which means that my fingers have memory. Traverse gives me the ability to customize all of the keyboard commands to suit my muscle memory. This makes it easier to adopt a new application in this category.

traverse new outlinestraverse new outlines

You can set some options for new outlines you create. I like Writer view. There are some other properties you can set.

traverse aitraverse ai

For those of you interested, there is a link to Apple Intelligence. I have it turned off.

traverse exporttraverse export

You get to control export functions in Traverse. I deal with Markdown Documents from Traverse. That is the panel I am showing in this screenshot.

traverse librarytraverse library

traverse library iCloudtraverse library iCloud

You get to choose where you put your outlines and there is iCloud integration, if you need that.

Imports and Exports

traverse importstraverse imports

Traverse imports OPML, iThoughts, Tree, Plain Text, Markdown and Fountain files.

traverse exportstraverse exports

It exports OPML, Markdown List, Markdown Documents, Fountain (I have that turned off), CSV, and the current view as PDF. Also lets you stream the content to Marked.

Using Traverse

Traverse comes across as a simple outliner, but there is power hidden under that façade. It does a good job of providing you with as much complexity as you want to endure. It is fast, and fairly stable. The developer, Stuart Marshall, is responsive and quick to respond to both bug fixes and feature requests.

I have been enjoying the Writer view and the outline view of the application. It supports typewriter scrolling (thank you).

The Competition

Suddenly we have choices in the outliner category in the macOS space. OmniOutliner, Zavala, Kosshi, Bike, and now Traverse are all playing in this space. Zavala is distinguished by being free. OmniOutliner is expensive but the standard in this category. Kosshi is simple, and capable. Bike is fairly mature and working towards the next version. Traverse sits somewhere in the middle of this spectrum. It does some things which no one else does and that is what makes it special.

For me, the outlining category is now served by Org-mode in Emacs. If I want to get away from Emacs and need a change of scenery, I find myself reaching for Kosshi. The urge to go write somewhere else is usually triggered by me temporarily not wanting to deal with the complexity of Emacs. Kosshi fits the bill for that retreat. It is simple and responsive. I am comfortable there. I can see spending time with Traverse and getting comfortable there too. It is well-designed software.

Suggested Improvements

More typographic controls would be welcome. Spaces between lines and paragraphs would enhance the writing experience. Please let us set the font size ourselves.

Conclusion

If you haven’t tried out an outliner and are looking for a comprehensive, well-designed solution, Traverse will not disappoint.

If you are missing the tree view of Tree (the disappeared outliner), Traverse is the only available alternative.

I recommend Traverse wholeheartedly.

Resources

Help Center

Category Traverse posts | Shaking the Habitual

Writing Screenplays with Fountain in Traverse | Shaking the Habitual

macosxguru at the gmail thingie.

Note: The developer provided a promo code for the program when I asked for one.

[^1: https://en.wikipedia.org/wiki/OPML]

-1:-- Traverse Through Outlines (Post Bicycle for Your Mind)--L0--C0--2026-09-11T07:00:00.000Z

Meta Redux: Clojurists Together Update: July and August 2026

Time for another bi-monthly update on the work that Clojurists Together are funding this year - my maintenance of nREPL, CIDER and friends. I published the previous one as a blog post for the first time and the feedback was good enough that I’ll keep doing it.

Last time I said that I had plucked most of the low-hanging fruit and that the next two months were unlikely to be as productive. Well, I was wrong. CIDER 2.0 finally shipped, and once it was out the door I used the momentum to sweep through pretty much every corner of the nREPL/CIDER ecosystem. A few long-neglected projects got proper releases, and nREPL got a couple of brand new implementations in languages I play on the side from time to time.

The big highlights from my perspective:

  • CIDER 2.0 (“Terceira”) is out, followed by 2.0.1, and 2.1 is taking shape on master
  • clj-refactor 4.0 is out
  • Sayid went from 0.4 to 0.8 in the span of three weeks
  • Drawbridge, nREPL’s HTTP transport, got its first meaningful release in years
  • nREPL went polyglot: nREPL servers for Erlang and Elixir and an OCaml client
  • clj-suitable 0.7 and 0.8 closed most of the gap between ClojureScript and Clojure completion
  • A lot of work landed on nREPL’s master (TLS hardening, URL-based connections, docs) and a new release is right around the corner

Below you’ll find more details about the work I did, project by project.

CIDER

CIDER 2.0 (“Terceira”) landed on July 15, right on the schedule I had announced in the preview post. For once in my life I was actually on time! The big themes were covered there and in the release announcement (transient menus, inline macro stepping, call-graph browsers, source-based find-references, the tracing and tap buffers, rich content in the REPL), so here’s just what changed between the preview and the final release:

  • cider-doctor, which checks your Emacs setup and the active nREPL session for common problems and produces a report you can paste in a bug report
  • an orchard value for cider-print-fn, selecting cider-nrepl’s much faster orchard.pp pretty-printer
  • SSH tunnels now forward a free local port, so remote REPLs sharing a port no longer collide on localhost
  • C-c C-d at the stdin prompt sends end-of-input, and stdin is routed to the exact connection that asked for it
  • a long tail of nREPL client fixes: a slow memory leak on the eldoc/completion path, nrepl-dict-merge mutating a shared literal, notifications treated as format strings

CIDER 2.0.1 followed a week later with fixes for the problems early adopters ran into: evaluation in a dependency’s source buffer erroring with “No linked CIDER sessions” (in several variants), cider-enlighten-mode never lighting anything up (a 1.22 regression), the macroexpansion commands refusing to expand let/fn/loop, and load-file potentially freezing Emacs on a huge result. Nothing dramatic, but I’m glad people were quick to report those.

After that master (the future CIDER 2.1) kept moving at a steady pace. A few of the things that landed there:

  • CIDER’s dynamic font-locking (REPL-defined macros, functions, deprecated/instrumented/traced symbols) now works better in clojure-ts-mode buffers via tree-sitter. Previously it worked “officially” only under clojure-mode. The debugging reader tags are highlighted there too.
  • A new cider-preferred-clojure-mode controls which mode CIDER uses to font-lock the code it renders - REPL results, doc examples, overlays and its own display buffers. clojure-ts-mode is finally a first-class citizen in CIDER.
  • Symbol prompts can go through completing-read (so Vertico/Ivy/Helm kick in) and completion annotations render as an aligned type/namespace column in Corfu, Vertico and the built-in *Completions*. More on this in Modernizing CIDER’s Completion.
  • Connecting got smarter. Container-published nREPL ports are resolved for /docker: and /podman: buffers, lein trampoline REPLs are detected, .nrepl-port files are no longer discarded on systems without lsof, and there’s a new “How CIDER Finds Ports” section in the manual.
  • Every form command got an “at point” variant (inspect, pprint, macroexpand, format, insert in REPL), there’s a cider-inspect-menu listing every way to start an inspection, and the contents of comment forms are treated as top level by the whole defun command family.
  • Stray output from long-lived background processes (say, a core.async go-loop still printing under a finished eval’s id) is now routed to the REPL instead of being dropped with a warning.

One more thing. I shipped “smarter form targeting” on master - the evaluation commands resolving the form from where the cursor actually is, rather than the form before it - wrote about it, got a lot of feedback, and reverted it a few days later. CIDER 2.1 will keep the classic Emacs semantics. Fifteen years in, the existing behaviour is the contract, not an implementation detail I get to tidy up. The detour wasn’t wasted, though: it surfaced a bug where the text of a line comment was treated as code, and the “at point” family of commands is a direct result of it.

cider-nrepl

Three cider-nrepl releases in July, wrapping up the tools.deps migration and driving the CIDER 2.0 launch:

  • cider-nrepl 0.62.0 finalized the Leiningen to tools.deps migration, simplified deferred middleware loading, documented the op response keys (with a test verifying the descriptor contract) and shipped the hardened content-type and slurp middleware that made rich content safe to enable by default.
  • cider-nrepl 0.62.1 fixed a whole cluster of debugger bugs. Record literals no longer get downgraded to plain maps by instrumentation, deftype/defrecord method bodies are skipped (goodbye Unable to resolve symbol: STATE__), and enlightening deftest bodies works again.
  • cider-nrepl 0.62.2 pruned trace and tap subscriptions with dead transports (a dead subscriber used to break every traced evaluation), stopped the debugger from shadowing enlighten’s evaluator, brought the docs back in sync with the code and added a section for tool authors.

Orchard

Orchard 0.44.0 shipped on July 4, mostly thanks to Sashko’s inspector work (a replace command, truncated table columns, ARef contents rendered fully). My part was a round of tests for the less covered namespaces and, later on master, a fix for orchard.print ignoring custom print-method implementations for records and collections. Thanks, Sashko!

clj-refactor 4.0

clj-refactor.el 4.0 is the release I had been promising for a few cycles. It requires Emacs 28.1+ and CIDER 2.0+, and it’s a big one:

  • project-wide refactorings (rename symbol, change signature, inline symbol) now show a diff preview before touching disk, and cljr-undo-last-refactoring reverts the last one in a single step
  • the slow refactorings run asynchronously, so Emacs no longer freezes while the middleware analyzes the project
  • cljr-change-function-signature can add and remove parameters and handles multi-arity functions
  • a clj-refactor-menu transient replaces the hydra menus, and the multiple-cursors, hydra and inflections dependencies are gone
  • many commands degrade gracefully without a REPL (cljr-clean-ns, cljr-slash, cljr-add-missing-libspec, cljr-remove-let, cljr-promote-function)
  • cljr-slash can add and hotload a missing library, artifact lists are cached, and the namespaced refactor-nrepl ops are used when available

I still think the long-term home for the most useful bits is CIDER and clojure-mode, but at least the project is in good shape while I figure that out. There’s a bit more in the release post.

clj-suitable

clj-suitable, the ClojureScript completion backend, was another project that had been coasting for years:

  • clj-suitable 0.7.0 adapted to Piggieback 0.7’s delegating repl-env, modernized every dependency, replaced the Leiningen build with tools.build, moved CI to GitHub Actions and added a shadow-cljs integration test over a real Node runtime.
  • clj-suitable 0.8.0 brought the static completion much closer to compliment: fuzzy matching (pr-fn completes print-function), compliment-style ranking, completion of local bindings (destructuring included) and of referred vars inside :refer vectors. It also fixed the REPL’s *1/*2/*3 getting clobbered by completions and a few long-standing shadow-cljs and Node.js issues.

ClojureScript users - I’d love to hear how the new completion feels in practice.

Sayid

The Sayid revival continued at a brisk pace, with five releases between July 1 and July 17:

  • Sayid 0.4.0 dropped the com.billpiel namespace prefix, added data-returning variants of the workspace and query ops, and introduced a client-rendered, foldable tree view of the recorded call tree built on CIDER’s cider-tree-view.
  • Sayid 0.5.0 made recording bounded: a record limit, per-function limits, sampling, a max trace depth and bounded printing. Tracing a namespace under a test suite can’t eat all your memory anymore.
  • Sayid 0.6.0 rebuilt inner tracing on tools.analyzer.jvm, replacing the fragile source-rewriting instrumenter.
  • Sayid 0.7.0 added sayid.data (the recorded call tree as plain data, with tap> integration for Portal and friends) and sayid.golden, a golden-trace testing helper.
  • Sayid 0.8.0 focused on the experience: a sayid-menu transient, plain-language feedback from the trace commands, getting-started hints in empty views, and a fix for the inspector integration that had been broken for years.

I wrote a bit more about the last one here. Not bad for a project that was completely dead in June, right?

Drawbridge

Drawbridge is nREPL’s HTTP transport, created by Chas Emerick in 2012 and “technically maintained” ever since. I finally gave it the attention it needed:

  • Drawbridge 0.3.1 updated the dependencies (nREPL 1.7, Ring 1.15) and throttled client polling so it stops flooding servers with GET requests.
  • Drawbridge 0.4.0 is the interesting one. It adds drawbridge.bridge, a local nREPL socket server that relays to a remote Drawbridge endpoint, so any socket-based client (CIDER, Calva, rebel-readline) can now talk to Drawbridge. There’s also a WebSocket transport with server push instead of long-polling, bearer-token authentication via secure-ring-handler (which refuses to run unauthenticated unless you insist), and a deps.edn, so it’s usable as a git dependency.

The full story is in Lowering the Drawbridge.

nREPL

No nREPL release this cycle, but master is shaping up nicely for 1.8:

  • the built-in command-line client can connect using a URL, including the nrepls:// and nrepl+unix: URLs that TLS and filesystem-socket servers advertise, and http(s):// when Drawbridge is on the classpath
  • TLS hardening: descriptive errors for invalid key material, Ed25519 keys, tolerating a swapped certificate order, and a documented security model
  • the built-in client sends input to the server as raw text, so reader typos, auto-resolved keywords and custom tagged literals no longer crash it
  • stdin fixes: EOF arriving behind buffered input is reported properly, and a race between the stdin consumer and producer is gone
  • nrepl.spec finally matches what describe and ls-sessions actually send
  • a pile of documentation debt cleared (lookup return values, the session-closed status, the -f/--repl-fn option, middleware best practices) and a CI check keeping ops.adoc in sync with the descriptors
  • Clojure 1.10 is the new minimum and nrepl.misc/requiring-resolve is gone in favour of the core one

The nrepl.org site also picked up links to several new clients and servers (Nautilos, nREPL.hx for Helix, Janet and Steel Scheme servers). The nREPL family keeps growing, which makes me happy every single time.

nREPL on the BEAM

nrepl-beam is a brand new project I started in July, mostly because I wanted to see how well the nREPL spec holds up when implemented from scratch outside the JVM. It’s home to:

  • dialtone, an nREPL server for Erlang (and a server core for the whole BEAM)
  • repartee, the Elixir server built on top of it
  • chaser, a terminal nREPL client that works with any nREPL server

nrepl-beam 0.1.0 shipped on July 14. Both servers implement the full op set (eval with streamed output, sessions, interrupts, stdin, load-file, completions, lookup) and pass neat’s cross-implementation integration suite alongside Clojure, Babashka and Basilisp. Writing them was a good test of the spec, and it produced a few of the documentation fixes listed above. Turns out that the best way to find holes in a spec is to implement it in a language you barely know.

mezcaml

In the same spirit, mezcaml is a minimal nREPL client for OCaml: a small client library plus a command-line REPL, working against any nREPL server regardless of the language on the other end. No release yet, but the core protocol works, it reads whole forms, and it has server-driven completion. Nothing serious - it was a fun way to combine my recent OCaml hacking with nREPL.

clojure-mode, clojure-ts-mode and MrAnderson

Smaller things: the #_ toggle commands in clojure-mode were renamed to clojure-toggle-discard and friends (matching Clojure’s own terminology, old names kept as aliases), both modes got a :to-have-face matcher for font-lock tests, and clojure-ts-mode now checks the indentation of its sources on CI.

MrAnderson 0.7.1 added a command-line interface, so it can be run without Leiningen, and reworked its downstream integration tests against cider-nrepl and refactor-nrepl, which had silently stopped exercising local changes. Oops.

Blog posts

I wrote a lot this summer, mostly a series on the notable changes in CIDER 2.0:

Epilogue

Big thanks to Clojurists Together, Nubank and the other organizations and people supporting my Clojure OSS work! None of this would have happened without you. You rock!

As for what’s next - CIDER 2.1 is the obvious milestone, and it’s mostly a matter of letting the clojure-ts-mode integration settle. After that I’d like to cut nREPL 1.8 with the TLS and URL work, and get mezcaml and the BEAM servers to a point where they are useful to someone other than me. I won’t make any predictions about productivity this time around. Clearly I’m bad at those.

Keep hacking!

-1:-- Clojurists Together Update: July and August 2026 (Post Meta Redux)--L0--C0--2026-09-10T09:30:00.000Z

Lars Ingebrigtsen: Some LLM spell check experiences

A few days back, I hacked up a little minor mode for Emacs to use an LLM for spell checking. (Before you get a heart attack — I did not say “editing”, “getting ideas” or “research”: Just spell checking. Using an LLM for actual writing isn’t just a bad idea — it leads to abhorrent results.)

I’ve been running a random selection of blog posts I wrote for the Comico read through blog to see whether the thing works well — and… yes! But also no.

OK, positives first:

1) For simple typos, it’s very good indeed — it finds them all.

c) For word substitution errors, like “to” instead of “too”, it’s also very good.

II) For grammar errors, it’s more mixed. It’s good at, for instance, basic noun/verb agreement, so if I’ve written “they writes”, it fixes that. But if the noun clause is long and complex, it doesn’t always react. I’ve experimented with adding/removing an “s” from the verb in sentences like that, and it sometimes lets both versions through without comment.

⁵) Since I’m usually giving the LLM the complete blog post to proof-read, it knows what area I’m writing about, and so it’s able to correct me when I misspell names, too. I hadn’t even considered that, but that was a very nice surprise. So when I’m writing about comics and casually mention “the art looks a bit like Kriegstein”, it knows to correct that to “Krigstein”. This is something that traditional spell check can’t do at all. (And amusingly enough, running this article through the LLM doesn’t tag that “they writes” up there as an error, because it gets that that was an example of an error.)

So that’s all very good. So what’s the bad bits? Well, it’s an LLM, so it’s slow. That’s not a major problem, though, since I’m just using it to give a blog post a look-over to weed out the worst spelling mistakes. But I’ve added some code to strip out (longer) HTML fragments before sending the text over to the LLM, and this helps a bit.

The biggest problem is that an LLM is an LLM and will randomly decide to not follow the instructions in the prompt. Sometimes it adds random elements to the text here and there. Sometimes it says that I should fix a word from “identical” to “identical”. Sometimes it removes a paragraph. This doesn’t happen often — perhaps in 5% of the cases? But…

LLMs are unreliable! It cannot be! *gasp*

(Somebody speculated that this unreliability is a feature for the LLM companies — because you know that you may get bad results, you get extra happy the times you get a good result. The slot machine thing? I think that’s probably the case — some people get really addicted to their LLMs…)

Fortunately, these things can be programmed around — I’ve now added code to do sanity checks to the output from the LLM before using it. So if the LLM decides to go off piste, the package detects that, and then you can just do the query again.

It used to be that doing the same thing and expecting a different result was a sign of insanity, but with an LLM, that’s fine.

It’s a brave new world out there.

It’s also … odd … to type commands in Emacs and then they cost money.

Anyway, I’ve pushed these fixes to Microsoft Github, so there you go.

-1:-- Some LLM spell check experiences (Post Lars Ingebrigtsen)--L0--C0--2026-09-10T08:14:38.000Z

James Cherti: Why Emacs Consult Async Searches Feel slow and How to Speed Them Up? (consult-fd, consult-find, consult-grep, consult-ripgrep...)

The consult Emacs package provides asynchronous search commands such as consult-fd, consult-find, consult-grep, consult-git-grep, and consult-ripgrep. For these commands, Consult does not necessarily start a new search for every change to the minibuffer input. Instead, it uses configurable debounce and throttle delays to control when asynchronous processes start, while a separate refresh delay controls how frequently asynchronous results are pushed to the completion UI.

The first time I tried Consult, I thought to myself: this feels slow compared to Counsel (I had a similar feeling about Emacs settings such as show-paren-delay). With Counsel, results updated immediately on every keystroke, while Consult seemed to hesitate for a fraction of a second before updating the list.

As it turns out, this behavior is entirely intentional. The current Consult defaults are conservative by design.

Aggressive asynchronous search

If you prefer lower latency and speed, these variables can be made more aggressive:

(setq consult-async-input-debounce 0.05
      consult-async-input-throttle 0.1
      consult-async-refresh-delay 0.05)

These values reduce the amount of time Consult waits before starting another asynchronous search and allow the completion UI to refresh much more frequently.

These values do not make the underlying search programs execute faster. External tools like ripgrep already use highly optimized, multi-threaded algorithms to scan files across multiple CPU cores. Instead, these variables speed up the feedback loop inside Emacs itself. They reduce the waiting period before Consult spawns the external process, and causing the Emacs UI to pull in new asynchronous data and redraw the screen at a much faster rate.

Variable: consult-async-input-debounce

(setq consult-async-input-debounce 0.05)

This setting forces Consult to wait 0.05 seconds after your last keystroke before launching an asynchronous process. Because this acts as a debounce delay rather than a fixed polling interval, every new input resets the timer. The search only executes once the full quiet period elapses.

This prevents Emacs from spinning up redundant background tasks for every intermediate character you type, which minimizes CPU overhead and keeps the UI responsive.

Variable: consult-async-input-throttle

(setq consult-async-input-throttle 0.1)

The consult-async-input-throttle variable acts as a hard rate limit for how often Consult starts asynchronous processes. With this value, a new process starts at most once every 0.1 seconds, regardless of how fast you type.

To break down the difference between the two concepts:

  • Debouncing is an idle timer. It waits for you to stop typing. If you keep hitting keys, the timer keeps resetting, and the search will not run until you stop typing.
  • Throttling is a strict speed limit. It enforces a maximum execution rate. Even if you pause just long enough between words to constantly trigger the debounce timer, the throttle ensures the system will not exceed the permitted rate.

Variable: consult-async-refresh-delay

(setq consult-async-refresh-delay 0.05)

The consult-async-refresh-delay variable controls how frequently Consult updates the completion UI with results from asynchronous commands. This value makes Consult refresh at intervals as short as 0.05 seconds when new results require an update. It does not cause Emacs to redraw continuously when there is nothing new to display.

A low refresh delay can make search results appear faster, but redisplay itself has a cost. Emacs redisplay is expensive enough that highly frequent updates can increase CPU and garbage-collection activity.

Choosing values

If you prefer asynchronous searches to feel as responsive as possible, these values offer a good balance between low input latency and resource usage. Consult responds quickly to minibuffer changes and updates the completion UI with minimal delay.

These settings are well suited to fast computers plugged into a power source. On a laptop, the aggressive process creation and continuous UI updates will keep the CPU active and drain your battery much faster than the conservative defaults. On slower computers or when searching very large projects, the default values may provide a better balance by avoiding unnecessary searches while input is still changing.

The optimal values depend on typing speed, project size, search-tool performance, power constraints, and the cost of processing and redisplaying asynchronous results in Emacs. Lowering the delays is therefore best viewed as an explicit trade-off: less input latency in exchange for more frequent asynchronous work.

-1:-- Why Emacs Consult Async Searches Feel slow and How to Speed Them Up? (consult-fd, consult-find, consult-grep, consult-ripgrep...) (Post James Cherti)--L0--C0--2026-09-08T16:54:16.000Z

Irreal: Jinx Update Number …

… I’ve lost track. I’ve been using Jinx for my spelling correction for a while now and really like it. When I first started, my main problem was that the selection procedure described in the documentation didn’t work for me. I was almost certain that was because of an interaction with Ivy; my commenters agreed and offered some things to try.

After some experimentation, I still wasn’t able to get that aspect of Jinx working. I decided to wait for Emacs 31 to revisit the issue. Emacs 31 is here but I still haven’t addressed the problem. Most of the reason for that is that I’ve developed some efficient muscle memory for dealing with the problem. I have jinx-correct bound to Ctrl+; and when I call jinx-correct, I continue holding down Ctrl and can easily move down in the list of alternatives by pressing n. Usually the first choice is the one I want so it’s only a single press. Then I type Return to update the text.

That seems like a little more work than simply pressing a number but I don’t need to move my hands or reach to do it so it seems easy to me. I’ll probably do a bit more research and get the default method working when I get a chance but in the mean time, I’m pretty happy with how it’s working for me now.

I’m really enjoying how easy it is to put an “unknown spelling” in a variety of places—the current file, the current directory, or in a personal global dictionary—and am happy I switched. If you haven’t tried it yet, you should give it a spin.

Update [2026-09-09 Wed 11:55]: Fixed title typo.

-1:-- Jinx Update Number … (Post Irreal)--L0--C0--2026-09-08T14:49:28.000Z

Lars Ingebrigtsen: Calendaring

The other week I made a calendrical display for the eink thing I have. The problem is that I don’t really use a calendar — I guess the main reason is because I hate planning, but the other reason is because I really dislike all the calendar entry interfaces I’ve used.

I haven’t used all that many, though, so I may well be missing some ingenious calendar out there. But what I’ve been resentfully using is the standard Google calendar on Android.

I filmed one of my interactions with this thing, and I swear — I didn’t try to make it more awkward than it is for me:

I’d be climbing up the walls if I were to try to use it more than I do now — I just put the absolute bare necessities in there, like dentist appointments and Boris shows. (One coming up in November!)

But now I have this calendar in the hall… wouldn’t it be nice to have it display more stuff? Stuff that I really should be remembering, anyway?

There’s also org-gcal, if you’re in the Org ecosystem… but… I’m not.

Does a command line interface to Google Calendar work? Because I want to have the calendar in my pocket, too. And… Yes, there’s gcalcli. (Which I noticed hasn’t been updated for two years and is probably going to be deprecated, but it works for now, so I haven’t bothered to redo my code.)

After going through the byzantine process on Google Cloud Console to create an “application”:

Yes! You have to create a logo! Behold my magnificent logo!

Anyway, after all that… tada:

Yes! The system works!

It does the normal Do What I Mean Thing — “fri 9” means “next Friday at 9am”, and “aug 11 1930” means “half past seven on August 11”, etc:

I’m freee! I never have to enter a calendar entry on the mobile phone ever again!

I’ve put the resulting code on Microsoft Github.

-1:-- Calendaring (Post Lars Ingebrigtsen)--L0--C0--2026-09-08T14:48:46.000Z

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

James Dyer: Git Worktrees 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 Worktrees Without Leaving Built-in VC (Post James Dyer)--L0--C0--2026-09-06T08:30:00.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 view 1 comment or 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

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!