<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
<title>the DyerDwelling!</title>
<link href="https://www.dyerdwelling.family/"/>
<link rel="self" href="https://www.dyerdwelling.family/index.xml"/>
<id>https://www.dyerdwelling.family/</id>
<updated>2026-04-21T08:00:00+0100</updated>
<author><name>James Dyer</name></author>
<entry>
<title>Highlighting git changes in a buffer with diff-hl</title>
<link href="https://www.dyerdwelling.family/emacs/20260421070329-emacs--getting-diff-hl-just-right/"/>
<id>https://www.dyerdwelling.family/emacs/20260421070329-emacs--getting-diff-hl-just-right/</id>
<updated>2026-04-21T08:00:00+0100</updated>
<content type="html">&lt;p&gt;
Lately I’ve found myself wanting a better, more fine-grained view of what’s going on in a file under &lt;code&gt;git&lt;/code&gt;. For some reason, my default workflow has been to keep jumping in and out of &lt;code&gt;project-vc-dir&lt;/code&gt; to check changes. It gets the job done, but honestly it’s a bit of a hassle.
&lt;/p&gt;


&lt;div id=&quot;org1aafb7c&quot; class=&quot;figure&quot;&gt;
&lt;p&gt;&lt;img class=&quot;img-fluid rounded&quot; src=&quot;/static/emacs/20260421070329-emacs--Getting-diff-hl-Just-Right.jpg&quot; alt=&quot;20260421070329-emacs--Getting-diff-hl-Just-Right.jpg&quot; width=&quot;100%&quot; /&gt;
&lt;/p&gt;
&lt;/div&gt;

&lt;p&gt;
What I really wanted was something right there in the buffer. Not a full-on inline diff (that gets messy fast I would guess), but just a small visual hint, something that lets me &quot;see&quot; what’s changed without breaking my flow.
&lt;/p&gt;

&lt;p&gt;
Turns out, that’s exactly what &lt;code&gt;diff-hl&lt;/code&gt; does
&lt;/p&gt;

&lt;p&gt;
It’s super lightweight and just highlights changes in the fringe. Nothing flashy but just enough to keep you aware of what you’ve modified. Once you start using it, it feels kind of weird not having it.
&lt;/p&gt;

&lt;p&gt;
One thing I really like is how nicely it plays with the built-in VC tools, move to a buffer position that aligns with a highlighted change, hit &lt;code&gt;C-x v =&lt;/code&gt; and it jumps straight to the relevant hunk in the diff. No friction, no extra thinking, it just works.
&lt;/p&gt;

&lt;p&gt;
Here’s the setup I’m using:
&lt;/p&gt;

&lt;div class=&quot;org-src-container&quot;&gt;
&lt;pre&gt;&lt;code class=&quot;language-elisp&quot;&gt;(use-package diff-hl
  :ensure t
  :hook (dired-mode . diff-hl-dired-mode)
  :config
  (global-diff-hl-mode 1)
  (diff-hl-flydiff-mode 1)
  (unless (display-graphic-p)
    (diff-hl-margin-mode 1)))
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;
By default, &lt;code&gt;diff-hl-mode&lt;/code&gt; only updates when you save the file. That’s okay, but enabling &lt;code&gt;diff-hl-flydiff-mode&lt;/code&gt; makes it update as you type, which feels more intuitive.
&lt;/p&gt;

&lt;p&gt;
Oh, and that &lt;code&gt;dired-mode&lt;/code&gt; hook? That turns on &lt;code&gt;diff-hl-dired-mode&lt;/code&gt;, which gives you a quick visual overview of changed files right inside &lt;code&gt;dired&lt;/code&gt;. It’s one of those small touches that ends up being surprisingly useful.
&lt;/p&gt;

&lt;p&gt;
If you’ve got &lt;code&gt;repeat-mode&lt;/code&gt; enabled, you can also hop through changes with &lt;code&gt;C-x v ]&lt;/code&gt; and &lt;code&gt;C-x v [&lt;/code&gt;, which makes reviewing edits really smooth.
&lt;/p&gt;

&lt;p&gt;
I am enjoying &lt;code&gt;diff-hl&lt;/code&gt; and is quietly improving my workflow without getting in my way. Simple, fast, and just really nice to have.
&lt;/p&gt;
</content>
</entry>
<entry>
<title>Emacs-DIYer: A Built-in dired-collapse Replacement</title>
<link href="https://www.dyerdwelling.family/emacs/20260409104443-emacs--emacs-diyer-a-built-in-dired-collapse-replacement/"/>
<id>https://www.dyerdwelling.family/emacs/20260409104443-emacs--emacs-diyer-a-built-in-dired-collapse-replacement/</id>
<updated>2026-04-15T19:20:00+0100</updated>
<content type="html">&lt;p&gt;
I have been slowly chipping away at my &lt;a href=&quot;https://github.com/captainflasmr/Emacs-DIYer&quot;&gt;Emacs-DIYer&lt;/a&gt; project, which is basically my ongoing experiment in rebuilding popular Emacs packages using only what ships with Emacs itself, no external dependencies, no MELPA, just the built-in pieces bolted together in a literate &lt;code&gt;README.org&lt;/code&gt; that tangles to &lt;code&gt;init.el&lt;/code&gt;.  The latest addition is a DIY version of &lt;code&gt;dired-collapse&lt;/code&gt; from the &lt;code&gt;dired-hacks&lt;/code&gt; family, which is one of those packages I did not realise I leaned on until I started browsing a deeply-nested Java project and felt the absence immediately.
&lt;/p&gt;


&lt;div id=&quot;org13a5894&quot; class=&quot;figure&quot;&gt;
&lt;p&gt;&lt;img class=&quot;img-fluid rounded&quot; src=&quot;/static/emacs/20260409104443-emacs--Emacs-DIYer-A-Built-in-dired-collapse-Replacement.jpg&quot; alt=&quot;20260409104443-emacs--Emacs-DIYer-A-Built-in-dired-collapse-Replacement.jpg&quot; width=&quot;100%&quot; /&gt;
&lt;/p&gt;
&lt;/div&gt;

&lt;p&gt;
If you have ever opened a dired buffer on something like a Maven project, or &lt;code&gt;node_modules&lt;/code&gt;, or a freshly generated resource bundle, you will know the pain, &lt;code&gt;src/&lt;/code&gt; contains a single &lt;code&gt;main/&lt;/code&gt; which contains a single &lt;code&gt;java/&lt;/code&gt; which contains a single &lt;code&gt;com/&lt;/code&gt; which contains a single &lt;code&gt;example/&lt;/code&gt;, and you are pressing &lt;code&gt;RET&lt;/code&gt; four times just to get to anything interesting.  The &lt;code&gt;dired-collapse&lt;/code&gt; minor mode from &lt;code&gt;dired-hacks&lt;/code&gt; solves this beautifully, it squashes that whole single-child chain into one dired line so &lt;code&gt;src/main/java/com/example/&lt;/code&gt; shows up as a single row and one &lt;code&gt;RET&lt;/code&gt; drops you straight into the deepest directory.
&lt;/p&gt;

&lt;p&gt;
So, as always with the Emacs-DIYer project, I wondered, can I implement this in a few elisp defuns?
&lt;/p&gt;

&lt;p&gt;
Right, so what is the plan?, dired already draws a nice listing with permissions, sizes, dates and filenames, all I really need to do is walk each line, look at the directory, figure out the deepest single-child descendant, and then rewrite the filename column in place with the collapsed path.  The trick, and this is the bit that took me a minute to convince myself of, is that dired uses a &lt;code&gt;dired-filename&lt;/code&gt; text property to know where the filename lives on the line, and &lt;code&gt;dired-get-filename&lt;/code&gt; happily accepts relative paths containing slashes.  So if I can rewrite the text and reapply the property, everything else, &lt;code&gt;RET&lt;/code&gt;, marking, copying, should just work without me having to touch the rest of dired at all!
&lt;/p&gt;

&lt;p&gt;
First function, &lt;code&gt;my/dired-collapse--deepest&lt;/code&gt;, which just walks the directory chain as long as each directory contains exactly one accessible child directory.  I added a 100-iteration guard so a pathological symlink cycle cannot wedge the whole thing, which, you know, future me might thank present me for:
&lt;/p&gt;

&lt;div class=&quot;org-src-container&quot;&gt;
&lt;pre&gt;&lt;code class=&quot;language-elisp&quot;&gt;(defun my/dired-collapse--deepest (dir)
  &quot;Return the deepest single-child descendant directory of DIR.
Walks the directory chain as long as each directory contains exactly
one entry which is itself an accessible directory.  Stops after 100
iterations to guard against symlink cycles.&quot;
  (let ((current dir)
        (depth 0))
    (catch &apos;done
      (while (&amp;lt; depth 100)
        (let ((entries (condition-case nil
                           (directory-files current t
                                            directory-files-no-dot-files-regexp
                                            t)
                         (error nil))))
          (if (and entries
                   (null (cdr entries))
                   (file-directory-p (car entries))
                   (file-accessible-directory-p (car entries)))
              (setq current (car entries)
                    depth (1+ depth))
            (throw &apos;done current)))))
    current))
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;
&lt;code&gt;directory-files-no-dot-files-regexp&lt;/code&gt; is one of those lovely little built-in constants I keep forgetting exists, it filters out &lt;code&gt;.&lt;/code&gt; and &lt;code&gt;..&lt;/code&gt; but keeps dotfiles, which is exactly what you want if you are deciding whether a directory is truly single-child.
&lt;/p&gt;

&lt;p&gt;
Second function does the actual buffer surgery, &lt;code&gt;my/dired-collapse&lt;/code&gt; iterates each dired line, grabs the filename with &lt;code&gt;dired-get-filename&lt;/code&gt;, asks the walker how deep the chain goes, and if there is anything to collapse it replaces the displayed filename with the collapsed relative path:
&lt;/p&gt;

&lt;div class=&quot;org-src-container&quot;&gt;
&lt;pre&gt;&lt;code class=&quot;language-elisp&quot;&gt;(defun my/dired-collapse ()
  &quot;Collapse single-child directory chains in the current dired buffer.
A DIY replacement for `dired-collapse-mode&apos; from the dired-hacks
package.  Rewrites the filename portion of each line in place and
reapplies the `dired-filename&apos; text property so that standard dired
navigation still resolves to the deepest directory.&quot;
  (when (derived-mode-p &apos;dired-mode)
    (let ((inhibit-read-only t))
      (save-excursion
        (goto-char (point-min))
        (while (not (eobp))
          (condition-case nil
              (let ((file (dired-get-filename nil t)))
                (when (and file
                           (file-directory-p file)
                           (not (member (file-name-nondirectory
                                         (directory-file-name file))
                                        &apos;(&quot;.&quot; &quot;..&quot;)))
                           (file-accessible-directory-p file))
                  (let ((deepest (my/dired-collapse--deepest file)))
                    (unless (string= deepest file)
                      (when (dired-move-to-filename)
                        (let* ((start (point))
                               (end (dired-move-to-end-of-filename t))
                               (displayed (buffer-substring-no-properties
                                           start end))
                               (suffix (substring deepest
                                                  (1+ (length file))))
                               (new (concat displayed &quot;/&quot; suffix)))
                          (delete-region start end)
                          (goto-char start)
                          (insert (propertize new
                                              &apos;face &apos;dired-directory
                                              &apos;mouse-face &apos;highlight
                                              &apos;dired-filename t))))))))
            (error nil))
          (forward-line))))))
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;
The key bit is the &lt;code&gt;propertize&lt;/code&gt; call at the end, the new filename text has to carry &lt;code&gt;dired-filename t&lt;/code&gt; so that &lt;code&gt;dired-get-filename&lt;/code&gt; picks it up, and &lt;code&gt;dired-directory&lt;/code&gt; on &lt;code&gt;face&lt;/code&gt; keeps the collapsed entry looking the same as a normal directory line.  Because &lt;code&gt;dired-get-filename&lt;/code&gt; will happily glue a relative path like &lt;code&gt;main/java/com/example&lt;/code&gt; onto the dired buffer&apos;s directory, pressing &lt;code&gt;RET&lt;/code&gt; on a collapsed line takes you straight to &lt;code&gt;src/main/java/com/example&lt;/code&gt; with no extra work from me.
&lt;/p&gt;

&lt;p&gt;
A while back I added a little unicode icon overlay thing to dired (&lt;code&gt;my/dired-add-icons&lt;/code&gt;, which puts a little symbol in front of each filename via a zero-length overlay), and I did not want the collapse to fight with it.  The icons hook into &lt;code&gt;dired-after-readin-hook&lt;/code&gt; as well, so I just gave collapse a negative depth when attaching its hook:
&lt;/p&gt;

&lt;div class=&quot;org-src-container&quot;&gt;
&lt;pre&gt;&lt;code class=&quot;language-elisp&quot;&gt;(add-hook &apos;dired-after-readin-hook #&apos;my/dired-collapse -50)
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;
Lower depth runs earlier, so collapse rewrites the line first, then the icon overlay attaches to the final collapsed filename position.  Without this, the icons would happily sit in front of a stub directory that was about to be rewritten, which is, well, fine I suppose, but it felt tidier to have them anchor on the post-collapse text.
&lt;/p&gt;

&lt;p&gt;
Before, a typical Maven project root might look something like this:
&lt;/p&gt;

&lt;pre class=&quot;example&quot; id=&quot;org4a556aa&quot;&gt;
drwxr-xr-x 3 jdyer users 4096 Apr  9 08:12 ▶ src
drwxr-xr-x 2 jdyer users 4096 Apr  9 08:11 ▶ target
-rw-r--r-- 1 jdyer users  812 Apr  9 08:10 ◦ pom.xml
&lt;/pre&gt;

&lt;p&gt;
After collapse kicks in:
&lt;/p&gt;

&lt;pre class=&quot;example&quot; id=&quot;org5431f6b&quot;&gt;
drwxr-xr-x 3 jdyer users 4096 Apr  9 08:12 ▶ src/main/java/com/example
drwxr-xr-x 2 jdyer users 4096 Apr  9 08:11 ▶ target
-rw-r--r-- 1 jdyer users  812 Apr  9 08:10 ◦ pom.xml
&lt;/pre&gt;

&lt;p&gt;
One &lt;code&gt;RET&lt;/code&gt; and you are in &lt;code&gt;com/example&lt;/code&gt;, which is where all the actual code lives anyway.  Marking, copying, deleting, renaming, all of it still behaves because the &lt;code&gt;dired-filename&lt;/code&gt; text property points at the real deepest path.
&lt;/p&gt;

&lt;p&gt;
One thing that initially bit me, is navigating &lt;b&gt;out&lt;/b&gt; of a collapsed chain.  If I hit &lt;code&gt;RET&lt;/code&gt; on a collapsed &lt;code&gt;src/main/java/com/example&lt;/code&gt; line I land in the deepest directory, which is great, but then pressing my usual &lt;code&gt;M-e&lt;/code&gt; to go back up was doing the wrong thing.  &lt;code&gt;M-e&lt;/code&gt; in my config has always been bound to &lt;code&gt;dired-jump&lt;/code&gt;, and &lt;code&gt;dired-jump&lt;/code&gt; called from inside a dired buffer does a &quot;pop up a level&quot; thing that ended up spawning a fresh dired for &lt;code&gt;com/&lt;/code&gt;, bypassing the collapsed view entirely and leaving me staring at a directory I never wanted to see.
&lt;/p&gt;

&lt;p&gt;
My first attempt at fixing this was to put some around-advice on &lt;code&gt;dired-jump&lt;/code&gt; so that if an existing dired buffer already had a collapsed line covering the jump target, it would switch to that buffer and land on the collapsed line instead of splicing in a duplicate subdir.  It worked, sort of, but &lt;code&gt;dired-jump&lt;/code&gt; in general felt a bit janky inside dired, it does a lot of &quot;refresh the buffer and try again&quot; under the hood and the in-dired pop-up-a-level path was always the weak link.  So I stepped back and split the two cases apart with a tiny dispatch wrapper:
&lt;/p&gt;

&lt;div class=&quot;org-src-container&quot;&gt;
&lt;pre&gt;&lt;code class=&quot;language-elisp&quot;&gt;(defun my/dired-jump-or-up ()
  &quot;If in Dired, go up a directory; otherwise dired-jump for current buffer.&quot;
  (interactive)
  (if (derived-mode-p &apos;dired-mode)
      (dired-up-directory)
    (dired-jump)))

(global-set-key (kbd &quot;M-e&quot;) #&apos;my/dired-jump-or-up)
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;
From a file buffer, &lt;code&gt;dired-jump&lt;/code&gt; is still exactly the right thing as you want the directory the file is in of course.  From inside a dired buffer, &lt;code&gt;dired-up-directory&lt;/code&gt; is just a much cleaner operation, it walks up one real level, no refresh, no splicing, nothing weird.  But on its own that would lose the collapsed round-trip, so I gave &lt;code&gt;dired-up-directory&lt;/code&gt; its own bit of advice that looks for a collapsed-ancestor buffer before falling through to the default behaviour.
&lt;/p&gt;

&lt;div class=&quot;org-src-container&quot;&gt;
&lt;pre&gt;&lt;code class=&quot;language-elisp&quot;&gt;(defun my/dired-collapse--find-hit (target-dir)
  &quot;Return (BUFFER . POS) of a dired buffer with a collapsed line covering TARGET-DIR.&quot;
  (let ((target (file-name-as-directory (expand-file-name target-dir)))
        hit)
    (dolist (buf (buffer-list))
      (unless hit
        (with-current-buffer buf
          (when (and (derived-mode-p &apos;dired-mode)
                     (stringp default-directory))
            (let ((buf-dir (file-name-as-directory
                            (expand-file-name default-directory))))
              (when (and (string-prefix-p buf-dir target)
                         (not (string= buf-dir target)))
                (save-excursion
                  (goto-char (point-min))
                  (catch &apos;found
                    (while (not (eobp))
                      (let ((line-file (ignore-errors
                                         (dired-get-filename nil t))))
                        (when (and line-file
                                   (file-directory-p line-file))
                          (let ((line-dir (file-name-as-directory
                                           (expand-file-name line-file))))
                            (when (string-prefix-p target line-dir)
                              (setq hit (cons buf (point)))
                              (throw &apos;found nil)))))
                      (forward-line))))))))))
    hit))
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;
The &lt;code&gt;dired-up-directory&lt;/code&gt; only fires when the literal parent is &lt;b&gt;not&lt;/b&gt; already open as a dired buffer, which keeps normal upward navigation completely unchanged:
&lt;/p&gt;

&lt;div class=&quot;org-src-container&quot;&gt;
&lt;pre&gt;&lt;code class=&quot;language-elisp&quot;&gt;(defun my/dired-collapse--up-advice (orig-fn &amp;amp;optional other-window)
  &quot;Around-advice for `dired-up-directory&apos; restoring collapsed round-trip.&quot;
  (let* ((dir (and (derived-mode-p &apos;dired-mode)
                   (stringp default-directory)
                   (expand-file-name default-directory)))
         (up (and dir (file-name-directory (directory-file-name dir))))
         (parent-buf (and up (dired-find-buffer-nocreate up)))
         (hit (and dir (null parent-buf)
                   (my/dired-collapse--find-hit dir))))
    (if hit
        (let ((buf (car hit))
              (pos (cdr hit)))
          (if other-window
              (switch-to-buffer-other-window buf)
            (pop-to-buffer-same-window buf))
          (goto-char pos)
          (dired-move-to-filename))
      (funcall orig-fn other-window))))

(advice-add &apos;dired-up-directory :around #&apos;my/dired-collapse--up-advice)
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;
If &lt;code&gt;/proj/src/main/java/com/&lt;/code&gt; happens to already exist as a dired buffer, &lt;code&gt;dired-up-directory&lt;/code&gt; does its usual thing and just goes there, the up-advice never fires.  It is only when the literal parent is absent that the advice kicks in and hands you back to the collapsed ancestor, which I think is the right tradeoff, the advice never surprises you when you were going to get the standard behaviour anyway, it only steps in when the standard behaviour would throw away context you clearly still had in a buffer somewhere.
&lt;/p&gt;

&lt;p&gt;
End result, &lt;code&gt;RET&lt;/code&gt; into a collapsed chain drops me deep, &lt;code&gt;M-e&lt;/code&gt; walks me back out to the original collapsed line, and none of it requires doing anything clever with &lt;code&gt;dired-jump&lt;/code&gt;&apos;s &quot;pop up a level&quot; path, which I am increasingly convinced I should not have been using in the first place.
&lt;/p&gt;

&lt;p&gt;
Everything lives in the &lt;a href=&quot;https://github.com/captainflasmr/Emacs-DIYer&quot;&gt;Emacs-DIYer&lt;/a&gt; project on GitHub, in the literate &lt;code&gt;README.org&lt;/code&gt;.  If you just want the snippet to drop into your own init file, the two functions and the &lt;code&gt;add-hook&lt;/code&gt; line above are the whole thing, no &lt;code&gt;require&lt;/code&gt;, no &lt;code&gt;use-package&lt;/code&gt;, no MELPA, just built-in dired and a bit of buffer shenanigans, and thats it!, phew, and breathe!
&lt;/p&gt;
</content>
</entry>
<entry>
<title>Wiring Flymake Diagnostics into a Follow Mode</title>
<link href="https://www.dyerdwelling.family/emacs/20260409061315-emacs--wiring-flymake-diagnostics-into-a-follow-mode/"/>
<id>https://www.dyerdwelling.family/emacs/20260409061315-emacs--wiring-flymake-diagnostics-into-a-follow-mode/</id>
<updated>2026-04-09T06:13:00+0100</updated>
<content type="html">&lt;p&gt;
Flymake has been quietly sitting in my config for years doing exactly what it says on the tin, squiggly lines under things that are wrong, and I mostly left it alone. But recently I noticed I was doing the same little dance over and over: spot a warning, squint at the modeline counter, run `M-x flymake-show-buffer-diagnostics`, scroll through the list to find the thing I was actually looking at, then flip back. Two windows, zero connection between them.
&lt;/p&gt;

&lt;p&gt;
So I wired it up properly, and while I was in there I gave it a set of keybindings that feel right to my muscle memory.
&lt;/p&gt;


&lt;div id=&quot;org3e337f3&quot; class=&quot;figure&quot;&gt;
&lt;p&gt;&lt;img class=&quot;img-fluid rounded&quot; src=&quot;/static/emacs/20260409061315-emacs--Wiring-Flymake-Diagnostics-into-a-Follow-Mode.jpg&quot; alt=&quot;20260409061315-emacs--Wiring-Flymake-Diagnostics-into-a-Follow-Mode.jpg&quot; width=&quot;100%&quot; /&gt;
&lt;/p&gt;
&lt;/div&gt;

&lt;p&gt;
The obvious bindings for stepping through errors are `M-n` and `M-p`, and most people using flymake bind exactly those. The problem is that in my config `M-n` and `M-p` are already taken, they step through simply-annotate annotations (which is itself a very handy thing and I am not giving it up!). So I shifted a key up and went with the shifted variants: `M-N` for next, `M-P` for previous, and `M-M` to toggle the diagnostics buffer.
&lt;/p&gt;

&lt;div class=&quot;org-src-container&quot;&gt;
&lt;pre&gt;&lt;code class=&quot;language-elisp&quot;&gt;(setq flymake-show-diagnostics-at-end-of-line nil)
(with-eval-after-load &apos;flymake
  (define-key flymake-mode-map (kbd &quot;M-N&quot;) #&apos;flymake-goto-next-error)
  (define-key flymake-mode-map (kbd &quot;M-P&quot;) #&apos;flymake-goto-prev-error))
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;
With &lt;code&gt;M-M&lt;/code&gt; I wanted it to be a bit smarter than just &quot;open the buffer&quot;. If it is already visible I want it gone, if it is not I want it up. The standard toggle pattern:
&lt;/p&gt;

&lt;div class=&quot;org-src-container&quot;&gt;
&lt;pre&gt;&lt;code class=&quot;language-elisp&quot;&gt;(defun my/flymake--diag-buffer ()
  &quot;Return the visible flymake diagnostics buffer, or nil.&quot;
  (seq-some (lambda (b)
              (and (with-current-buffer b
                     (derived-mode-p &apos;flymake-diagnostics-buffer-mode))
                   (get-buffer-window b)
                   b))
            (buffer-list)))

(defun my/flymake-toggle-diagnostics ()
  &quot;Toggle the flymake diagnostics buffer.&quot;
  (interactive)
  (let ((buf (my/flymake--diag-buffer)))
    (if buf
        (quit-window nil (get-buffer-window buf))
      (flymake-show-buffer-diagnostics)
      (my/flymake-sync-diagnostics))))
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;
Now the interesting bit. What I really wanted was a follow mode, something like how the compilation buffer tracks position or how Occur highlights the current hit. When my point lands on an error in the source buffer, the corresponding row in the diagnostics buffer should light up. That way the diagnostics window becomes a live index of where I am rather than a static dump and think in general this is how a lot of other IDEs work.
&lt;/p&gt;

&lt;p&gt;
I tried the lazy route first, turning on hl-line-mode in the diagnostics buffer and calling hl-line-highlight from a post-command-hook in the source buffer. The line lit up once and then refused to move. Nothing I did would shift it. This is because hl-line-highlight is really only designed to be driven from the window whose line is being highlighted, and I was firing it from afar.
&lt;/p&gt;

&lt;p&gt;
Ok, so why not just manage my own overlay:
&lt;/p&gt;

&lt;div class=&quot;org-src-container&quot;&gt;
&lt;pre&gt;&lt;code class=&quot;language-elisp&quot;&gt;(defvar my/flymake--sync-overlay nil
  &quot;Overlay used to highlight the current entry in the diagnostics buffer.&quot;)

(defun my/flymake-sync-diagnostics ()
  &quot;Highlight the diagnostics buffer entry matching the error at point.&quot;
  (when-let* ((buf (my/flymake--diag-buffer))
              (win (get-buffer-window buf))
              (diag (or (car (flymake-diagnostics (point)))
                        (car (flymake-diagnostics (line-beginning-position)
                                                  (line-end-position))))))
    (with-current-buffer buf
      (save-excursion
        (goto-char (point-min))
        (let ((found nil))
          (while (and (not found) (not (eobp)))
            (let ((id (tabulated-list-get-id)))
              (if (and (listp id) (eq (plist-get id :diagnostic) diag))
                  (setq found (point))
                (forward-line 1))))
          (when found
            (unless (overlayp my/flymake--sync-overlay)
              (setq my/flymake--sync-overlay (make-overlay 1 1))
              (overlay-put my/flymake--sync-overlay &apos;face &apos;highlight)
              (overlay-put my/flymake--sync-overlay &apos;priority 100))
            (move-overlay my/flymake--sync-overlay
                          found
                          (min (point-max) (1+ (line-end-position)))
                          buf)
            (set-window-point win found)))))))
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;
My first pass at the walk through the tabulated list did not work. I was comparing (tabulated-list-get-id) directly against the diagnostic returned by flymake-diagnostics using eq, and it was always false, which meant found stayed nil forever and the overlay never moved. A dive into flymake.el revealed why. Each row in the diagnostics buffer stores its ID as a plist, not as the diagnostic itself:
&lt;/p&gt;

&lt;div class=&quot;org-src-container&quot;&gt;
&lt;pre&gt;&lt;code class=&quot;language-nil&quot;&gt;(list :diagnostic diag
      :line line
      :severity ...)
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;
So I need to pluck out :diagnostic before comparing. Obvious in hindsight, as these things always are. With plist-get in place the comparison lines up and the overlay moves exactly where I want it, tracking every navigation command.
&lt;/p&gt;

&lt;p&gt;
The fallback lookup using line-beginning-position and line-end-position is there because flymake-diagnostics (point) only returns something if point is strictly inside the diagnostic span. When I land between errors or on the same line as an error but a few columns off, I still want the diagnostics buffer to track, so I widen the search to the whole line.
&lt;/p&gt;

&lt;p&gt;
Finally, wrap the hook in a minor mode so I can toggle it per buffer and enable it automatically whenever flymake comes up:
&lt;/p&gt;

&lt;div class=&quot;org-src-container&quot;&gt;
&lt;pre&gt;&lt;code class=&quot;language-elisp&quot;&gt;(define-minor-mode my/flymake-follow-mode
  &quot;Sync the diagnostics buffer to the error at point.&quot;
  :lighter nil
  (if my/flymake-follow-mode
      (add-hook &apos;post-command-hook #&apos;my/flymake-sync-diagnostics nil t)
    (remove-hook &apos;post-command-hook #&apos;my/flymake-sync-diagnostics t)))

(add-hook &apos;flymake-mode-hook #&apos;my/flymake-follow-mode)
(define-key flymake-mode-map (kbd &quot;M-M&quot;) #&apos;my/flymake-toggle-diagnostics)
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;
The end result is nice. M-M pops the diagnostics buffer, M-N and M-P walk through the errors, and as I navigate the source the matching row in the diagnostics buffer highlights in step with me. If I close the buffer with another M-M everything goes quiet, and I can still step through with M-N/M-P on their own.
&lt;/p&gt;

&lt;p&gt;
Three little keybindings and twenty lines of elisp, but they turn flymake from a static reporter into something that actually feels connected to where I am in the buffer.
&lt;/p&gt;
</content>
</entry>
<entry>
<title>Simply Annotate 0.9.8: Threaded Conversations on Your Code</title>
<link href="https://www.dyerdwelling.family/emacs/20260329084330-emacs--simply-annotate-0.9.8:-threaded-conversations-on-your-code/"/>
<id>https://www.dyerdwelling.family/emacs/20260329084330-emacs--simply-annotate-0.9.8:-threaded-conversations-on-your-code/</id>
<updated>2026-03-29T09:08:00+0100</updated>
<content type="html">&lt;p&gt;
I have been busy improving my annotation package! &lt;a href=&quot;https://github.com/captainflasmr/simply-annotate&quot;&gt;Simply Annotate&lt;/a&gt;, the latest release is 0.9.8 and I have put in a bunch of new features, so it felt like a good time to step back and show what the package actually does at this point, because honestly, quite a lot has changed since I last wrote about it.
&lt;/p&gt;


&lt;div id=&quot;org677cb18&quot; class=&quot;figure&quot;&gt;
&lt;p&gt;&lt;img class=&quot;img-fluid rounded&quot; src=&quot;/static/emacs/simply-annotate-banner.jpg&quot; alt=&quot;simply-annotate-banner.jpg&quot; width=&quot;100%&quot; /&gt;
&lt;/p&gt;
&lt;/div&gt;

&lt;p&gt;
There are annotation packages out there already, &lt;code&gt;annotate.el&lt;/code&gt; being the most established.  And they are good!  But I kept running into the same friction: I wanted threaded conversations directly on my code, I wanted multiple display styles I could combine, and I wanted the whole thing to be a single file with no dependencies that I could drop onto an air-gapped machine and just use (yup, that again!)
&lt;/p&gt;

&lt;p&gt;
So I built my own!, this is Emacs, after all.
&lt;/p&gt;


&lt;div id=&quot;org01652ae&quot; class=&quot;figure&quot;&gt;
&lt;p&gt;&lt;img class=&quot;img-fluid rounded&quot; src=&quot;/static/emacs/simply-annotate-screen-recording.gif&quot; alt=&quot;simply-annotate-screen-recording.gif&quot; width=&quot;100%&quot; /&gt;
&lt;/p&gt;
&lt;/div&gt;

&lt;p&gt;
At its core, simply-annotate lets you attach persistent notes to any text file (or Info manual, or dired buffer) without modifying the original content.  Annotations are stored in a simple s-expression database at &lt;code&gt;~/.emacs.d/simply-annotations.el&lt;/code&gt;.  The entire package is a single elisp file, requires Emacs 28.1+, and has zero external dependencies.
&lt;/p&gt;

&lt;p&gt;
Basic setup is two lines:
&lt;/p&gt;

&lt;div class=&quot;org-src-container&quot;&gt;
&lt;pre&gt;&lt;code class=&quot;language-elisp&quot;&gt;(use-package simply-annotate
  :bind-keymap (&quot;C-c a&quot; . simply-annotate-command-map)
  :hook (find-file-hook . simply-annotate-mode))
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;
Or if you prefer &lt;code&gt;require&lt;/code&gt;:
&lt;/p&gt;

&lt;div class=&quot;org-src-container&quot;&gt;
&lt;pre&gt;&lt;code class=&quot;language-elisp&quot;&gt;(require &apos;simply-annotate)
(global-set-key (kbd &quot;C-c a&quot;) simply-annotate-command-map)
(add-hook &apos;find-file-hook #&apos;simply-annotate-mode)
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;
Open a file, select some text, press &lt;code&gt;C-c a j&lt;/code&gt;, and you have your first annotation.  &lt;code&gt;M-n&lt;/code&gt; and &lt;code&gt;M-p&lt;/code&gt; step through them.
&lt;/p&gt;

&lt;p&gt;
I am always fiddling around with styles, themes, backgrounds e.t.c, so I thought I would build this tinkering enthusiasm into this package.  Simply-annotate has five display styles, and you can layer them together:
&lt;/p&gt;

&lt;ul class=&quot;org-ul&quot;&gt;
&lt;li&gt;&lt;b&gt;Highlight&lt;/b&gt; &amp;#x2013; classic background colour on the annotated region&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Tint&lt;/b&gt; &amp;#x2013; a subtle background derived from your current theme, lightened by a configurable amount.  Adapts automatically when you switch themes&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Fringe&lt;/b&gt; &amp;#x2013; a small triangle indicator in the fringe, minimal and unobtrusive&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Fringe-bracket&lt;/b&gt; &amp;#x2013; a vertical bracket spanning the full annotated region in the fringe, with a proper top cap, continuous vertical bar, and bottom cap&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Subtle&lt;/b&gt; &amp;#x2013; overline and underline bracketing the region, barely visible but there when you need it&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;
You can combine them, so &lt;code&gt;(tint fringe-bracket)&lt;/code&gt; gives you a gentle background wash with a clear fringe bracket showing exactly where the annotation spans.  Cycle through styles with &lt;code&gt;C-c a &apos;&lt;/code&gt;.
&lt;/p&gt;

&lt;p&gt;
Toggle inline display with &lt;code&gt;C-c a /&lt;/code&gt; and annotation content appears as box-drawn blocks directly in your buffer:
&lt;/p&gt;

&lt;pre class=&quot;example&quot; id=&quot;org542136d&quot;&gt;
    some annotated code here
    ▴
┌─ ✎ [OPEN/NORMAL] ──────────────
│ This function needs refactoring.
│ The nested conditionals are hard
│ to follow.
└─────────────────────────────────
&lt;/pre&gt;

&lt;p&gt;
New in 0.9.8 is the inline pointer, that little &lt;code&gt;▴&lt;/code&gt; connecting the box to the annotated text.  It is indented to the exact column where the annotation starts, so you always know what the comment refers to.
&lt;/p&gt;

&lt;p&gt;
The fun bit is that the pointer is just a string, and it supports multiline.  So you can customise it to whatever shape you like:
&lt;/p&gt;

&lt;div class=&quot;org-src-container&quot;&gt;
&lt;pre&gt;&lt;code class=&quot;language-elisp&quot;&gt;;; Simple arrow (default)
(setq simply-annotate-inline-pointer-after &quot;▴&quot;)
(setq simply-annotate-inline-pointer-above &quot;▾&quot;)

;; Heavy L-bracket (my current favourite)
(setq simply-annotate-inline-pointer-after &quot;┃\n┗━▶&quot;)
(setq simply-annotate-inline-pointer-above &quot;┏━▶\n┃&quot;)

;; Speech bubble tail
(setq simply-annotate-inline-pointer-after &quot; ╰┐&quot;)
(setq simply-annotate-inline-pointer-above &quot; ╰┐&quot;)

;; Decorative diamond
(setq simply-annotate-inline-pointer-after &quot;◆&quot;)
(setq simply-annotate-inline-pointer-above &quot;◆&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;
There is a full list of copy-paste options in the Commentary section of the elisp file.  Set to &lt;code&gt;nil&lt;/code&gt; to disable the pointer entirely.
&lt;/p&gt;

&lt;p&gt;
So I think this is where simply-annotate really differs from other annotation packages.  Every annotation is a thread.  You can reply to it with &lt;code&gt;C-c a r&lt;/code&gt;, and replies can be nested under any comment in the thread, not just the root.  It prompts with a hierarchical completing-read menu showing the comment tree.
&lt;/p&gt;

&lt;p&gt;
Each thread has:
&lt;/p&gt;

&lt;ul class=&quot;org-ul&quot;&gt;
&lt;li&gt;&lt;b&gt;Status&lt;/b&gt; &amp;#x2013; open, in-progress, resolved, closed (&lt;code&gt;C-c a s&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Priority&lt;/b&gt; &amp;#x2013; low, normal, high, critical (&lt;code&gt;C-c a p&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Tags&lt;/b&gt; &amp;#x2013; freeform hashtags for organisation (&lt;code&gt;C-c a t&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Author tracking&lt;/b&gt; &amp;#x2013; configurable per-team, per-file, or single-user&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;
The comment tree renders with box-drawing characters so the hierarchy is always clear:
&lt;/p&gt;

&lt;pre class=&quot;example&quot; id=&quot;orgc1e4459&quot;&gt;
┌— ® [OPEN/NORMAL] —
| james dyer (03/29 08:27)
|   This is the original comment
| L james dyer (03/29 08:27)
| |   here is a reply to this comment
| | L james dyer (@3/29 08:27)
| |     and a reply within a reply!!
| L james dyer (03/29 08:28)
|    Here is another reply to the original comment
└────────────────────
&lt;/pre&gt;

&lt;p&gt;
For team collaboration:
&lt;/p&gt;

&lt;div class=&quot;org-src-container&quot;&gt;
&lt;pre&gt;&lt;code class=&quot;language-elisp&quot;&gt;(setq simply-annotate-author-list &apos;(&quot;Alice&quot; &quot;Bob&quot; &quot;Charlie&quot;))
(setq simply-annotate-prompt-for-author &apos;threads-only)
(setq simply-annotate-remember-author-per-file t)
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;
Annotations exist at three levels: &lt;code&gt;file&lt;/code&gt; (whole-file overview), &lt;code&gt;defun&lt;/code&gt; (function or block description), and &lt;code&gt;line&lt;/code&gt; (individual elements).  There is also an &lt;code&gt;all&lt;/code&gt; pseudo-level that shows everything at once, which is the default.
&lt;/p&gt;

&lt;p&gt;
Cycle levels with &lt;code&gt;C-c a ]&lt;/code&gt; and &lt;code&gt;C-c a [&lt;/code&gt;.  The header-line shows counts per level (&lt;code&gt;FILE:2 | DEFUN:5 | LINE:3&lt;/code&gt;) with the active level in bold, so you always know where you are, my idea here is to lean towards a coding annotation tool to help teach code or help to remember what has been implemented, so the levels start at a broad file overview and enables you to switch instantly to a more granular level.
&lt;/p&gt;

&lt;p&gt;
The org-mode listing (&lt;code&gt;C-c a l&lt;/code&gt;) gives you a foldable, navigable overview of all annotations in the current file, grouped by level.  Press &lt;code&gt;n&lt;/code&gt; and &lt;code&gt;p&lt;/code&gt; to step through headings, &lt;code&gt;RET&lt;/code&gt; to jump to source.
&lt;/p&gt;

&lt;p&gt;
New in 0.9.6, the tabular listing (&lt;code&gt;C-c a T&lt;/code&gt;) opens a fast, sortable table using &lt;code&gt;tabulated-list-mode&lt;/code&gt; (a feature in Emacs I am starting to leverage more).  Columns for Level, Line, Status, Priority, Comments, Tags, Author, and the first line of the comment.  Click column headers to sort.  This is brilliant for getting a quick birds-eye view of all the open items in a file.
&lt;/p&gt;

&lt;p&gt;
For the global view, &lt;code&gt;simply-annotate-show-all&lt;/code&gt; gathers annotations from every file in the database into a single org-mode buffer.
&lt;/p&gt;

&lt;p&gt;
Enable &lt;code&gt;simply-annotate-dired-mode&lt;/code&gt; and dired buffers show fringe indicators next to files that have annotations.  You can see at a glance which files have notes attached:
&lt;/p&gt;

&lt;div class=&quot;org-src-container&quot;&gt;
&lt;pre&gt;&lt;code class=&quot;language-elisp&quot;&gt;(add-hook &apos;dired-mode-hook #&apos;simply-annotate-dired-mode)
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;
Info manuals are also fully supported.  Annotations are tracked per-node, and the listing and jump-to-file commands navigate to Info nodes seamlessly.
&lt;/p&gt;

&lt;p&gt;
Press &lt;code&gt;C-c a e&lt;/code&gt; and you can edit the raw s-expression data structure of any annotation.  Every field is there: thread ID, status, priority, tags, comments with their IDs, parent-IDs, timestamps, and text.  &lt;code&gt;C-c C-c&lt;/code&gt; to save.  This is the escape hatch for when the UI does not quite cover what you need.
&lt;/p&gt;

&lt;p&gt;
Rather than writing paragraphs about how simply-annotate compares to other packages, I have put together a feature matrix in the README.  The short version: if you want threaded conversations, multiple combinable display styles, annotation levels, a smart context-aware command, and zero dependencies in a single file, this is the package for you.  If you need PDF annotation, go with org-noter or org-remark, they are excellent at that.
&lt;/p&gt;

&lt;div class=&quot;org-src-container&quot;&gt;
&lt;pre&gt;&lt;code class=&quot;language-elisp&quot;&gt;(use-package simply-annotate
  :bind-keymap (&quot;C-c a&quot; . simply-annotate-command-map)
  :hook (find-file-hook . simply-annotate-mode))

(with-eval-after-load &apos;simply-annotate
  (add-hook &apos;dired-mode-hook #&apos;simply-annotate-dired-mode))
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;
The package is available on GitHub on melpa at &lt;a href=&quot;https://melpa.org/#/simply-annotate&quot;&gt;simply-annotate&lt;/a&gt; or  &lt;a href=&quot;https://github.com/captainflasmr/simply-annotate&quot;&gt;https://github.com/captainflasmr/simply-annotate&lt;/a&gt;.  There is also an Info manual if you run &lt;code&gt;M-x info&lt;/code&gt; and search for &lt;code&gt;simply-annotate&lt;/code&gt;.
&lt;/p&gt;
</content>
</entry>
<entry>
<title>Ollama Buddy - Seven Lines to Any LLM Provider</title>
<link href="https://www.dyerdwelling.family/emacs/20260319143558-emacs--ollama-buddy---create-provider-helper-function/"/>
<id>https://www.dyerdwelling.family/emacs/20260319143558-emacs--ollama-buddy---create-provider-helper-function/</id>
<updated>2026-03-19T14:50:00+0000</updated>
<content type="html">&lt;p&gt;
Ever found yourself wanting to add a new AI provider to ollama-buddy? (probably not I would guess 🙂), only to realise you&apos;d need to write an entire Elisp module? Or perhaps you&apos;re running a local inference server that speaks the OpenAI API, but can&apos;t be bothered with the ceremony of creating a dedicated provider file?
&lt;/p&gt;


&lt;div id=&quot;org9910afa&quot; class=&quot;figure&quot;&gt;
&lt;p&gt;&lt;img class=&quot;img-fluid rounded&quot; src=&quot;/static/emacs/ollama-buddy-logo_001.jpg&quot; alt=&quot;ollama-buddy-logo_001.jpg&quot; width=&quot;100%&quot; /&gt;
&lt;/p&gt;
&lt;/div&gt;

&lt;p&gt;
Fair question. That&apos;s exactly why I built &lt;code&gt;ollama-buddy-provider-create&lt;/code&gt; — a single function that lets you register any LLM provider in seconds, whether it&apos;s a cloud API or your own local server.
&lt;/p&gt;

&lt;p&gt;
The traditional approach required a separate &lt;code&gt;.el&lt;/code&gt; file for each provider — OpenAI, Claude, Gemini, you name it. Each with its own &lt;code&gt;defcustom&lt;/code&gt; variables, configuration boilerplate, and maintenance overhead. It worked, but it felt a bit&amp;#x2026; heavy-handed for simple use cases.
&lt;/p&gt;

&lt;p&gt;
What if you just wanted to quickly add support for that new local LM Studio instance running on port 1234? Or point ollama-buddy at your company&apos;s internal AI gateway? Previously, you&apos;d be looking at copying an existing provider file and modifying dozens of lines. Now? One function call.
&lt;/p&gt;

&lt;p&gt;
The magic (yes, of the elisp kind!) happens in &lt;code&gt;ollama-buddy-provider.el&lt;/code&gt;, which provides a generic provider registration system. Instead of requiring separate Elisp files, you can register any provider with a single call:
&lt;/p&gt;

&lt;div class=&quot;org-src-container&quot;&gt;
&lt;pre&gt;&lt;code class=&quot;language-elisp&quot;&gt;(ollama-buddy-provider-create
 :name &quot;My Local Server&quot;
 :api-type &apos;openai
 :endpoint &quot;http://localhost:1234/v1/chat&quot;
 :models-endpoint &quot;http://localhost:1234/v1/models&quot;
 :api-key &quot;your-key-here&quot;
 :prefix &quot;l:&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;
&lt;b&gt;Three API types are supported out of the box:&lt;/b&gt;
&lt;/p&gt;

&lt;ul class=&quot;org-ul&quot;&gt;
&lt;li&gt;&lt;code&gt;openai&lt;/code&gt; (default) — Any OpenAI-compatible chat/completions API&lt;/li&gt;
&lt;li&gt;&lt;code&gt;claude&lt;/code&gt; — Anthropic Claude Messages API&lt;/li&gt;
&lt;li&gt;&lt;code&gt;gemini&lt;/code&gt; — Google Gemini generateContent API&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;
The system handles all the underlying HTTP requests, error mapping, and session management automatically. Your provider just needs to specify which API flavour it speaks.
&lt;/p&gt;

&lt;p&gt;
&lt;b&gt;Adding a local LM Studio instance:&lt;/b&gt;
&lt;/p&gt;

&lt;div class=&quot;org-src-container&quot;&gt;
&lt;pre&gt;&lt;code class=&quot;language-elisp&quot;&gt;(ollama-buddy-provider-create
 :name &quot;LM Studio&quot;
 :api-type &apos;openai
 :endpoint &quot;http://localhost:1234/v1/chat/completions&quot;
 :models-endpoint &quot;http://localhost:1234/v1/models&quot;
 :api-key &quot;not-needed&quot;  ; LM Studio often doesn&apos;t require auth
 :model-prefix &quot;l:&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;
&lt;b&gt;Connecting to OpenRouter (400+ models through one API):&lt;/b&gt;
&lt;/p&gt;
&lt;div class=&quot;org-src-container&quot;&gt;
&lt;pre&gt;&lt;code class=&quot;language-elisp&quot;&gt;(ollama-buddy-provider-create
 :name &quot;OpenRouter&quot;
 :api-type &apos;openai
 :endpoint &quot;https://openrouter.ai/api/v1/chat/completions&quot;
 :models-endpoint &quot;https://openrouter.ai/api/v1/models&quot;
 :api-key &quot;your-openrouter-key&quot;
 :model-prefix &quot;r:&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;
After registration, your new provider appears in the status line and becomes available through the standard model selection interface. The &lt;code&gt;model-prefix&lt;/code&gt; (like &lt;code&gt;l:&lt;/code&gt; for local or &lt;code&gt;r:&lt;/code&gt; for OpenRouter) lets you quickly identify which provider a model belongs to.
&lt;/p&gt;

&lt;p&gt;
The provider system leverages ollama-buddy&apos;s shared infrastructure in &lt;code&gt;ollama-buddy-remote.el&lt;/code&gt;, which extracts common functionality like request handling, error mapping, and response processing. This means your custom provider gets the same robust error handling as the built-in ones:
&lt;/p&gt;

&lt;ul class=&quot;org-ul&quot;&gt;
&lt;li&gt;Proper HTTP status code mapping (rate limits, timeouts, authentication errors)&lt;/li&gt;
&lt;li&gt;Async request support for non-blocking UI&lt;/li&gt;
&lt;li&gt;Automatic model listing and caching&lt;/li&gt;
&lt;li&gt;Integration with the existing session and conversation system&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;
When you call &lt;code&gt;ollama-buddy-provider-create&lt;/code&gt;, it registers your provider with the core system, making it available to all the usual entry points: the transient menu, model selection, and conversation buffers.
&lt;/p&gt;

&lt;p&gt;
This approach is perfect for:
&lt;/p&gt;
&lt;ul class=&quot;org-ul&quot;&gt;
&lt;li&gt;Local inference servers (LM Studio, llama.cpp, vLLM, Ollama&apos;s own OpenAI-compat layer)&lt;/li&gt;
&lt;li&gt;Company/internal AI gateways&lt;/li&gt;
&lt;li&gt;Quick experiments with new APIs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;
The beauty ojf this system is that it makes ollama-buddy genuinely extensible without requiring deep knowledge of its internals. Want to add support for that new AI service that launched yesterday? You can probably do it in five lines of configuration rather than fifty.
&lt;/p&gt;

&lt;p&gt;
Next up I think this will be the big one, adding tooling for those external providers!!!
&lt;/p&gt;
</content>
</entry>
<entry>
<title>Ollama Buddy - In-Buffer LLM Streaming</title>
<link href="https://www.dyerdwelling.family/emacs/20260311185447-emacs--ollama-buddy-in-buffer-replace/"/>
<id>https://www.dyerdwelling.family/emacs/20260311185447-emacs--ollama-buddy-in-buffer-replace/</id>
<updated>2026-03-12T11:09:00+0000</updated>
<content type="html">&lt;p&gt;
There is now an in-buffer replace feature in &lt;a href=&quot;https://github.com/captainflasmr/ollama-buddy&quot;&gt;ollama-buddy&lt;/a&gt;, so now an ollama response can work directly on your text, streaming the replacement in real-time, and giving you a simple accept/reject choice!, I have also added an smerge diff inline if desired to show the differences and give the user the ability to accept or reject
&lt;/p&gt;


&lt;div id=&quot;orgee50a6c&quot; class=&quot;figure&quot;&gt;
&lt;p&gt;&lt;img class=&quot;img-fluid rounded&quot; src=&quot;/static/emacs/ollama-buddy-logo.jpg&quot; alt=&quot;ollama-buddy-logo.jpg&quot; width=&quot;100%&quot; /&gt;
&lt;/p&gt;
&lt;/div&gt;

&lt;p&gt;
Here is how it works : &lt;a href=&quot;https://www.youtube.com/watch?v=Po7Wqpk0sqY&quot;&gt;https://www.youtube.com/watch?v=Po7Wqpk0sqY&lt;/a&gt;
&lt;/p&gt;

&lt;p&gt;
The feature is tucked away in the transient menu. Here&apos;s the workflow:
&lt;/p&gt;

&lt;ol class=&quot;org-ol&quot;&gt;
&lt;li&gt;Toggle it on via &lt;code&gt;C-c O&lt;/code&gt; → Actions → &lt;code&gt;W&lt;/code&gt;, or run &lt;code&gt;M-x ollama-buddy-toggle-in-buffer-replace&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;A small &lt;code&gt;✎&lt;/code&gt; indicator appears in your header line, confirming the mode is active&lt;/li&gt;
&lt;li&gt;Select a region of text you want rewritten&lt;/li&gt;
&lt;li&gt;Invoke a command from the role menu; for example, &lt;code&gt;C-c o&lt;/code&gt; then pick your rewrite command&lt;/li&gt;
&lt;li&gt;Watch as the AI streams the replacement directly into your buffer&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;
It is also worth noting that each custom menu can be defined with a &lt;code&gt;:destination&lt;/code&gt; option, for either &lt;code&gt;in-buffer&lt;/code&gt; or &lt;code&gt;chat&lt;/code&gt;, so the global in-buffer replace doesn&apos;t need to be selected, and the custom transient menu can be tailored for each command. For example, in the default custom menu, refactor code and proofread have the destination of &lt;code&gt;in-buffer&lt;/code&gt; set, but actions like git commit message or describe code will fall back to the global option, which by default is &lt;code&gt;chat&lt;/code&gt;, which is probably what you would want for these options.
&lt;/p&gt;

&lt;p&gt;
During streaming, you&apos;ll see a dimmed, italic &lt;code&gt;[Rewriting...]&lt;/code&gt; placeholder where your selection was. The new text then flows in with a highlight overlay.
&lt;/p&gt;

&lt;p&gt;
Once the stream finishes, you&apos;re dropped into a minor mode with two options:
&lt;/p&gt;

&lt;pre class=&quot;example&quot; id=&quot;org442e3b7&quot;&gt;
C-c C-c → accept the changes (keep new text, clear highlighting)
C-c C-k → reject and restore your original text
&lt;/pre&gt;

&lt;p&gt;
The mode line helpfully shows &lt;code&gt;[Rewrite?]&lt;/code&gt; while you&apos;re deciding. It&apos;s a bit like &lt;code&gt;ediff&lt;/code&gt; but for AI-generated changes and uses smerge-mode.
&lt;/p&gt;

&lt;p&gt;
What if the AI starts going off the rails halfway through? No problem. Press &lt;code&gt;C-c C-k&lt;/code&gt; at any point during streaming and the network process stops immediately, restoring your original selection. No waiting for it to finish rambling!
&lt;/p&gt;

&lt;p&gt;
Press &lt;code&gt;C-c d&lt;/code&gt; and the original text gets inserted below the new text in the same buffer. Word-level differences are highlighted using &lt;code&gt;smerge-refine-regions&lt;/code&gt;, so you can see exactly what changed at a glance.
&lt;/p&gt;

&lt;p&gt;
Green overlays mark added or modified words. Red strikethrough overlays show what was removed. It&apos;s proper granular diffing, not just a before-and-after comparison.
&lt;/p&gt;

&lt;p&gt;
This workflow is particularly useful for:
&lt;/p&gt;

&lt;ul class=&quot;org-ul&quot;&gt;
&lt;li&gt;Refactoring code blocks you&apos;ve already written&lt;/li&gt;
&lt;li&gt;Rephrasing documentation or prose that feels clunky&lt;/li&gt;
&lt;li&gt;Adjusting tone without losing your core message&lt;/li&gt;
&lt;li&gt;Quick grammar and style improvements&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;
It&apos;s less about &quot;write this for me from scratch&quot; and more &quot;help me iterate on what I&apos;ve already got.&quot;
&lt;/p&gt;

&lt;p&gt;
For best results, I&apos;ve found that smaller, focused selections work better than trying to rewrite entire files in one go. The AI has more context to work with, and you can make more granular decisions about what to keep.
&lt;/p&gt;

&lt;p&gt;
Next up is probably some more polish on the diff highlighting, or perhaps exploring how this could work with multi-file projects. But for now, again I think this implementation is good enough.
&lt;/p&gt;
</content>
</entry>
<entry>
<title>Photos Evie 2026-03-10</title>
<link href="https://www.dyerdwelling.family/blog/20260310091000-blog--evie/"/>
<id>https://www.dyerdwelling.family/blog/20260310091000-blog--evie/</id>
<updated>2026-03-10T09:10:00+0000</updated>
<content type="html">&lt;p&gt;
The kitchen chair
&lt;/p&gt;
</content>
</entry>
<entry>
<title>Ollama Buddy - Web Search Integration</title>
<link href="https://www.dyerdwelling.family/emacs/20260212142000-emacs--web-search-integration-in-ollama-buddy/"/>
<id>https://www.dyerdwelling.family/emacs/20260212142000-emacs--web-search-integration-in-ollama-buddy/</id>
<updated>2026-03-04T09:34:00+0000</updated>
<content type="html">&lt;p&gt;
One of the fundamental limitations of local LLMs is their knowledge cutoff - they don&apos;t know about anything that happened after their training data ended. The new web search integration in &lt;a href=&quot;https://github.com/captainflasmr/ollama-buddy&quot;&gt;ollama-buddy&lt;/a&gt; solves this by fetching current information from the web and injecting it into your conversation context.  Ollama has a specific API web search section, so it has now been activated!
&lt;/p&gt;

&lt;p&gt;
Here is a demonstration:
&lt;/p&gt;

&lt;p&gt;
&lt;a href=&quot;https://www.youtube.com/watch?v=05VzAajH404&quot;&gt;https://www.youtube.com/watch?v=05VzAajH404&lt;/a&gt;
&lt;/p&gt;


&lt;div id=&quot;org1eafea9&quot; class=&quot;figure&quot;&gt;
&lt;p&gt;&lt;img class=&quot;img-fluid rounded&quot; src=&quot;/static/emacs/20250424085731-emacs--Ollama-Buddy-0-9-35-Grok-Gemini-Integration-Enhanced-Sessions.jpg&quot; alt=&quot;20250424085731-emacs--Ollama-Buddy-0-9-35-Grok-Gemini-Integration-Enhanced-Sessions.jpg&quot; width=&quot;100%&quot; /&gt;
&lt;/p&gt;
&lt;/div&gt;

&lt;p&gt;
The web search feature implements a multi-stage pipeline that transforms search queries into clean, LLM-friendly context, your search query is sent to Ollama&apos;s Web Search API, the API returns structured search results with URLs and snippets.
&lt;/p&gt;

&lt;p&gt;
I have decided that each URL by default is fetched and processed through Emacs&apos; built-in &lt;code&gt;eww&lt;/code&gt; and &lt;code&gt;shr&lt;/code&gt; HTML rendering, but this can of course be configured, set &lt;code&gt;ollama-buddy-web-search-content-source&lt;/code&gt; to control how content is retrieved:
&lt;/p&gt;

&lt;ul class=&quot;org-ul&quot;&gt;
&lt;li&gt;`eww&apos; (default): Fetch each URL and render through eww/shr for clean text&lt;/li&gt;
&lt;li&gt;`api&apos;: Use content returned directly from Ollama API (faster, less refined)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;
The &lt;code&gt;shr&lt;/code&gt; (Simple HTML Renderer) library does an excellent job of converting HTML to readable plain text, stripping ads, navigation, and other noise, so I thought why not just use this rather than the return results from the ollama API, as they didn&apos;t seem to be particularly accurate.
&lt;/p&gt;

&lt;p&gt;
The cleaned text is formatted with org headings showing the source URL and attached to your conversation context, so when you send your next prompt, the search results are automatically included in the context. The LLM can now reason about current information as if it had this knowledge all along.
&lt;/p&gt;

&lt;p&gt;
There are multiple ways to search; firstly, is inline &lt;code&gt;@search()&lt;/code&gt; syntax in your prompts (gradually expanding the inline prompting language!), so for example:
&lt;/p&gt;

&lt;div class=&quot;org-src-container&quot;&gt;
&lt;pre&gt;&lt;code class=&quot;language-nil&quot;&gt;What are the key improvements in @search(Emacs 31 new features)?

Compare @search(Rust async programming) with @search(Go concurrency model)
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;
ollama-buddy automatically detects these markers, executes the searches, attaches the results, and then sends your prompt, so you can carry out multiple searches.
&lt;/p&gt;

&lt;p&gt;
You can also manual Search and Attach, Use &lt;code&gt;C-c / a&lt;/code&gt; (or &lt;code&gt;M-x ollama-buddy-web-search-attach&lt;/code&gt;)
&lt;/p&gt;

&lt;p&gt;
The search executes, results are attached to your session, and the &lt;code&gt;♁1&lt;/code&gt; indicator appears in the header line and the results can be viewed from the attachments menu, so for example would display something like:
&lt;/p&gt;

&lt;pre class=&quot;example&quot; id=&quot;orgc0befe4&quot;&gt;
* Web Searches (1)
** latest Emacs 31 features
*** 1. Hide Minor Modes in the Modeline in Emacs 31
*** 2. New Window Commands For Emacs 31
*** 3. Latest version of Emacs (GNU Emacs FAQ)
*** 4. bug#74145: 31.0.50; Default lexical-binding to t
*** 5. New in Emacs 30 (GNU Emacs FAQ)
&lt;/pre&gt;

&lt;p&gt;
with each header foldable, containing the actual search results.
&lt;/p&gt;

&lt;p&gt;
There is a little configuration required to go through the ollama API, first, get an API key from &lt;a href=&quot;https://ollama.com/settings/keys&quot;&gt;https://ollama.com/settings/keys&lt;/a&gt; (it&apos;s free). Then configure:
&lt;/p&gt;

&lt;div class=&quot;org-src-container&quot;&gt;
&lt;pre&gt;&lt;code class=&quot;language-elisp&quot;&gt;(use-package ollama-buddy
  :bind
  (&quot;C-c o&quot; . ollama-buddy-role-transient-menu)
  (&quot;C-c O&quot; . ollama-buddy-transient-menu-wrapper)
  :custom
  ;; Required: Your Ollama web search API key
  (ollama-buddy-web-search-api-key &quot;your-api-key-here&quot;))
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;
For clarification, the content source options are as follows:
&lt;/p&gt;

&lt;p&gt;
The &lt;code&gt;ollama-buddy-web-search-content-source&lt;/code&gt; variable controls how content is retrieved:
&lt;/p&gt;

&lt;p&gt;
&lt;b&gt;&lt;code&gt;eww&lt;/code&gt; (default, recommended)&lt;/b&gt;
&lt;/p&gt;

&lt;p&gt;
Fetches each URL and renders HTML through Emacs&apos; eww/shr. Produces cleaner, more complete content but requires additional HTTP requests.
&lt;/p&gt;

&lt;p&gt;
Pros:
&lt;/p&gt;
&lt;ul class=&quot;org-ul&quot;&gt;
&lt;li&gt;Much cleaner text extraction&lt;/li&gt;
&lt;li&gt;Full page content, not just snippets&lt;/li&gt;
&lt;li&gt;Removes ads, navigation, clutter&lt;/li&gt;
&lt;li&gt;Works with any website&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;
Cons:
&lt;/p&gt;
&lt;ul class=&quot;org-ul&quot;&gt;
&lt;li&gt;Slightly slower (additional HTTP requests)&lt;/li&gt;
&lt;li&gt;Requires network access for each URL&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;
&lt;b&gt;&lt;code&gt;api&lt;/code&gt; (experimental)&lt;/b&gt;
&lt;/p&gt;

&lt;p&gt;
Uses content returned directly from the Ollama API without fetching individual URLs. Faster but content quality depends on what the API provides.
&lt;/p&gt;

&lt;p&gt;
Pros:
&lt;/p&gt;
&lt;ul class=&quot;org-ul&quot;&gt;
&lt;li&gt;Faster (single API call)&lt;/li&gt;
&lt;li&gt;Less network traffic&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;
Cons:
&lt;/p&gt;
&lt;ul class=&quot;org-ul&quot;&gt;
&lt;li&gt;Content may be truncated&lt;/li&gt;
&lt;li&gt;Quality varies by source&lt;/li&gt;
&lt;li&gt;May miss important context&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;
I strongly recommend sticking with &lt;code&gt;eww&lt;/code&gt; - the quality difference is substantial.
&lt;/p&gt;

&lt;p&gt;
By default, web search fetches up to 5 URLs with 2000 characters per result. This provides rich context without overwhelming the LLM&apos;s context window.
&lt;/p&gt;

&lt;p&gt;
For longer research sessions, you can adjust:
&lt;/p&gt;

&lt;div class=&quot;org-src-container&quot;&gt;
&lt;pre&gt;&lt;code class=&quot;language-elisp&quot;&gt;(setq ollama-buddy-web-search-max-results 10)      ;; More sources
(setq ollama-buddy-web-search-snippet-length 5000) ;; Longer excerpts
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;
Be mindful of your LLM&apos;s context window limits. With 5 results at 2000 chars each, you&apos;re adding ~10K characters to your context.
&lt;/p&gt;

&lt;p&gt;
The web search integration fundamentally expands what your local LLMs can do. They&apos;re no longer limited to their training data - they can reach out, fetch current information, and reason about it just like they would with any other context, so hopefully this will now make &lt;code&gt;ollama-buddy&lt;/code&gt; a little more useful
&lt;/p&gt;
</content>
</entry>
<entry>
<title>Ollama Buddy v2.5 - RAG (Retrieval-Augmented Generation) Support</title>
<link href="https://www.dyerdwelling.family/emacs/20260224104044-emacs--ollama-buddy-v2.5---rag-(retrieval-augmented-generation)-support/"/>
<id>https://www.dyerdwelling.family/emacs/20260224104044-emacs--ollama-buddy-v2.5---rag-(retrieval-augmented-generation)-support/</id>
<updated>2026-02-24T12:10:00+0000</updated>
<content type="html">&lt;p&gt;
One of the things that has always slightly bothered me about chatting with a local LLM is that it only knows what it was trained on (although I suppose most LLMs are like that) . Ask it about your own codebase, your org notes, your project docs - and it&apos;s just guessing. Well, not anymore! Ollama Buddy now ships with proper Retrieval Augmented Generation support built-in
&lt;/p&gt;


&lt;div id=&quot;org55b7a49&quot; class=&quot;figure&quot;&gt;
&lt;p&gt;&lt;img class=&quot;img-fluid rounded&quot; src=&quot;/static/emacs/20250424085731-emacs--Ollama-Buddy-0-9-35-Grok-Gemini-Integration-Enhanced-Sessions.jpg&quot; alt=&quot;20250424085731-emacs--Ollama-Buddy-0-9-35-Grok-Gemini-Integration-Enhanced-Sessions.jpg&quot; width=&quot;100%&quot; /&gt;
&lt;/p&gt;
&lt;/div&gt;
&lt;section id=&quot;outline-container-org7d8659b&quot; class=&quot;outline-2&quot;&gt;
&lt;h2 id=&quot;org7d8659b&quot;&gt;What even is RAG?&lt;/h2&gt;
&lt;div class=&quot;outline-text-2&quot; id=&quot;text-org7d8659b&quot;&gt;
&lt;p&gt;
If you haven&apos;t come across the term before, the basic idea is simple. Instead of asking the LLM a question cold, you first go off and find the most relevant bits of text from your own documents, then you hand those bits to the LLM along with your question. The LLM now has actual context to work with rather than just vibes. The &quot;retrieval&quot; part is done using vector embeddings - each chunk of your documents gets turned into a mathematical representation, and at query time your question gets the same treatment. Chunks that are mathematically &quot;close&quot; to your question are the ones that get retrieved.
&lt;/p&gt;

&lt;p&gt;
In this case, I have worked to keep the whole pipeline inside Emacs; it talks to Ollama directly to contact an embedding model, which then returns the required information. I have tried to make this as Emacs Org-friendly as possible by storing the embedding information in Org files.
&lt;/p&gt;
&lt;/div&gt;
&lt;/section&gt;
&lt;section id=&quot;outline-container-orgca88db2&quot; class=&quot;outline-2&quot;&gt;
&lt;h2 id=&quot;orgca88db2&quot;&gt;Getting started&lt;/h2&gt;
&lt;div class=&quot;outline-text-2&quot; id=&quot;text-orgca88db2&quot;&gt;
&lt;p&gt;
You&apos;ll need an embedding model pulled alongside your chat model. The default is &lt;code&gt;nomic-embed-text&lt;/code&gt; which is a solid general-purpose choice:
&lt;/p&gt;

&lt;div class=&quot;org-src-container&quot;&gt;
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;ollama pull nomic-embed-text
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;
or just do it within ollama-buddy from the Model Management page.
&lt;/p&gt;
&lt;/div&gt;
&lt;/section&gt;
&lt;section id=&quot;outline-container-orge7de6a1&quot; class=&quot;outline-2&quot;&gt;
&lt;h2 id=&quot;orge7de6a1&quot;&gt;Indexing your documents&lt;/h2&gt;
&lt;div class=&quot;outline-text-2&quot; id=&quot;text-orge7de6a1&quot;&gt;
&lt;p&gt;
The main entry point is &lt;code&gt;M-x ollama-buddy-rag-index-directory&lt;/code&gt;. Point it at a directory and it will crawl through, chunk everything up, generate embeddings for each chunk, and save an index file. The first time you run this it can take a while depending on how much content you have and how fast your machine is - subsequent updates are much quicker as it only processes changed files.
&lt;/p&gt;

&lt;p&gt;
Supported file types (and I even managed to get pdf text extraction working!):
&lt;/p&gt;

&lt;ul class=&quot;org-ul&quot;&gt;
&lt;li&gt;Emacs Lisp (&lt;code&gt;.el&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Python, JavaScript, TypeScript, Go, Rust, C/C++, Java, Ruby - basically most languages&lt;/li&gt;
&lt;li&gt;Org-mode and Markdown&lt;/li&gt;
&lt;li&gt;Plain text&lt;/li&gt;
&lt;li&gt;PDF files (if you have &lt;code&gt;pdftotext&lt;/code&gt; from poppler-utils installed)&lt;/li&gt;
&lt;li&gt;YAML, TOML, JSON, HTML, CSS&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;
Files over 1MB are skipped (configurable), and the usual suspects like &lt;code&gt;.git&lt;/code&gt;, &lt;code&gt;node_modules&lt;/code&gt;, &lt;code&gt;__pycache__&lt;/code&gt; are excluded automatically.
&lt;/p&gt;

&lt;p&gt;
The index gets saved into &lt;code&gt;~/.emacs.d/ollama-buddy/rag-indexes/&lt;/code&gt; as a &lt;code&gt;.rag&lt;/code&gt; file named after the directory. You can see what you&apos;ve got with &lt;code&gt;M-x ollama-buddy-rag-list-indexes&lt;/code&gt;.
&lt;/p&gt;
&lt;/div&gt;
&lt;/section&gt;
&lt;section id=&quot;outline-container-orgf732c44&quot; class=&quot;outline-2&quot;&gt;
&lt;h2 id=&quot;orgf732c44&quot;&gt;The chunking strategy&lt;/h2&gt;
&lt;div class=&quot;outline-text-2&quot; id=&quot;text-orgf732c44&quot;&gt;
&lt;p&gt;
One thing I&apos;m quite happy with here is the chunking. Rather than just splitting on a fixed character count, documents are split into overlapping word-based chunks. The defaults are:
&lt;/p&gt;

&lt;div class=&quot;org-src-container&quot;&gt;
&lt;pre&gt;&lt;code class=&quot;language-elisp&quot;&gt;(setq ollama-buddy-rag-chunk-size 400)    ; ~500 tokens per chunk
(setq ollama-buddy-rag-chunk-overlap 50)  ; 50-word overlap between chunks
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;
The overlap is important - it means a piece of information that sits right at a chunk boundary doesn&apos;t get lost. Each chunk also tracks its source file and line numbers, so you can see exactly where a result came from.
&lt;/p&gt;
&lt;/div&gt;
&lt;/section&gt;
&lt;section id=&quot;outline-container-orgfe866fd&quot; class=&quot;outline-2&quot;&gt;
&lt;h2 id=&quot;orgfe866fd&quot;&gt;Searching and attaching context&lt;/h2&gt;
&lt;div class=&quot;outline-text-2&quot; id=&quot;text-orgfe866fd&quot;&gt;
&lt;p&gt;
Once you have an index, there are two main ways to use it:
&lt;/p&gt;

&lt;ul class=&quot;org-ul&quot;&gt;
&lt;li&gt;&lt;code&gt;M-x ollama-buddy-rag-search&lt;/code&gt; - searches and displays the results in a dedicated buffer so you can read through them&lt;/li&gt;

&lt;li&gt;&lt;code&gt;M-x ollama-buddy-rag-attach&lt;/code&gt; - searches and attaches the results directly to your chat context&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;
The second one is the useful one for day-to-day work. After running it, your next chat message will automatically include the retrieved document chunks as context. The status line shows &lt;code&gt;♁N&lt;/code&gt; (where N is the number of attached searches) so you always know what context is in play. Clear everything with &lt;code&gt;M-x ollama-buddy-clear-attachments&lt;/code&gt; or &lt;code&gt;C-c 0&lt;/code&gt;.
&lt;/p&gt;

&lt;p&gt;
You can also trigger searches inline using the &lt;code&gt;@rag()&lt;/code&gt; syntax directly in your prompt and is something fun I have been working on to include an inline command language of sorts, but more about that in a future post.
&lt;/p&gt;

&lt;p&gt;
The similarity search uses cosine similarity with sensible defaults (hopefully!)
&lt;/p&gt;

&lt;div class=&quot;org-src-container&quot;&gt;
&lt;pre&gt;&lt;code class=&quot;language-elisp&quot;&gt;(setq ollama-buddy-rag-top-k 5)                  ; return top 5 matching chunks
(setq ollama-buddy-rag-similarity-threshold 0.3)  ; filter out low-relevance results
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;
Bump &lt;code&gt;top-k&lt;/code&gt; if you want more context, lower the threshold if you&apos;re not getting enough results.
&lt;/p&gt;
&lt;/div&gt;
&lt;/section&gt;
&lt;section id=&quot;outline-container-org107325c&quot; class=&quot;outline-2&quot;&gt;
&lt;h2 id=&quot;org107325c&quot;&gt;A practical example&lt;/h2&gt;
&lt;div class=&quot;outline-text-2&quot; id=&quot;text-org107325c&quot;&gt;
&lt;p&gt;
Say you&apos;ve been working on a large Emacs package and you want the LLM to help you understand something specific. You&apos;d do:
&lt;/p&gt;

&lt;ol class=&quot;org-ol&quot;&gt;
&lt;li&gt;&lt;code&gt;M-x ollama-buddy-rag-index-directory&lt;/code&gt; → point at your project directory&lt;/li&gt;
&lt;li&gt;Wait for indexing to complete (the chat header-line shows progress)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;M-x ollama-buddy-rag-attach&lt;/code&gt; → type your search query, e.g. &quot;streaming filter process&quot;&lt;/li&gt;
&lt;li&gt;Ask your question in the chat buffer as normal&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;
The LLM now has the relevant source chunks as context and can give you a much more grounded answer than it would cold.
&lt;/p&gt;

&lt;p&gt;
And the important aspect, especially regarding local models which don&apos;t often have the huge context sizes often found in online LLMs is that it allows for very efficient context retrieval.
&lt;/p&gt;
&lt;/div&gt;
&lt;/section&gt;
&lt;section id=&quot;outline-container-org46a6210&quot; class=&quot;outline-2&quot;&gt;
&lt;h2 id=&quot;org46a6210&quot;&gt;That&apos;s pretty much it!&lt;/h2&gt;
&lt;div class=&quot;outline-text-2&quot; id=&quot;text-org46a6210&quot;&gt;
&lt;p&gt;
The whole thing is self-contained inside Emacs, no external packages or vector databases, you index once, search as needed, and the LLM gets actual information rather than hallucinating answers about your codebase or anything else that you would want to ingest and it will hopefully make working with local LLMs through ollama noticeably more useful and accurate.
&lt;/p&gt;
&lt;/div&gt;
&lt;/section&gt;
</content>
</entry>
<entry>
<title>Ollama Buddy v2.0 - LLMs can now call Emacs functions!</title>
<link href="https://www.dyerdwelling.family/emacs/20260216084213-emacs--ollama-buddy-v2.0---llms-can-now-call-emacs-functions/"/>
<id>https://www.dyerdwelling.family/emacs/20260216084213-emacs--ollama-buddy-v2.0---llms-can-now-call-emacs-functions/</id>
<updated>2026-02-16T08:56:00+0000</updated>
<content type="html">&lt;p&gt;
Tool calling has landed in ollama-buddy!, it&apos;s originally not something I really thought I would end up doing, but as ollama has provided tool enabled models and an API for this feature then I felt obliged to add it. So now LLMs through ollama can now actually do things inside Emacs rather than just talk about them, my original &quot;do things only in the chat buffer and copy and paste&quot; might have gone right out the window in an effort to fully support the ollama API!
&lt;/p&gt;


&lt;div id=&quot;org495a4b1&quot; class=&quot;figure&quot;&gt;
&lt;p&gt;&lt;img class=&quot;img-fluid rounded&quot; src=&quot;/static/emacs/20250424085731-emacs--Ollama-Buddy-0-9-35-Grok-Gemini-Integration-Enhanced-Sessions.jpg&quot; alt=&quot;20250424085731-emacs--Ollama-Buddy-0-9-35-Grok-Gemini-Integration-Enhanced-Sessions.jpg&quot; width=&quot;100%&quot; /&gt;
&lt;/p&gt;
&lt;/div&gt;

&lt;p&gt;
What is Tool Calling?
&lt;/p&gt;

&lt;p&gt;
The basic idea is simple: instead of the model only generating text, it can request to invoke functions. You ask &quot;what files are in my project?&quot;, and instead of guessing, the model calls list_directory, gets the real answer, and responds with actual information.
&lt;/p&gt;

&lt;p&gt;
This creates a conversational loop:
&lt;/p&gt;

&lt;ol class=&quot;org-ol&quot;&gt;
&lt;li&gt;You send a prompt&lt;/li&gt;
&lt;li&gt;The model decides it needs to call a tool&lt;/li&gt;
&lt;li&gt;ollama-buddy executes the tool and feeds the result back&lt;/li&gt;
&lt;li&gt;The model generates a response using the real data&lt;/li&gt;
&lt;li&gt;Steps 2-4 repeat if more tools are needed&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;
All of this is transparent - you just see the final response in the chat buffer.
&lt;/p&gt;

&lt;p&gt;
The new ollama-buddy-tools.el module ships with 8 built-in tools:
&lt;/p&gt;

&lt;p&gt;
Safe tools (read-only, enabled by default):
&lt;/p&gt;

&lt;ul class=&quot;org-ul&quot;&gt;
&lt;li&gt;&lt;b&gt;read_file&lt;/b&gt; - read file contents&lt;/li&gt;
&lt;li&gt;&lt;b&gt;list_directory&lt;/b&gt; - list directory contents&lt;/li&gt;
&lt;li&gt;&lt;b&gt;get_buffer_content&lt;/b&gt; - read an Emacs buffer&lt;/li&gt;
&lt;li&gt;&lt;b&gt;list_buffers&lt;/b&gt; - list open buffers with optional regex filtering&lt;/li&gt;
&lt;li&gt;&lt;b&gt;search_buffer&lt;/b&gt; - regex search within a buffer&lt;/li&gt;
&lt;li&gt;&lt;b&gt;calculate&lt;/b&gt; - evaluate math expressions via calc-eval&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;
Unsafe tools (require safe mode off):
&lt;/p&gt;

&lt;ul class=&quot;org-ul&quot;&gt;
&lt;li&gt;&lt;b&gt;write_file&lt;/b&gt; - write content to files&lt;/li&gt;
&lt;li&gt;&lt;b&gt;execute_shell&lt;/b&gt; - run shell commands&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;
Safe mode is on by default, so the model can only read - it can&apos;t modify anything unless you explicitly allow it, I think this is quite a nice simple implementation, at the moment I generally have safe mode off but always allowing confirmation for each tool action, but of course you can configure as necessary.
&lt;/p&gt;

&lt;p&gt;
&lt;b&gt;Example Session&lt;/b&gt;
&lt;/p&gt;

&lt;p&gt;
With a tool-capable model like qwen3:8b and tools enabled (C-c W):
&lt;/p&gt;

&lt;p&gt;
&lt;b&gt;&amp;gt;&amp;gt; PROMPT: What defuns are defined in ollama-buddy-tools.el?&lt;/b&gt;
&lt;/p&gt;

&lt;p&gt;
The model calls &lt;b&gt;search_buffer&lt;/b&gt; with a regex pattern, gets the list of function definitions, and gives you a nicely formatted summary. No copy-pasting needed.
&lt;/p&gt;

&lt;p&gt;
&lt;b&gt;Custom Tools&lt;/b&gt;
&lt;/p&gt;

&lt;p&gt;
You can register your own tools with ollama-buddy-tools-register:
&lt;/p&gt;

&lt;div class=&quot;org-src-container&quot;&gt;
&lt;pre&gt;&lt;code class=&quot;language-nil&quot;&gt;(ollama-buddy-tools-register
 &apos;my-tool
 &quot;Description of what the tool does&quot;
 &apos;((type . &quot;object&quot;)
   (required . [&quot;param1&quot;])
   (properties . ((param1 . ((type . &quot;string&quot;)
                              (description . &quot;Parameter description&quot;))))))
 (lambda (args)
   (let ((param1 (alist-get &apos;param1 args)))
     (format &quot;Result: %s&quot; param1)))
 t)  ; t = safe tool
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;
The registration API takes a name, description, JSON schema for parameters, an implementation function, and a safety flag. The model sees the schema and decides when to call your tool based on the conversation.
&lt;/p&gt;

&lt;p&gt;
A ⚒ symbol now appears next to tool-capable models everywhere - header line, model selector (C-c m), and model management buffer (C-c M). This follows the same pattern as the existing ⊙ vision indicator, so you can see at a glance which models support tools.
&lt;/p&gt;

&lt;p&gt;
That&apos;s it. Pull a tool-capable model (qwen3, llama3.1, mistral, etc.) or use an online tool enabled model from ollama and start chatting. Next up is probably some web searching!, as again the ollama API supports this, so you will be able to pull in the latest from the interwebs to augment your prompt definition!
&lt;/p&gt;
</content>
</entry>
<entry>
<title>Automatically Syncing Emacs Tab Bar Styling With Your Theme</title>
<link href="https://www.dyerdwelling.family/emacs/20260211182648-emacs--automatically-syncing-emacs-tab-bar-styling-with-your-theme/"/>
<id>https://www.dyerdwelling.family/emacs/20260211182648-emacs--automatically-syncing-emacs-tab-bar-styling-with-your-theme/</id>
<updated>2026-02-11T18:26:00+0000</updated>
<content type="html">&lt;p&gt;
If you&apos;ve ever enabled a new theme and noticed your &lt;b&gt;tab-bar&lt;/b&gt; faces stubbornly hanging onto old colours or custom tweaks, I have found often that the &lt;code&gt;tab-bar&lt;/code&gt;, &lt;code&gt;tab-bar-tab&lt;/code&gt;, and &lt;code&gt;tab-bar-tab-inactive&lt;/code&gt; faces don’t always blend cleanly with freshly loaded themes - especially of the older variety (a bit like me) and especially ones that came out before the tab bar was introduced into Emacs.
&lt;/p&gt;


&lt;div id=&quot;org57d2957&quot; class=&quot;figure&quot;&gt;
&lt;p&gt;&lt;img class=&quot;img-fluid rounded&quot; src=&quot;/static/emacs/20260211182648-emacs--Automatically-Syncing-Emacs-Tab-Bar-Styling-With-Your-Theme.jpg&quot; alt=&quot;20260211182648-emacs--Automatically-Syncing-Emacs-Tab-Bar-Styling-With-Your-Theme.jpg&quot; width=&quot;100%&quot; /&gt;
&lt;/p&gt;
&lt;/div&gt;

&lt;p&gt;
So how about a simple solution?, Can I implement something, that whenever I load a theme, the tab-bar faces update based on the theme’s default faces to establish a visually pleasant and coherent look?
&lt;/p&gt;

&lt;p&gt;
Yes, yes I can!; the result is a tiny Elisp enhancement that hooks directly into Emacs&apos; theme-loading process.
&lt;/p&gt;

&lt;p&gt;
Firstly however we need to have a method that will reliably pass over the themes default faces to the tab-bar. Here’s the function that realigns the tab-bar styling with your active theme:
&lt;/p&gt;

&lt;div class=&quot;org-src-container&quot;&gt;
&lt;pre&gt;&lt;code class=&quot;language-nil&quot;&gt;(defun selected-window-accent-sync-tab-bar-to-theme ()
  &quot;Synchronize tab-bar faces with the current theme.&quot;
  (interactive)
  (let ((default-bg (face-background &apos;default))
        (default-fg (face-foreground &apos;default))
        (inactive-fg (face-foreground &apos;mode-line-inactive)))
    (custom-set-faces
     `(tab-bar ((t (:inherit default :background ,default-bg :foreground ,default-fg))))
     `(tab-bar-tab ((t (:inherit default :background ,default-fg :foreground ,default-bg))))
     `(tab-bar-tab-inactive ((t (:inherit default :background ,default-bg :foreground ,inactive-fg)))))))
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;
This simply rebuilds the key tab-bar faces so they derive their colours from the current theme’s normal face definitions, so any old themes should now not leave the tab bar faces hanging.
&lt;/p&gt;

&lt;p&gt;
Now for the function activation; Emacs 29 introduced &lt;code&gt;enable-theme-functions&lt;/code&gt;, a hook that runs &lt;b&gt;every time a theme is enabled&lt;/b&gt; - perfect for our use case, but as always I have my eye on older Emacs versions, so lets fall back to a classic approach: advice on &lt;code&gt;load-theme&lt;/code&gt;.
&lt;/p&gt;

&lt;p&gt;
Here’s a version‑aware setup that does the right thing automatically:
&lt;/p&gt;

&lt;div class=&quot;org-src-container&quot;&gt;
&lt;pre&gt;&lt;code class=&quot;language-nil&quot;&gt;(if (version&amp;lt;= &quot;29.1&quot; emacs-version)
    ;; Emacs 29.1+ - use the official theme hook
    (add-hook &apos;enable-theme-functions
              (lambda (_theme)
                (selected-window-accent-sync-tab-bar-to-theme)))
  ;; Older Emacs - fall back to advising load-theme
  (progn
    (defun selected-window-accent-sync-tab-bar-to-theme--after (&amp;amp;rest _)
      (selected-window-accent-sync-tab-bar-to-theme))
    (advice-add &apos;load-theme :after
                #&apos;selected-window-accent-sync-tab-bar-to-theme--after)))
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;
With this tweak in place, every time you change themes, your tab-bar instantly updates, colours stay consistent, clean, and theme‑accurate without you having to do anything at all!  The downside to this of course is that any newer themes that were created after the advent of the tab bar in Emacs will have their tab-bar faces overridden, but for me this solution is good enough and gives a pleasant coherent visual tab bar experience.
&lt;/p&gt;

&lt;p&gt;
Yay!, yet another Yak shaved!
&lt;/p&gt;
</content>
</entry>
<entry>
<title>Spent a bit of free time polishing ollama-buddy - github Copilot is now onboard!</title>
<link href="https://www.dyerdwelling.family/emacs/20260204100913-emacs--ollama-buddy-updates---github-copilot-integration-plus-others/"/>
<id>https://www.dyerdwelling.family/emacs/20260204100913-emacs--ollama-buddy-updates---github-copilot-integration-plus-others/</id>
<updated>2026-02-04T10:40:00+0000</updated>
<content type="html">&lt;p&gt;
I&apos;ve had a little free time recently (figuring out this baby stuff!) and thought I would spend time revisiting and refining my AI assistant &lt;a href=&quot;https://github.com/captainflasmr/ollama-buddy&quot;&gt;ollama-buddy&lt;/a&gt;
&lt;/p&gt;

&lt;p&gt;
I&apos;ve been playing around with agentic coding and keeping up-to-date on the rapid development of the Emacs AI package landscape and I think I have refined in my own mind my idea of what I would like to see in an Emacs AI assistant.
&lt;/p&gt;

&lt;p&gt;
The headline change regarding the latest release of ollama-buddy is GitHub Copilot integration;  the rest of the work is about smoothing the UI and simplifying day-to-day use.
&lt;/p&gt;


&lt;div id=&quot;orgb950e27&quot; class=&quot;figure&quot;&gt;
&lt;p&gt;&lt;img class=&quot;img-fluid rounded&quot; src=&quot;/static/emacs/20250424085731-emacs--Ollama-Buddy-0-9-35-Grok-Gemini-Integration-Enhanced-Sessions.jpg&quot; alt=&quot;20250424085731-emacs--Ollama-Buddy-0-9-35-Grok-Gemini-Integration-Enhanced-Sessions.jpg&quot; width=&quot;100%&quot; /&gt;
&lt;/p&gt;
&lt;/div&gt;

&lt;p&gt;
What’s new - the Copilot addition (v1.2)
&lt;/p&gt;

&lt;ul class=&quot;org-ul&quot;&gt;
&lt;li&gt;GitHub Copilot Chat API support via a new file, ollama-buddy-copilot.el, so Copilot models can be used alongside your existing providers.&lt;/li&gt;
&lt;li&gt;Authentication uses GitHub’s device flow (OAuth). No API key required: M-x ollama-buddy-copilot-login opens a browser and guides you through secure authentication.&lt;/li&gt;
&lt;li&gt;Copilot models are identified with a &quot;p:&quot; prefix (for example, p:gpt-4o). The header line shows a &quot;p&quot; indicator when the Copilot provider is loaded so you always know it’s available.&lt;/li&gt;
&lt;li&gt;Copilot access exposes a broad set of models from multiple vendors through the Copilot interface: OpenAI (gpt-4o, gpt-5), Anthropic (claude-sonnet-4, claude-opus-4.5), Google (gemini-2.5-pro), and xAI models.&lt;/li&gt;
&lt;li&gt;Quick usage notes:
&lt;ol class=&quot;org-ol&quot;&gt;
&lt;li&gt;Ensure you have an active GitHub Copilot subscription.&lt;/li&gt;
&lt;li&gt;Run M-x ollama-buddy-copilot-login.&lt;/li&gt;
&lt;li&gt;Enter the device code in your browser at github.com/login/device when prompted.&lt;/li&gt;
&lt;li&gt;Select a Copilot model with C-c m (e.g., p:gpt-4o).&lt;/li&gt;
&lt;/ol&gt;&lt;/li&gt;
&lt;li&gt;Example config to load Copilot support:&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&quot;org-src-container&quot;&gt;
&lt;pre&gt;&lt;code class=&quot;language-nil&quot;&gt;(use-package ollama-buddy
  :bind
  (&quot;C-c o&quot; . ollama-buddy-menu)
  (&quot;C-c O&quot; . ollama-buddy-transient-menu-wrapper)
  :config
  (require &apos;ollama-buddy-copilot nil t))
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;
Other notable updates in this release series
&lt;/p&gt;
&lt;ul class=&quot;org-ul&quot;&gt;
&lt;li&gt;&lt;b&gt;v1.2.1 (2026-02-02)&lt;/b&gt;
&lt;ul class=&quot;org-ul&quot;&gt;
&lt;li&gt;Attachment count indicator on the header line so you get a constant visual reminder that the session has attachments.&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;v1.1.5 (2026-01-31)&lt;/b&gt;
&lt;ul class=&quot;org-ul&quot;&gt;
&lt;li&gt;Global system prompt feature (enabled by default): sets a baseline set of instructions (for example, to prefer plain prose and avoid markdown tables) that is prepended to session-specific system prompts. This helps keep responses consistent across providers and things like malformed markdown tables for example, which seems to be common. There’s a toggle (ollama-buddy-global-system-prompt-enabled) and a quick command to flip it (ollama-buddy-toggle-global-system-prompt), plus a transient-menu entry.&lt;/li&gt;
&lt;li&gt;Consolidated model management: streamlined into a single model management buffer (C-c W) and the welcome screen now points to that buffer for model tasks.&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;v1.1.4 (2026-01-31)&lt;/b&gt;
&lt;ul class=&quot;org-ul&quot;&gt;
&lt;li&gt;Header-line and keybinding cleanup: C-c RET to send prompts (matches gptel, as I feel this seems intuitive), removed a redundant backend indicator, shortened the markdown indicator to &quot;MD&quot;, and fixed markdown → org heading conversion to keep structure sane.&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;v1.1.3 (2026-01-31)&lt;/b&gt;
&lt;ul class=&quot;org-ul&quot;&gt;
&lt;li&gt;Chat UX improvements and simplification: added ollama-buddy-auto-scroll (default nil - don’t auto-scroll so you can read while streaming) and ollama-buddy-pulse-response (flashes the response on completion, taking from gptel again, as if there is no autoscrolling it is useful to visually see when the response has completed). Removed the model name coloring feature and related toggles to simplify code and improve org-mode performance.&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;v1.1.2 (2026-01-30)&lt;/b&gt;
&lt;ul class=&quot;org-ul&quot;&gt;
&lt;li&gt;Streamlined welcome screen and model selection, clearer provider indicators in the header line and an improved list of enabled online LLM providers.&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;/ul&gt;
</content>
</entry>
<entry>
<title>Ollama buddy now supports cloud models!</title>
<link href="https://www.dyerdwelling.family/emacs/20260128082917-emacs--ollama-buddy-now-supports-cloud-models/"/>
<id>https://www.dyerdwelling.family/emacs/20260128082917-emacs--ollama-buddy-now-supports-cloud-models/</id>
<updated>2026-01-28T08:29:00+0000</updated>
<content type="html">&lt;p&gt;
Having another look at my AI assistant - ollama-buddy, its been a while and it seems ollama has moved on since I started creating this package last year, so I have developed a new roadmap and the first step is to add ollama cloud models!
&lt;/p&gt;


&lt;div id=&quot;orga97d56b&quot; class=&quot;figure&quot;&gt;
&lt;p&gt;&lt;img class=&quot;img-fluid rounded&quot; src=&quot;/static/emacs/20250424085731-emacs--Ollama-Buddy-0-9-35-Grok-Gemini-Integration-Enhanced-Sessions.jpg&quot; alt=&quot;20250424085731-emacs--Ollama-Buddy-0-9-35-Grok-Gemini-Integration-Enhanced-Sessions.jpg&quot; width=&quot;100%&quot; /&gt;
&lt;/p&gt;
&lt;/div&gt;

&lt;p&gt;
Here are some references to the project, including a youtube channel where I upload ollama-buddy demonstrations:
&lt;/p&gt;

&lt;p&gt;
&lt;a href=&quot;https://melpa.org/#/ollama-buddy&quot;&gt;https://melpa.org/#/ollama-buddy&lt;/a&gt;
&lt;/p&gt;

&lt;p&gt;
&lt;a href=&quot;https://github.com/captainflasmr/ollama-buddy&quot;&gt;https://github.com/captainflasmr/ollama-buddy&lt;/a&gt;
&lt;/p&gt;

&lt;p&gt;
Here is the changelog for the cloud model implementation:
&lt;/p&gt;
&lt;section id=&quot;outline-container-orgaefead1&quot; class=&quot;outline-2&quot;&gt;
&lt;h2 id=&quot;orgaefead1&quot;&gt;&lt;span class=&quot;timestamp-wrapper&quot;&gt;&lt;span class=&quot;timestamp&quot;&gt;&amp;lt;2026-01-28 Wed&amp;gt; &lt;/span&gt;&lt;/span&gt; &lt;b&gt;1.1&lt;/b&gt;&lt;/h2&gt;
&lt;div class=&quot;outline-text-2&quot; id=&quot;text-orgaefead1&quot;&gt;
&lt;p&gt;
Added Ollama Cloud Models support
&lt;/p&gt;

&lt;ul class=&quot;org-ul&quot;&gt;
&lt;li&gt;Cloud models (running on ollama.com infrastructure) now work seamlessly&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ollama-buddy-cloud-signin&lt;/code&gt; to automatically open browser for authentication&lt;/li&gt;
&lt;li&gt;Cloud models are proxied through the local Ollama server which handles authentication&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;C-u C-c m&lt;/code&gt; or transient menu &quot;Model &amp;gt; Cloud&quot; to select cloud models&lt;/li&gt;
&lt;li&gt;Status line shows ☁ indicator when using a cloud model&lt;/li&gt;
&lt;li&gt;Available cloud models include: qwen3-coder:480b-cloud, deepseek-v3.1:671b-cloud, gpt-oss:120b-cloud, minimax-m2.1:cloud, and more&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/section&gt;
</content>
</entry>
<entry>
<title>Auto-Populating Weekly Dates in Org-Mode Tables</title>
<link href="https://www.dyerdwelling.family/emacs/20260126101317-emacs--auto-populating-weekly-dates-in-org-mode-tables/"/>
<id>https://www.dyerdwelling.family/emacs/20260126101317-emacs--auto-populating-weekly-dates-in-org-mode-tables/</id>
<updated>2026-01-26T10:13:00+0000</updated>
<content type="html">&lt;p&gt;
Here is just a quick one, I was working with an org-mode table for tracking work weeks and needed to auto-populate a Date column where each row increments by exactly one week. The table structure looked like this:
&lt;/p&gt;


&lt;div id=&quot;org0986769&quot; class=&quot;figure&quot;&gt;
&lt;p&gt;&lt;img class=&quot;img-fluid rounded&quot; src=&quot;/static/emacs/20260126101317-emacs--Auto-Populating-Weekly-Dates-in-Org-Mode-Tables.jpg&quot; alt=&quot;20260126101317-emacs--Auto-Populating-Weekly-Dates-in-Org-Mode-Tables.jpg&quot; width=&quot;100%&quot; /&gt;
&lt;/p&gt;
&lt;/div&gt;

&lt;p&gt;
The first row has a base date (2026-01-05), and I wanted subsequent rows to automatically calculate as weekly increments: 2026-01-12, 2026-01-19, and so on.
&lt;/p&gt;

&lt;p&gt;
Initially, I tried several approaches that seemed logical but encountered &lt;code&gt;#ERROR&lt;/code&gt; results and eventually settled on a working solution which is to hardcode the base date directly in the formula:
&lt;/p&gt;

&lt;div class=&quot;org-src-container&quot;&gt;
&lt;pre&gt;&lt;code class=&quot;language-org&quot;&gt;#+TBLFM: $3=&apos;(format-time-string &quot;%Y-%m-%d&quot; (time-add (date-to-time &quot;2026-01-05&quot;) (* (- @# 2) 7 24 3600)))
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;
which gave:
&lt;/p&gt;


&lt;div id=&quot;org29e564b&quot; class=&quot;figure&quot;&gt;
&lt;p&gt;&lt;img class=&quot;img-fluid rounded&quot; src=&quot;/static/emacs/20260126101317-emacs--Auto-Populating-Weekly-Dates-in-Org-Mode-Tables2.jpg&quot; alt=&quot;20260126101317-emacs--Auto-Populating-Weekly-Dates-in-Org-Mode-Tables2.jpg&quot; width=&quot;100%&quot; /&gt;
&lt;/p&gt;
&lt;/div&gt;

&lt;p&gt;
and I can now of course extend the table for all the weeks in the year and I don&apos;t have to fill in manually any more!
&lt;/p&gt;

&lt;p&gt;
Here&apos;s how it works:
&lt;/p&gt;

&lt;ul class=&quot;org-ul&quot;&gt;
&lt;li&gt;&lt;code&gt;(date-to-time &quot;2026-01-05&quot;)&lt;/code&gt; - Convert the hardcoded base date to Emacs time format&lt;/li&gt;
&lt;li&gt;&lt;code&gt;(- @# 2)&lt;/code&gt; - Calculate the offset from the base row&lt;/li&gt;
&lt;li&gt;&lt;code&gt;(* (- @# 2) 7 24 3600)&lt;/code&gt; - Convert the offset to seconds (weeks × days × hours × seconds)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;time-add&lt;/code&gt; - Add the offset to the base date&lt;/li&gt;
&lt;li&gt;&lt;code&gt;format-time-string &quot;%Y-%m-%d&quot;&lt;/code&gt; - Format back to ISO date string&lt;/li&gt;
&lt;/ul&gt;
</content>
</entry>
<entry>
<title>Photos Evie 2026-01-23</title>
<link href="https://www.dyerdwelling.family/blog/20260123114700-blog--evie/"/>
<id>https://www.dyerdwelling.family/blog/20260123114700-blog--evie/</id>
<updated>2026-01-23T11:47:00+0000</updated>
<content type="html">&lt;p&gt;
Evie in pram
&lt;/p&gt;
</content>
</entry>
<entry>
<title>Speed Reading in Emacs: Building an RSVP Reader</title>
<link href="https://www.dyerdwelling.family/emacs/20260116182841-emacs--speed-reading-in-emacs-building-an-rsvp-reader/"/>
<id>https://www.dyerdwelling.family/emacs/20260116182841-emacs--speed-reading-in-emacs-building-an-rsvp-reader/</id>
<updated>2026-01-18T10:30:00+0000</updated>
<content type="html">&lt;p&gt;
I recently came across a fascinating video titled &quot;How Fast Can You Read? - Speed Reading Challenge&quot; that demonstrated the power of RSVP (Rapid Serial Visual Presentation) for speed reading. The concept  is quite nice and simple and I vaguely remember seeing something about it a few years back.  Instead of your eyes scanning across lines of text, words are presented one at a time in a fixed position. This eliminates the mechanical overhead of eye movements and can dramatically increase reading speed!
&lt;/p&gt;


&lt;div id=&quot;orgc8ffde0&quot; class=&quot;figure&quot;&gt;
&lt;p&gt;&lt;img class=&quot;img-fluid rounded&quot; src=&quot;/static/emacs/20260116182841-emacs--Speed-Reading-in-Emacs-Building-an-RSVP-Reader.jpg&quot; alt=&quot;20260116182841-emacs--Speed-Reading-in-Emacs-Building-an-RSVP-Reader.jpg&quot; width=&quot;100%&quot; /&gt;
&lt;/p&gt;
&lt;/div&gt;

&lt;p&gt;
So, I immediately wondered, could I build this into Emacs?, actually no, firstly I thought, are there any packages for Emacs that can do this?, of course there are!, the &lt;b&gt;spray&lt;/b&gt; package from MELPA is a more mature, feature-rich option if you&apos;re looking for production-ready RSVP reading in Emacs, and also there is &lt;b&gt;speedread&lt;/b&gt;.  However, there&apos;s something satisfying about having a compact, single-function solution that does exactly what you need, so lets see if I can build one!
&lt;/p&gt;

&lt;p&gt;
RSVP works by displaying words sequentially in the same location on screen. Your eyes remain stationary, focused on a single point, while words flash by at a controlled pace. This technique can boost reading speeds to 300-600+ words per minute, compared to typical reading speeds of 200-300 WPM.
&lt;/p&gt;

&lt;p&gt;
The key innovation is the &lt;b&gt;Optimal Recognition Point (ORP)&lt;/b&gt; - typically positioned about one-third into each word. This is where your eye naturally fixates when reading. By aligning each word&apos;s ORP at the same screen position, RSVP creates an optimal visual flow.
&lt;/p&gt;

&lt;p&gt;
Given Emacs&apos; extensive text processing capabilities, this sounds something that Emacs could eat for breakfast. Here is what I came up with:
&lt;/p&gt;

&lt;p&gt;
Here is a quick video of my implementation:
&lt;/p&gt;


&lt;div id=&quot;org4f7f891&quot; class=&quot;figure&quot;&gt;
&lt;p&gt;&lt;img class=&quot;img-fluid rounded&quot; src=&quot;/static/emacs/20260118100321--screen-recording.gif&quot; alt=&quot;20260118100321--screen-recording.gif&quot; width=&quot;100%&quot; /&gt;
&lt;/p&gt;
&lt;/div&gt;

&lt;p&gt;
and the defun:
&lt;/p&gt;

&lt;div class=&quot;org-src-container&quot;&gt;
&lt;pre&gt;&lt;code class=&quot;language-elisp&quot;&gt;(defun rsvp-minibuffer ()
  &quot;Display words from point (or mark to point) in minibuffer using RSVP.
Use f/s for speed, [/] for size, b/n to skip, SPC to pause, q to quit.&quot;
  (interactive)
  (let* ((start (if (region-active-p) (region-beginning) (point)))
         (end (if (region-active-p) (region-end) (point-max)))
         (text (buffer-substring-no-properties start end))
         (wpm 350) (font-size 200) (orp-column 20)
         (word-positions &apos;()) (pos 0) (i 0)
         (message-log-max nil))  ; Disable message logging
    ;; Build word positions list
    (dolist (word (split-string text))
      (unless (string-blank-p word)
        (when-let ((word-start (string-match (regexp-quote word) text pos)))
          (push (cons word (+ start word-start)) word-positions)
          (setq pos (+ word-start (length word))))))
    (setq word-positions (nreverse word-positions))
    ;; Display loop
    (while (&amp;lt; i (length word-positions))
      (let* ((word (car (nth i word-positions)))
             (word-pos (cdr (nth i word-positions)))
             (word-len (length word))
             (delay (* (/ 60.0 wpm)
                      (cond ((&amp;lt; word-len 3) 0.8) ((&amp;gt; word-len 8) 1.3) (t 1.0))
                      (if (string-match-p &quot;[.!?]$&quot; word) 1.5 1.0)))
             (orp-pos (/ word-len 3))
             (face-mono `(:height ,font-size :family &quot;monospace&quot;))
             (face-orp `(:foreground &quot;red&quot; :weight normal ,@face-mono))
             (padded-word (concat 
                          (propertize (make-string (max 0 (- orp-column orp-pos)) ?\s) &apos;face face-mono)
                          (propertize (substring word 0 orp-pos) &apos;face face-mono)
                          (propertize (substring word orp-pos (1+ orp-pos)) &apos;face face-orp)
                          (propertize (substring word (1+ orp-pos)) &apos;face face-mono))))
        (goto-char (+ word-pos word-len))
        (message &quot;%s&quot; padded-word)
        (pcase (read-event nil nil delay)
          (?f (setq wpm (min 1000 (+ wpm 50))))
          (?s (setq wpm (max 50 (- wpm 50))))
          (?\[ (setq font-size (max 100 (- font-size 20))))
          (?\] (setq font-size (min 400 (+ font-size 20))))
          (?b (setq i (max 0 (- i 10))))
          (?n (setq i (min (1- (length word-positions)) (+ i 10))))
          (?\s (read-event (format &quot;%s [PAUSED - WPM: %d]&quot; padded-word wpm)))
          (?q (setq i (length word-positions)))
          (_ (setq i (1+ i))))))))
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;
The function calculates the ORP as one-third through each word and highlights it in red. By padding each word with spaces, the ORP character stays perfectly aligned in the same column, creating that crucial stationary focal point.
&lt;/p&gt;

&lt;p&gt;
To ensure pixel-perfect alignment, the function explicitly sets a monospace font family for all displayed text. Without this, proportional fonts would cause the ORP to drift slightly between words, although I think at times there is a little waddle, but it is good enough.
&lt;/p&gt;

&lt;p&gt;
Also, Not all words are created equal:
&lt;/p&gt;

&lt;ul class=&quot;org-ul&quot;&gt;
&lt;li&gt;Short words (&amp;lt; 3 characters) display 20% faster&lt;/li&gt;
&lt;li&gt;Long words (&amp;gt; 8 characters) display 30% slower&lt;/li&gt;
&lt;li&gt;Words ending in punctuation (&lt;code&gt;.!?&lt;/code&gt;) get 50% more time&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;
This mimics natural reading rhythms where you&apos;d naturally pause at sentence boundaries.
&lt;/p&gt;

&lt;p&gt;
While reading, you can try these keybindings: (which I borrowed off &lt;b&gt;spray&lt;/b&gt;)
&lt;/p&gt;

&lt;ul class=&quot;org-ul&quot;&gt;
&lt;li&gt;&lt;code&gt;f&lt;/code&gt; / &lt;code&gt;s&lt;/code&gt; - Speed up or slow down (±50 WPM)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;[&lt;/code&gt; / &lt;code&gt;]&lt;/code&gt; - Decrease or increase font size&lt;/li&gt;
&lt;li&gt;&lt;code&gt;b&lt;/code&gt; / &lt;code&gt;n&lt;/code&gt; - Skip backward or forward by 10 words&lt;/li&gt;
&lt;li&gt;&lt;code&gt;SPC&lt;/code&gt; - Pause (press any key to resume)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;q&lt;/code&gt; - Quit&lt;/li&gt;
&lt;li&gt;&lt;code&gt;C-g&lt;/code&gt; - Emergency quit&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;
Also The function tracks each word&apos;s position in the original buffer and updates &lt;code&gt;point&lt;/code&gt; as you read. This means:
&lt;/p&gt;

&lt;ul class=&quot;org-ul&quot;&gt;
&lt;li&gt;You can see where you are in the text&lt;/li&gt;
&lt;li&gt;When you quit, your cursor is at the last word you read&lt;/li&gt;
&lt;li&gt;You can resume reading by running the function again&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;
To use it, simply:
&lt;/p&gt;

&lt;ol class=&quot;org-ol&quot;&gt;
&lt;li&gt;Position your cursor where you want to start reading (or select a region)&lt;/li&gt;
&lt;li&gt;Run &lt;code&gt;M-x rsvp-minibuffer&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Watch the words flow in the minibuffer&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;
The function works from point to end of buffer, or if you have an active region, it only processes the selected text.
&lt;/p&gt;

&lt;p&gt;
If you&apos;re curious about RSVP reading, drop this function into your Emacs config and give it a try. Start at 300-350 WPM and see how it feels. You might be surprised at how much faster you can consume text when your eyes aren&apos;t constantly moving across the page.
&lt;/p&gt;

&lt;p&gt;
The code is simple enough to customize - adjust the default WPM, change the ORP colour, modify the timing multipliers, or add new controls. That&apos;s the beauty of Emacs, if you can imagine it, you can build it.
&lt;/p&gt;
</content>
</entry>
<entry>
<title>A single function ripgrep alternative to rgrep</title>
<link href="https://www.dyerdwelling.family/emacs/20260109094340-emacs--a-single-function-ripgrep-alternative-to-rgrep/"/>
<id>https://www.dyerdwelling.family/emacs/20260109094340-emacs--a-single-function-ripgrep-alternative-to-rgrep/</id>
<updated>2026-01-09T09:43:00+0000</updated>
<content type="html">&lt;p&gt;
For years, &lt;code&gt;rgrep&lt;/code&gt; has been the go-to solution for searching codebases in Emacs. It&apos;s built-in, reliable, and works everywhere. But it&apos;s slow on large projects and uses the aging &lt;code&gt;find&lt;/code&gt; and &lt;code&gt;grep&lt;/code&gt; commands.
&lt;/p&gt;

&lt;p&gt;
Packages like &lt;code&gt;deadgrep&lt;/code&gt; and &lt;code&gt;rg.el&lt;/code&gt; provide ripgrep integration, and for years I used &lt;code&gt;deadgrep&lt;/code&gt; and really liked it. But what if you could get ripgrep&apos;s speed with just a single function you paste into your config?
&lt;/p&gt;


&lt;div id=&quot;orge648566&quot; class=&quot;figure&quot;&gt;
&lt;p&gt;&lt;img class=&quot;img-fluid rounded&quot; src=&quot;/static/emacs/20260109094340-emacs--A-Single-Function-ripgrep-Alternative-to-rgrep.jpg&quot; alt=&quot;20260109094340-emacs--A-Single-Function-ripgrep-Alternative-to-rgrep.jpg&quot; width=&quot;100%&quot; /&gt;
&lt;/p&gt;
&lt;/div&gt;

&lt;p&gt;
This post introduces a ~100 line &lt;code&gt;defun&lt;/code&gt; that replaces rgrep, no packages, no dependencies, just pure Elisp. It&apos;s fast, asynchronous, works offline, and mimics rgrep&apos;s familiar interface so it can leverage &lt;code&gt;grep-mode&lt;/code&gt;
&lt;/p&gt;

&lt;p&gt;
So, why not just use rgrep?
&lt;/p&gt;

&lt;p&gt;
I think that rgrep has three main limitations:
&lt;/p&gt;

&lt;p&gt;
Firstly, speed. On a project with 10,000+ files, rgrep can take 15-30 seconds. Ripgrep completes the same search in under a second.
&lt;/p&gt;

&lt;p&gt;
Secondly, file ignoring, rgrep requires manually configuring &lt;code&gt;grep-find-ignored-directories&lt;/code&gt; or &lt;code&gt;grep-find-ignored-files&lt;/code&gt;, I had the following typical configuration for rgrep, but it wasn&apos;t as flexible as I would like it to be:
&lt;/p&gt;

&lt;div class=&quot;org-src-container&quot;&gt;
&lt;pre&gt;&lt;code class=&quot;language-elisp&quot;&gt;(eval-after-load &apos;grep
  &apos;(progn
     (dolist (dir &apos;(&quot;nas&quot; &quot;.cache&quot; &quot;cache&quot; &quot;elpa&quot; &quot;chromium&quot; &quot;.local/share&quot; &quot;syncthing&quot; &quot;.mozilla&quot; &quot;.local/lib&quot; &quot;Games&quot;))
       (push dir grep-find-ignored-directories))
     (dolist (file &apos;(&quot;.cache&quot; &quot;*cache*&quot; &quot;*.iso&quot; &quot;*.xmp&quot; &quot;*.jpg&quot; &quot;*.mp4&quot;))
       (push file grep-find-ignored-files))
     ))
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;
Ripgrep automatically respects an &lt;code&gt;.ignore&lt;/code&gt; file. Just create an &lt;code&gt;.ignore&lt;/code&gt; file in your project root and list patterns to exclude, this is just a simple text file, universally applied across all searches and any changes can be easily applied.
&lt;/p&gt;

&lt;p&gt;
Thirdly, modern features. Ripgrep includes smart-case search, better regex support, and automatic binary file detection. Of course, there is a context that can be displayed around the found line, but in order to get ripgrep to work with grep-mode, this is not really doable, and it&apos;s not something I need anyway.
&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;
Here is the complete ripgrep implementation that you can paste directly into your &lt;code&gt;init.el&lt;/code&gt;:
&lt;/p&gt;

&lt;div class=&quot;org-src-container&quot;&gt;
&lt;pre&gt;&lt;code class=&quot;language-elisp&quot;&gt;(defun my/grep (search-term &amp;amp;optional directory glob)
  &quot;Run ripgrep (rg) with SEARCH-TERM and optionally DIRECTORY and GLOB.
If ripgrep is unavailable, fall back to Emacs&apos;s rgrep command. Highlights SEARCH-TERM in results.
By default, only the SEARCH-TERM needs to be provided. If called with a
universal argument, DIRECTORY and GLOB are prompted for as well.&quot;
  (interactive
   (let* ((univ-arg current-prefix-arg)
          (default-search-term
           (cond
            ((use-region-p)
             (buffer-substring-no-properties (region-beginning) (region-end)))
            ((thing-at-point &apos;symbol t))
            ((thing-at-point &apos;word t))
            (t &quot;&quot;))))
     (list
      (read-string (if (string-empty-p default-search-term)
                       &quot;Search for: &quot;
                     (format &quot;Search for (default `%s`): &quot; default-search-term))
                   nil nil default-search-term)
      (when univ-arg (read-directory-name &quot;Directory: &quot;))
      (when univ-arg (read-string &quot;File pattern (glob, default: ): &quot; nil nil &quot;&quot;)))))
  (let* ((directory (expand-file-name (or directory default-directory)))
         (glob (or glob &quot;&quot;))
         (buffer-name &quot;*grep*&quot;))
    (if (executable-find &quot;rg&quot;)
        (let ((buffer (get-buffer-create buffer-name)))
          (with-current-buffer buffer
            (setq default-directory directory)
            (let ((inhibit-read-only t))
              (erase-buffer)
              (insert (format &quot;-*- mode: grep; default-directory: \&quot;%s\&quot; -*-\n\n&quot; directory))
              (if (not (string= &quot;&quot; glob))
                  (insert (format &quot;[o] Glob: %s\n\n&quot; glob)))
              (insert &quot;Searching...\n\n&quot;))
            (grep-mode)
            (setq-local my/grep-search-term search-term)
            (setq-local my/grep-directory directory)
            (setq-local my/grep-glob glob))
          
          (pop-to-buffer buffer)
          (goto-char (point-min))
          
          (make-process
           :name &quot;ripgrep&quot;
           :buffer buffer
           :command `(&quot;rg&quot; &quot;--color=never&quot; &quot;--max-columns=500&quot; 
                      &quot;--column&quot; &quot;--line-number&quot; &quot;--no-heading&quot; 
                      &quot;--smart-case&quot; &quot;-e&quot; ,search-term
                      &quot;--glob&quot; ,glob ,directory)
           :filter (lambda (proc string)
                     (when (buffer-live-p (process-buffer proc))
                       (with-current-buffer (process-buffer proc)
                         (let ((inhibit-read-only t)
                               (moving (= (point) (process-mark proc))))
                           (setq string (replace-regexp-in-string &quot;[\r\0\x01-\x08\x0B-\x0C\x0E-\x1F]&quot; &quot;&quot; string))
                           ;; Replace full directory path with ./ in the incoming output
                           (setq string (replace-regexp-in-string 
                                         (concat &quot;^&quot; (regexp-quote directory))
                                         &quot;./&quot;
                                         string))
                           (save-excursion
                             (goto-char (process-mark proc))
                             (insert string)
                             (set-marker (process-mark proc) (point)))
                           (if moving (goto-char (process-mark proc)))))))
           :sentinel
           (lambda (proc _event)
             (when (memq (process-status proc) &apos;(exit signal))
               (with-current-buffer (process-buffer proc)
                 (let ((inhibit-read-only t))
                   ;; Remove &quot;Searching...&quot; line
                   (goto-char (point-min))
                   (while (re-search-forward &quot;Searching\\.\\.\\.\n\n&quot; nil t)
                     (replace-match &quot;&quot; nil t))

                   ;; Clean up the output - replace full paths with ./
                   (goto-char (point-min))
                   (forward-line 3)
                   (let ((start-pos (point)))
                     (while (re-search-forward (concat &quot;^&quot; (regexp-quote directory)) nil t)
                       (replace-match &quot;./&quot; t t))
                     
                     ;; Check if any results were found
                     (goto-char start-pos)
                     (when (= (point) (point-max))
                       (insert &quot;No results found.\n&quot;)))
                   
                   (goto-char (point-max))
                   (insert &quot;\nRipgrep finished\n&quot;)

                   ;; Highlight search terms using grep&apos;s match face
                   (goto-char (point-min))
                   (forward-line 3)
                   (save-excursion
                     (while (re-search-forward (regexp-quote search-term) nil t)
                       (put-text-property (match-beginning 0) (match-end 0)
                                          &apos;face &apos;match)
                       (put-text-property (match-beginning 0) (match-end 0)
                                          &apos;font-lock-face &apos;match))))
                 
                 ;; Set up keybindings
                 (local-set-key (kbd &quot;D&quot;) 
                                (lambda () 
                                  (interactive)
                                  (my/grep my/grep-search-term 
                                           (read-directory-name &quot;New search directory: &quot;)
                                           my/grep-glob)))
                 (local-set-key (kbd &quot;S&quot;) 
                                (lambda () 
                                  (interactive)
                                  (my/grep (read-string &quot;New search term: &quot;
                                                        nil nil my/grep-search-term)
                                           my/grep-directory
                                           my/grep-glob)))
                 (local-set-key (kbd &quot;o&quot;) 
                                (lambda () 
                                  (interactive)
                                  (my/grep my/grep-search-term
                                           my/grep-directory
                                           (read-string &quot;New glob: &quot;))))
                 (local-set-key (kbd &quot;g&quot;) 
                                (lambda () 
                                  (interactive)
                                  (my/grep my/grep-search-term my/grep-directory my/grep-glob)))
                 
                 (goto-char (point-min))
                 (message &quot;ripgrep finished.&quot;))))
           )
          (message &quot;ripgrep started...&quot;))
      ;; Fallback to rgrep
      (progn
        (setq default-directory directory)
        (message (format &quot;%s : %s : %s&quot; search-term glob directory))
        (rgrep search-term (if (string= &quot;&quot; glob) &quot;*&quot; glob) directory)))))
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;
That&apos;s it. ~100 lines. No dependencies. No packages to manage! (well except ripgrep of course)
&lt;/p&gt;

&lt;p&gt;
Now that I have complete control over this function, I have added further improvements over rgrep, inspired by &lt;code&gt;deadgrep&lt;/code&gt;
&lt;/p&gt;

&lt;ul class=&quot;org-ul&quot;&gt;
&lt;li&gt;&lt;b&gt;&lt;code&gt;S&lt;/code&gt;&lt;/b&gt; - New search term&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&lt;code&gt;D&lt;/code&gt;&lt;/b&gt; - New directory&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&lt;code&gt;o&lt;/code&gt;&lt;/b&gt; - New glob pattern&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&lt;code&gt;g&lt;/code&gt;&lt;/b&gt; - Re-run current search&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;
and a universal argument can be passed through to set these up on the initial grep
&lt;/p&gt;

&lt;p&gt;
I have tried to make the output as similar as possible to rgrep, to be compatible with &lt;code&gt;grep-mode&lt;/code&gt; and for familiarity, so it will be something like:
&lt;/p&gt;

&lt;div class=&quot;org-src-container&quot;&gt;
&lt;pre&gt;&lt;code class=&quot;language-nil&quot;&gt;-*- mode: grep; default-directory: &quot;~/project/&quot; -*-

[o] Glob: *.el

./init.el:42:10:(defun my-function ()
./config.el:156:5:  (my-function)
./helpers.el:89:12:;; Helper for my-function

Ripgrep finished
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;
and if a glob is applied it will display the glob pattern.
&lt;/p&gt;

&lt;p&gt;
Its perfect for offline environments, and yes, I&apos;m banging on about this again!, no network, no package manager, no dependencies (except ripgrep of course!)
&lt;/p&gt;
</content>
</entry>
<entry>
<title>New package dired-video-thumbnail added to MELPA!</title>
<link href="https://www.dyerdwelling.family/emacs/20251231183401-emacs--dired-video-thumbnail/"/>
<id>https://www.dyerdwelling.family/emacs/20251231183401-emacs--dired-video-thumbnail/</id>
<updated>2025-12-31T18:34:00+0000</updated>
<content type="html">&lt;p&gt;
I have created another package!, this time something that I thought was missing from the mighty Emacs and that is the ability to show video thumbnails in a grid and to be able to filter, sort e.t.c.  Basically like an enhanced &lt;code&gt;image-dired&lt;/code&gt;.  I have been increasingly using &lt;code&gt;image-dired&lt;/code&gt; for my image editing and management needs and am always adding little improvements, to such an extent I decided to create a video thumb grid package, enjoy!
&lt;/p&gt;


&lt;div id=&quot;org82089cc&quot; class=&quot;figure&quot;&gt;
&lt;p&gt;&lt;img class=&quot;img-fluid rounded&quot; src=&quot;/static/emacs/20251231183401-emacs--dired-video-thumbnail.jpg&quot; alt=&quot;20251231183401-emacs--dired-video-thumbnail.jpg&quot; width=&quot;100%&quot; /&gt;
&lt;/p&gt;
&lt;/div&gt;
&lt;section id=&quot;outline-container-org12b62fe&quot; class=&quot;outline-2&quot;&gt;
&lt;h2 id=&quot;org12b62fe&quot;&gt;Introduction&lt;/h2&gt;
&lt;div class=&quot;outline-text-2&quot; id=&quot;text-org12b62fe&quot;&gt;
&lt;p&gt;
&lt;code&gt;dired-video-thumbnail&lt;/code&gt; is an Emacs package that provides &lt;code&gt;image-dired&lt;/code&gt; style thumbnail viewing for video files. It extracts thumbnails from videos using &lt;code&gt;ffmpeg&lt;/code&gt; and displays them in a grid layout, allowing you to visually browse and manage video collections directly from Emacs.
&lt;/p&gt;
&lt;/div&gt;
&lt;/section&gt;
&lt;section id=&quot;outline-container-orgc1f862d&quot; class=&quot;outline-2&quot;&gt;
&lt;h2 id=&quot;orgc1f862d&quot;&gt;Features&lt;/h2&gt;
&lt;div class=&quot;outline-text-2&quot; id=&quot;text-orgc1f862d&quot;&gt;
&lt;ul class=&quot;org-ul&quot;&gt;
&lt;li&gt;&lt;b&gt;Thumbnail grid display&lt;/b&gt; - View video thumbnails in a configurable grid layout&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Persistent caching&lt;/b&gt; - Thumbnails are cached and only regenerated when the source file changes&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Async generation&lt;/b&gt; - Emacs remains responsive while thumbnails are generated in the background&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Dired integration&lt;/b&gt; - Marks sync bidirectionally with the associated dired buffer&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Visual mark indication&lt;/b&gt; - Marked thumbnails display a coloured border (like &lt;code&gt;image-dired&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Dynamic header line&lt;/b&gt; - Shows filename, dimensions, duration, and file size for the current video&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Click to play&lt;/b&gt; - Open videos in your preferred external player&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Cross-platform&lt;/b&gt; - Works on Linux, macOS, and Windows&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Resizable thumbnails&lt;/b&gt; - Adjust thumbnail size on the fly&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Sorting&lt;/b&gt; - Sort videos by name, date, size, or duration&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Filtering&lt;/b&gt; - Filter videos by name pattern, duration range, or file size&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Recursive search&lt;/b&gt; - Browse videos across subdirectories with optional auto-recursive mode&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Transient menu&lt;/b&gt; - Comprehensive command menu accessible via &lt;code&gt;.&lt;/code&gt; or &lt;code&gt;C-c .&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/section&gt;
&lt;section id=&quot;outline-container-orgfb2f3b5&quot; class=&quot;outline-2&quot;&gt;
&lt;h2 id=&quot;orgfb2f3b5&quot;&gt;Whats New&lt;/h2&gt;
&lt;div class=&quot;outline-text-2&quot; id=&quot;text-orgfb2f3b5&quot;&gt;
&lt;/div&gt;
&lt;div id=&quot;outline-container-org05a2d45&quot; class=&quot;outline-3&quot;&gt;
&lt;h3 id=&quot;org05a2d45&quot;&gt;&lt;span class=&quot;timestamp-wrapper&quot;&gt;&lt;span class=&quot;timestamp&quot;&gt;&amp;lt;2025-12-15 Mon&amp;gt; &lt;/span&gt;&lt;/span&gt; 0.3.0&lt;/h3&gt;
&lt;div class=&quot;outline-text-3&quot; id=&quot;text-org05a2d45&quot;&gt;
&lt;p&gt;
Added transient menu interface
&lt;/p&gt;

&lt;p&gt;
Introduced a comprehensive transient menu (&lt;code&gt;dired-video-thumbnail-transient&lt;/code&gt;) providing quick access to all commands via &lt;code&gt;.&lt;/code&gt; or &lt;code&gt;C-c .&lt;/code&gt; in the thumbnail buffer. The menu displays current state (sort order, filters, video count, recursive/wrap mode) and organises commands into logical groups: Navigation, Playback, Sorting, Filtering, Marking, Delete, Display, and Other.
&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;outline-container-org8c74e17&quot; class=&quot;outline-3&quot;&gt;
&lt;h3 id=&quot;org8c74e17&quot;&gt;&lt;span class=&quot;timestamp-wrapper&quot;&gt;&lt;span class=&quot;timestamp&quot;&gt;&amp;lt;2025-12-15 Mon&amp;gt; &lt;/span&gt;&lt;/span&gt; 0.2.0&lt;/h3&gt;
&lt;div class=&quot;outline-text-3&quot; id=&quot;text-org8c74e17&quot;&gt;
&lt;p&gt;
Enhanced package with sorting, filtering, and docs
&lt;/p&gt;

&lt;p&gt;
Added sorting and filtering features to &lt;code&gt;dired-video-thumbnail&lt;/code&gt;. Introduced customizable options for sorting and filtering criteria, and implement interactive commands for toggling these settings. Included comprehensive documentation in Texinfo format, covering installation, usage, and customization.
&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/section&gt;
&lt;section id=&quot;outline-container-org0c573bf&quot; class=&quot;outline-2&quot;&gt;
&lt;h2 id=&quot;org0c573bf&quot;&gt;Requirements&lt;/h2&gt;
&lt;div class=&quot;outline-text-2&quot; id=&quot;text-org0c573bf&quot;&gt;
&lt;ul class=&quot;org-ul&quot;&gt;
&lt;li&gt;Emacs 28.1 or later&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://ffmpeg.org/&quot;&gt;ffmpeg&lt;/a&gt; and &lt;code&gt;ffprobe&lt;/code&gt; installed and available in your PATH&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/magit/transient&quot;&gt;transient&lt;/a&gt; 0.4.0 or later (for the transient menu)&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/section&gt;
&lt;section id=&quot;outline-container-org9800fc1&quot; class=&quot;outline-2&quot;&gt;
&lt;h2 id=&quot;org9800fc1&quot;&gt;Installation&lt;/h2&gt;
&lt;div class=&quot;outline-text-2&quot; id=&quot;text-org9800fc1&quot;&gt;
&lt;/div&gt;
&lt;div id=&quot;outline-container-orgc6c8581&quot; class=&quot;outline-3&quot;&gt;
&lt;h3 id=&quot;orgc6c8581&quot;&gt;Manual&lt;/h3&gt;
&lt;div class=&quot;outline-text-3&quot; id=&quot;text-orgc6c8581&quot;&gt;
&lt;p&gt;
Download &lt;code&gt;dired-video-thumbnail.el&lt;/code&gt; and place it in your load-path:
&lt;/p&gt;

&lt;div class=&quot;org-src-container&quot;&gt;
&lt;pre&gt;&lt;code class=&quot;language-emacs-lisp&quot;&gt;(add-to-list &apos;load-path &quot;/path/to/dired-video-thumbnail/&quot;)
(require &apos;dired-video-thumbnail)
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;outline-container-orgb94a384&quot; class=&quot;outline-3&quot;&gt;
&lt;h3 id=&quot;orgb94a384&quot;&gt;use-package&lt;/h3&gt;
&lt;div class=&quot;outline-text-3&quot; id=&quot;text-orgb94a384&quot;&gt;
&lt;div class=&quot;org-src-container&quot;&gt;
&lt;pre&gt;&lt;code class=&quot;language-emacs-lisp&quot;&gt;(use-package dired-video-thumbnail
  :load-path &quot;/path/to/dired-video-thumbnail/&quot;
  :bind (:map dired-mode-map
         (&quot;C-t v&quot; . dired-video-thumbnail)))
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;outline-container-org946b317&quot; class=&quot;outline-3&quot;&gt;
&lt;h3 id=&quot;org946b317&quot;&gt;straight.el&lt;/h3&gt;
&lt;div class=&quot;outline-text-3&quot; id=&quot;text-org946b317&quot;&gt;
&lt;div class=&quot;org-src-container&quot;&gt;
&lt;pre&gt;&lt;code class=&quot;language-emacs-lisp&quot;&gt;(straight-use-package
 &apos;(dired-video-thumbnail :type git :host github :repo &quot;captainflasmr/dired-video-thumbnail&quot;))
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/section&gt;
&lt;section id=&quot;outline-container-org35a191a&quot; class=&quot;outline-2&quot;&gt;
&lt;h2 id=&quot;org35a191a&quot;&gt;Usage&lt;/h2&gt;
&lt;div class=&quot;outline-text-2&quot; id=&quot;text-org35a191a&quot;&gt;
&lt;/div&gt;
&lt;div id=&quot;outline-container-orgd2bac45&quot; class=&quot;outline-3&quot;&gt;
&lt;h3 id=&quot;orgd2bac45&quot;&gt;Basic Usage&lt;/h3&gt;
&lt;div class=&quot;outline-text-3&quot; id=&quot;text-orgd2bac45&quot;&gt;
&lt;ol class=&quot;org-ol&quot;&gt;
&lt;li&gt;Open a directory containing video files in dired&lt;/li&gt;
&lt;li&gt;Run &lt;code&gt;M-x dired-video-thumbnail&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;A new buffer opens displaying thumbnails for all videos in the directory&lt;/li&gt;
&lt;li&gt;The cursor automatically moves to the first thumbnail&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;outline-container-orge79ff34&quot; class=&quot;outline-3&quot;&gt;
&lt;h3 id=&quot;orge79ff34&quot;&gt;With Marked Files&lt;/h3&gt;
&lt;div class=&quot;outline-text-3&quot; id=&quot;text-orge79ff34&quot;&gt;
&lt;ol class=&quot;org-ol&quot;&gt;
&lt;li&gt;In dired, mark specific video files with &lt;code&gt;m&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Run &lt;code&gt;M-x dired-video-thumbnail&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Only thumbnails for the marked videos are displayed&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;outline-container-org9f27dd0&quot; class=&quot;outline-3&quot;&gt;
&lt;h3 id=&quot;org9f27dd0&quot;&gt;Recursive Mode&lt;/h3&gt;
&lt;div class=&quot;outline-text-3&quot; id=&quot;text-org9f27dd0&quot;&gt;
&lt;p&gt;
To include videos from subdirectories:
&lt;/p&gt;

&lt;ul class=&quot;org-ul&quot;&gt;
&lt;li&gt;Use &lt;code&gt;C-u M-x dired-video-thumbnail&lt;/code&gt; (with prefix argument)&lt;/li&gt;
&lt;li&gt;Or run &lt;code&gt;M-x dired-video-thumbnail-recursive&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Or press &lt;code&gt;R&lt;/code&gt; in the thumbnail buffer to toggle recursive mode&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;
When &lt;code&gt;dired-video-thumbnail-auto-recursive&lt;/code&gt; is enabled (the default), the package automatically searches subdirectories if the current directory contains no video files.
&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;outline-container-orga591c06&quot; class=&quot;outline-3&quot;&gt;
&lt;h3 id=&quot;orga591c06&quot;&gt;Suggested Keybinding&lt;/h3&gt;
&lt;div class=&quot;outline-text-3&quot; id=&quot;text-orga591c06&quot;&gt;
&lt;p&gt;
Add a keybinding in dired for quick access:
&lt;/p&gt;

&lt;div class=&quot;org-src-container&quot;&gt;
&lt;pre&gt;&lt;code class=&quot;language-emacs-lisp&quot;&gt;(with-eval-after-load &apos;dired
  (define-key dired-mode-map (kbd &quot;C-t v&quot;) #&apos;dired-video-thumbnail))
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/section&gt;
&lt;section id=&quot;outline-container-org615664d&quot; class=&quot;outline-2&quot;&gt;
&lt;h2 id=&quot;org615664d&quot;&gt;Transient Menu&lt;/h2&gt;
&lt;div class=&quot;outline-text-2&quot; id=&quot;text-org615664d&quot;&gt;
&lt;p&gt;
Press &lt;code&gt;.&lt;/code&gt; or &lt;code&gt;C-c .&lt;/code&gt; in the thumbnail buffer to open the transient menu. This provides a comprehensive interface to all commands with a live status display.
&lt;/p&gt;

&lt;pre class=&quot;example&quot; id=&quot;org6261acf&quot;&gt;
State: Sort: name ↑ | Videos: 42 | Recursive: OFF | Wrap: ON

Navigation            Playback       Sorting              Filtering
n Next                RET Play video s Sort menu...       / Filter menu...
p Previous            o Play video   S Interactive sort   \ Interactive filter
C-n Next row                         r Reverse order      c Clear filters
C-p Previous row
d Go to dired

Marking               Delete         Display              Other
m Mark menu...        D Delete       v Display menu...    g Regenerate thumbnail
M Mark all            x Delete marked+ Larger thumbnails  G Regenerate all
U Unmark all                         - Smaller thumbnails C Clear cache
t Toggle all marks                   w Toggle wrap        ? Help
                                     R Toggle recursive   q Quit menu
                                                          Q Quit buffer
&lt;/pre&gt;

&lt;p&gt;
The status line at the top shows:
&lt;/p&gt;
&lt;ul class=&quot;org-ul&quot;&gt;
&lt;li&gt;Current sort criteria and direction (e.g., &lt;code&gt;name ↑&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Number of videos displayed (and total if filtered)&lt;/li&gt;
&lt;li&gt;Recursive mode status&lt;/li&gt;
&lt;li&gt;Wrap display mode status&lt;/li&gt;
&lt;li&gt;Active filters (if any)&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div id=&quot;outline-container-orgd1c42d6&quot; class=&quot;outline-3&quot;&gt;
&lt;h3 id=&quot;orgd1c42d6&quot;&gt;Submenus&lt;/h3&gt;
&lt;div class=&quot;outline-text-3&quot; id=&quot;text-orgd1c42d6&quot;&gt;
&lt;p&gt;
Several keys open submenus with additional options:
&lt;/p&gt;

&lt;ul class=&quot;org-ul&quot;&gt;
&lt;li&gt;&lt;code&gt;s&lt;/code&gt; - &lt;b&gt;Sort menu&lt;/b&gt;: Sort by name, date, size, or duration; reverse order&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/&lt;/code&gt; - &lt;b&gt;Filter menu&lt;/b&gt;: Filter by name regexp, duration range, or size range; clear filters&lt;/li&gt;
&lt;li&gt;&lt;code&gt;m&lt;/code&gt; - &lt;b&gt;Mark menu&lt;/b&gt;: Mark/unmark current, toggle current, mark/unmark/toggle all&lt;/li&gt;
&lt;li&gt;&lt;code&gt;v&lt;/code&gt; - &lt;b&gt;Display menu&lt;/b&gt;: Adjust size, toggle wrap/recursive, refresh, regenerate thumbnails&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/section&gt;
&lt;section id=&quot;outline-container-orgdcb2189&quot; class=&quot;outline-2&quot;&gt;
&lt;h2 id=&quot;orgdcb2189&quot;&gt;Header Line&lt;/h2&gt;
&lt;div class=&quot;outline-text-2&quot; id=&quot;text-orgdcb2189&quot;&gt;
&lt;p&gt;
As you navigate between thumbnails, the header line dynamically displays information about the current video:
&lt;/p&gt;

&lt;ul class=&quot;org-ul&quot;&gt;
&lt;li&gt;&lt;b&gt;Mark indicator&lt;/b&gt; - A red &lt;code&gt;*&lt;/code&gt; if the video is marked&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Filename&lt;/b&gt; - The video filename in bold&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Dimensions&lt;/b&gt; - Video resolution (e.g., &lt;code&gt;1920x1080&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Duration&lt;/b&gt; - Video length (e.g., &lt;code&gt;5:32&lt;/code&gt; or &lt;code&gt;1:23:45&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;File size&lt;/b&gt; - Size in MB (e.g., &lt;code&gt;45.2 MB&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;
The header also shows current sort settings (e.g., &lt;code&gt;[name ↑]&lt;/code&gt;), active filters, and a &lt;code&gt;[recursive]&lt;/code&gt; indicator when browsing subdirectories.
&lt;/p&gt;
&lt;/div&gt;
&lt;/section&gt;
&lt;section id=&quot;outline-container-orgb24f64c&quot; class=&quot;outline-2&quot;&gt;
&lt;h2 id=&quot;orgb24f64c&quot;&gt;Keybindings&lt;/h2&gt;
&lt;div class=&quot;outline-text-2&quot; id=&quot;text-orgb24f64c&quot;&gt;
&lt;p&gt;
In the &lt;code&gt;*Video Thumbnails*&lt;/code&gt; buffer:
&lt;/p&gt;
&lt;/div&gt;
&lt;div id=&quot;outline-container-org6f8ccaa&quot; class=&quot;outline-3&quot;&gt;
&lt;h3 id=&quot;org6f8ccaa&quot;&gt;Transient Menu&lt;/h3&gt;
&lt;div class=&quot;outline-text-3&quot; id=&quot;text-org6f8ccaa&quot;&gt;
&lt;table class=&quot;table table-striped&quot; border=&quot;2&quot; cellspacing=&quot;0&quot; cellpadding=&quot;6&quot; rules=&quot;groups&quot; frame=&quot;hsides&quot;&gt;


&lt;colgroup&gt;
&lt;col  class=&quot;org-left&quot; /&gt;

&lt;col  class=&quot;org-left&quot; /&gt;

&lt;col  class=&quot;org-left&quot; /&gt;
&lt;/colgroup&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th scope=&quot;col&quot; class=&quot;org-left&quot;&gt;Key&lt;/th&gt;
&lt;th scope=&quot;col&quot; class=&quot;org-left&quot;&gt;Command&lt;/th&gt;
&lt;th scope=&quot;col&quot; class=&quot;org-left&quot;&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td class=&quot;org-left&quot;&gt;&lt;code&gt;.&lt;/code&gt;&lt;/td&gt;
&lt;td class=&quot;org-left&quot;&gt;&lt;code&gt;dired-video-thumbnail-transient&lt;/code&gt;&lt;/td&gt;
&lt;td class=&quot;org-left&quot;&gt;Open transient menu&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td class=&quot;org-left&quot;&gt;&lt;code&gt;C-c .&lt;/code&gt;&lt;/td&gt;
&lt;td class=&quot;org-left&quot;&gt;&lt;code&gt;dired-video-thumbnail-transient&lt;/code&gt;&lt;/td&gt;
&lt;td class=&quot;org-left&quot;&gt;Open transient menu&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;outline-container-org9b3aa1b&quot; class=&quot;outline-3&quot;&gt;
&lt;h3 id=&quot;org9b3aa1b&quot;&gt;Navigation&lt;/h3&gt;
&lt;div class=&quot;outline-text-3&quot; id=&quot;text-org9b3aa1b&quot;&gt;
&lt;table class=&quot;table table-striped&quot; border=&quot;2&quot; cellspacing=&quot;0&quot; cellpadding=&quot;6&quot; rules=&quot;groups&quot; frame=&quot;hsides&quot;&gt;


&lt;colgroup&gt;
&lt;col  class=&quot;org-left&quot; /&gt;

&lt;col  class=&quot;org-left&quot; /&gt;

&lt;col  class=&quot;org-left&quot; /&gt;
&lt;/colgroup&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th scope=&quot;col&quot; class=&quot;org-left&quot;&gt;Key&lt;/th&gt;
&lt;th scope=&quot;col&quot; class=&quot;org-left&quot;&gt;Command&lt;/th&gt;
&lt;th scope=&quot;col&quot; class=&quot;org-left&quot;&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td class=&quot;org-left&quot;&gt;&lt;code&gt;n&lt;/code&gt;&lt;/td&gt;
&lt;td class=&quot;org-left&quot;&gt;&lt;code&gt;dired-video-thumbnail-next&lt;/code&gt;&lt;/td&gt;
&lt;td class=&quot;org-left&quot;&gt;Move to next thumbnail&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td class=&quot;org-left&quot;&gt;&lt;code&gt;p&lt;/code&gt;&lt;/td&gt;
&lt;td class=&quot;org-left&quot;&gt;&lt;code&gt;dired-video-thumbnail-previous&lt;/code&gt;&lt;/td&gt;
&lt;td class=&quot;org-left&quot;&gt;Move to previous thumbnail&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td class=&quot;org-left&quot;&gt;&lt;code&gt;SPC&lt;/code&gt;&lt;/td&gt;
&lt;td class=&quot;org-left&quot;&gt;&lt;code&gt;dired-video-thumbnail-play&lt;/code&gt;&lt;/td&gt;
&lt;td class=&quot;org-left&quot;&gt;Play video at point&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td class=&quot;org-left&quot;&gt;&lt;code&gt;C-f&lt;/code&gt;&lt;/td&gt;
&lt;td class=&quot;org-left&quot;&gt;&lt;code&gt;dired-video-thumbnail-forward&lt;/code&gt;&lt;/td&gt;
&lt;td class=&quot;org-left&quot;&gt;Move to next thumbnail&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td class=&quot;org-left&quot;&gt;&lt;code&gt;C-b&lt;/code&gt;&lt;/td&gt;
&lt;td class=&quot;org-left&quot;&gt;&lt;code&gt;dired-video-thumbnail-backward&lt;/code&gt;&lt;/td&gt;
&lt;td class=&quot;org-left&quot;&gt;Move to previous thumbnail&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td class=&quot;org-left&quot;&gt;&lt;code&gt;&amp;lt;right&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td class=&quot;org-left&quot;&gt;&lt;code&gt;dired-video-thumbnail-forward&lt;/code&gt;&lt;/td&gt;
&lt;td class=&quot;org-left&quot;&gt;Move to next thumbnail&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td class=&quot;org-left&quot;&gt;&lt;code&gt;&amp;lt;left&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td class=&quot;org-left&quot;&gt;&lt;code&gt;dired-video-thumbnail-backward&lt;/code&gt;&lt;/td&gt;
&lt;td class=&quot;org-left&quot;&gt;Move to previous thumbnail&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td class=&quot;org-left&quot;&gt;&lt;code&gt;&amp;lt;up&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td class=&quot;org-left&quot;&gt;&lt;code&gt;dired-video-thumbnail-previous-row&lt;/code&gt;&lt;/td&gt;
&lt;td class=&quot;org-left&quot;&gt;Move up one row&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td class=&quot;org-left&quot;&gt;&lt;code&gt;&amp;lt;down&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td class=&quot;org-left&quot;&gt;&lt;code&gt;dired-video-thumbnail-next-row&lt;/code&gt;&lt;/td&gt;
&lt;td class=&quot;org-left&quot;&gt;Move down one row&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td class=&quot;org-left&quot;&gt;&lt;code&gt;d&lt;/code&gt;&lt;/td&gt;
&lt;td class=&quot;org-left&quot;&gt;&lt;code&gt;dired-video-thumbnail-goto-dired&lt;/code&gt;&lt;/td&gt;
&lt;td class=&quot;org-left&quot;&gt;Switch to associated dired buffer&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td class=&quot;org-left&quot;&gt;&lt;code&gt;q&lt;/code&gt;&lt;/td&gt;
&lt;td class=&quot;org-left&quot;&gt;&lt;code&gt;quit-window&lt;/code&gt;&lt;/td&gt;
&lt;td class=&quot;org-left&quot;&gt;Close the thumbnail buffer&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td class=&quot;org-left&quot;&gt;&lt;code&gt;Q&lt;/code&gt;&lt;/td&gt;
&lt;td class=&quot;org-left&quot;&gt;&lt;code&gt;dired-video-thumbnail-quit-and-kill&lt;/code&gt;&lt;/td&gt;
&lt;td class=&quot;org-left&quot;&gt;Quit and kill the buffer&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;outline-container-org6eb107f&quot; class=&quot;outline-3&quot;&gt;
&lt;h3 id=&quot;org6eb107f&quot;&gt;Playback&lt;/h3&gt;
&lt;div class=&quot;outline-text-3&quot; id=&quot;text-org6eb107f&quot;&gt;
&lt;table class=&quot;table table-striped&quot; border=&quot;2&quot; cellspacing=&quot;0&quot; cellpadding=&quot;6&quot; rules=&quot;groups&quot; frame=&quot;hsides&quot;&gt;


&lt;colgroup&gt;
&lt;col  class=&quot;org-left&quot; /&gt;

&lt;col  class=&quot;org-left&quot; /&gt;

&lt;col  class=&quot;org-left&quot; /&gt;
&lt;/colgroup&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th scope=&quot;col&quot; class=&quot;org-left&quot;&gt;Key&lt;/th&gt;
&lt;th scope=&quot;col&quot; class=&quot;org-left&quot;&gt;Command&lt;/th&gt;
&lt;th scope=&quot;col&quot; class=&quot;org-left&quot;&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td class=&quot;org-left&quot;&gt;&lt;code&gt;RET&lt;/code&gt;&lt;/td&gt;
&lt;td class=&quot;org-left&quot;&gt;&lt;code&gt;dired-video-thumbnail-play&lt;/code&gt;&lt;/td&gt;
&lt;td class=&quot;org-left&quot;&gt;Play video at point&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td class=&quot;org-left&quot;&gt;&lt;code&gt;o&lt;/code&gt;&lt;/td&gt;
&lt;td class=&quot;org-left&quot;&gt;&lt;code&gt;dired-video-thumbnail-play&lt;/code&gt;&lt;/td&gt;
&lt;td class=&quot;org-left&quot;&gt;Play video at point&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td class=&quot;org-left&quot;&gt;&lt;code&gt;mouse-1&lt;/code&gt;&lt;/td&gt;
&lt;td class=&quot;org-left&quot;&gt;&lt;code&gt;dired-video-thumbnail-play&lt;/code&gt;&lt;/td&gt;
&lt;td class=&quot;org-left&quot;&gt;Play video (click)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;
On Linux, videos open with &lt;code&gt;xdg-open&lt;/code&gt;. On macOS, they open with &lt;code&gt;open&lt;/code&gt;. On Windows, they open with the system default player. You can also specify a custom player.
&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;outline-container-orgbf6f606&quot; class=&quot;outline-3&quot;&gt;
&lt;h3 id=&quot;orgbf6f606&quot;&gt;Marking&lt;/h3&gt;
&lt;div class=&quot;outline-text-3&quot; id=&quot;text-orgbf6f606&quot;&gt;
&lt;p&gt;
Marks are synchronised with the associated dired buffer, so marking a video in the thumbnail view also marks it in dired, and vice versa.
&lt;/p&gt;

&lt;table class=&quot;table table-striped&quot; border=&quot;2&quot; cellspacing=&quot;0&quot; cellpadding=&quot;6&quot; rules=&quot;groups&quot; frame=&quot;hsides&quot;&gt;


&lt;colgroup&gt;
&lt;col  class=&quot;org-left&quot; /&gt;

&lt;col  class=&quot;org-left&quot; /&gt;

&lt;col  class=&quot;org-left&quot; /&gt;
&lt;/colgroup&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th scope=&quot;col&quot; class=&quot;org-left&quot;&gt;Key&lt;/th&gt;
&lt;th scope=&quot;col&quot; class=&quot;org-left&quot;&gt;Command&lt;/th&gt;
&lt;th scope=&quot;col&quot; class=&quot;org-left&quot;&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td class=&quot;org-left&quot;&gt;&lt;code&gt;m&lt;/code&gt;&lt;/td&gt;
&lt;td class=&quot;org-left&quot;&gt;&lt;code&gt;dired-video-thumbnail-mark&lt;/code&gt;&lt;/td&gt;
&lt;td class=&quot;org-left&quot;&gt;Mark video and move to next&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td class=&quot;org-left&quot;&gt;&lt;code&gt;u&lt;/code&gt;&lt;/td&gt;
&lt;td class=&quot;org-left&quot;&gt;&lt;code&gt;dired-video-thumbnail-unmark&lt;/code&gt;&lt;/td&gt;
&lt;td class=&quot;org-left&quot;&gt;Unmark video and move to next&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td class=&quot;org-left&quot;&gt;&lt;code&gt;mouse-3&lt;/code&gt;&lt;/td&gt;
&lt;td class=&quot;org-left&quot;&gt;&lt;code&gt;dired-video-thumbnail-toggle-mark&lt;/code&gt;&lt;/td&gt;
&lt;td class=&quot;org-left&quot;&gt;Toggle mark (right-click)&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td class=&quot;org-left&quot;&gt;&lt;code&gt;M&lt;/code&gt;&lt;/td&gt;
&lt;td class=&quot;org-left&quot;&gt;&lt;code&gt;dired-video-thumbnail-mark-all&lt;/code&gt;&lt;/td&gt;
&lt;td class=&quot;org-left&quot;&gt;Mark all videos&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td class=&quot;org-left&quot;&gt;&lt;code&gt;U&lt;/code&gt;&lt;/td&gt;
&lt;td class=&quot;org-left&quot;&gt;&lt;code&gt;dired-video-thumbnail-unmark-all&lt;/code&gt;&lt;/td&gt;
&lt;td class=&quot;org-left&quot;&gt;Unmark all videos&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td class=&quot;org-left&quot;&gt;&lt;code&gt;t&lt;/code&gt;&lt;/td&gt;
&lt;td class=&quot;org-left&quot;&gt;&lt;code&gt;dired-video-thumbnail-toggle-all-marks&lt;/code&gt;&lt;/td&gt;
&lt;td class=&quot;org-left&quot;&gt;Invert all marks&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;outline-container-orgada074d&quot; class=&quot;outline-3&quot;&gt;
&lt;h3 id=&quot;orgada074d&quot;&gt;Deletion&lt;/h3&gt;
&lt;div class=&quot;outline-text-3&quot; id=&quot;text-orgada074d&quot;&gt;
&lt;table class=&quot;table table-striped&quot; border=&quot;2&quot; cellspacing=&quot;0&quot; cellpadding=&quot;6&quot; rules=&quot;groups&quot; frame=&quot;hsides&quot;&gt;


&lt;colgroup&gt;
&lt;col  class=&quot;org-left&quot; /&gt;

&lt;col  class=&quot;org-left&quot; /&gt;

&lt;col  class=&quot;org-left&quot; /&gt;
&lt;/colgroup&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th scope=&quot;col&quot; class=&quot;org-left&quot;&gt;Key&lt;/th&gt;
&lt;th scope=&quot;col&quot; class=&quot;org-left&quot;&gt;Command&lt;/th&gt;
&lt;th scope=&quot;col&quot; class=&quot;org-left&quot;&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td class=&quot;org-left&quot;&gt;&lt;code&gt;D&lt;/code&gt;&lt;/td&gt;
&lt;td class=&quot;org-left&quot;&gt;&lt;code&gt;dired-video-thumbnail-delete&lt;/code&gt;&lt;/td&gt;
&lt;td class=&quot;org-left&quot;&gt;Delete video at point (with confirmation)&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td class=&quot;org-left&quot;&gt;&lt;code&gt;x&lt;/code&gt;&lt;/td&gt;
&lt;td class=&quot;org-left&quot;&gt;&lt;code&gt;dired-video-thumbnail-delete-marked&lt;/code&gt;&lt;/td&gt;
&lt;td class=&quot;org-left&quot;&gt;Delete marked videos (with confirmation)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;outline-container-orgc045ad7&quot; class=&quot;outline-3&quot;&gt;
&lt;h3 id=&quot;orgc045ad7&quot;&gt;Display&lt;/h3&gt;
&lt;div class=&quot;outline-text-3&quot; id=&quot;text-orgc045ad7&quot;&gt;
&lt;table class=&quot;table table-striped&quot; border=&quot;2&quot; cellspacing=&quot;0&quot; cellpadding=&quot;6&quot; rules=&quot;groups&quot; frame=&quot;hsides&quot;&gt;


&lt;colgroup&gt;
&lt;col  class=&quot;org-left&quot; /&gt;

&lt;col  class=&quot;org-left&quot; /&gt;

&lt;col  class=&quot;org-left&quot; /&gt;
&lt;/colgroup&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th scope=&quot;col&quot; class=&quot;org-left&quot;&gt;Key&lt;/th&gt;
&lt;th scope=&quot;col&quot; class=&quot;org-left&quot;&gt;Command&lt;/th&gt;
&lt;th scope=&quot;col&quot; class=&quot;org-left&quot;&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td class=&quot;org-left&quot;&gt;&lt;code&gt;+&lt;/code&gt;&lt;/td&gt;
&lt;td class=&quot;org-left&quot;&gt;&lt;code&gt;dired-video-thumbnail-increase-size&lt;/code&gt;&lt;/td&gt;
&lt;td class=&quot;org-left&quot;&gt;Increase thumbnail size&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td class=&quot;org-left&quot;&gt;&lt;code&gt;-&lt;/code&gt;&lt;/td&gt;
&lt;td class=&quot;org-left&quot;&gt;&lt;code&gt;dired-video-thumbnail-decrease-size&lt;/code&gt;&lt;/td&gt;
&lt;td class=&quot;org-left&quot;&gt;Decrease thumbnail size&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td class=&quot;org-left&quot;&gt;&lt;code&gt;r&lt;/code&gt;&lt;/td&gt;
&lt;td class=&quot;org-left&quot;&gt;&lt;code&gt;dired-video-thumbnail-refresh&lt;/code&gt;&lt;/td&gt;
&lt;td class=&quot;org-left&quot;&gt;Refresh the display&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td class=&quot;org-left&quot;&gt;&lt;code&gt;w&lt;/code&gt;&lt;/td&gt;
&lt;td class=&quot;org-left&quot;&gt;&lt;code&gt;dired-video-thumbnail-toggle-wrap&lt;/code&gt;&lt;/td&gt;
&lt;td class=&quot;org-left&quot;&gt;Toggle wrap mode (flow vs fixed cols)&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td class=&quot;org-left&quot;&gt;&lt;code&gt;R&lt;/code&gt;&lt;/td&gt;
&lt;td class=&quot;org-left&quot;&gt;&lt;code&gt;dired-video-thumbnail-toggle-recursive&lt;/code&gt;&lt;/td&gt;
&lt;td class=&quot;org-left&quot;&gt;Toggle recursive directory search&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td class=&quot;org-left&quot;&gt;&lt;code&gt;g&lt;/code&gt;&lt;/td&gt;
&lt;td class=&quot;org-left&quot;&gt;&lt;code&gt;dired-video-thumbnail-regenerate&lt;/code&gt;&lt;/td&gt;
&lt;td class=&quot;org-left&quot;&gt;Regenerate thumbnail at point&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td class=&quot;org-left&quot;&gt;&lt;code&gt;G&lt;/code&gt;&lt;/td&gt;
&lt;td class=&quot;org-left&quot;&gt;&lt;code&gt;dired-video-thumbnail-regenerate-all&lt;/code&gt;&lt;/td&gt;
&lt;td class=&quot;org-left&quot;&gt;Regenerate all thumbnails&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;outline-container-orgd55209e&quot; class=&quot;outline-3&quot;&gt;
&lt;h3 id=&quot;orgd55209e&quot;&gt;Sorting&lt;/h3&gt;
&lt;div class=&quot;outline-text-3&quot; id=&quot;text-orgd55209e&quot;&gt;
&lt;table class=&quot;table table-striped&quot; border=&quot;2&quot; cellspacing=&quot;0&quot; cellpadding=&quot;6&quot; rules=&quot;groups&quot; frame=&quot;hsides&quot;&gt;


&lt;colgroup&gt;
&lt;col  class=&quot;org-left&quot; /&gt;

&lt;col  class=&quot;org-left&quot; /&gt;

&lt;col  class=&quot;org-left&quot; /&gt;
&lt;/colgroup&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th scope=&quot;col&quot; class=&quot;org-left&quot;&gt;Key&lt;/th&gt;
&lt;th scope=&quot;col&quot; class=&quot;org-left&quot;&gt;Command&lt;/th&gt;
&lt;th scope=&quot;col&quot; class=&quot;org-left&quot;&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td class=&quot;org-left&quot;&gt;&lt;code&gt;S&lt;/code&gt;&lt;/td&gt;
&lt;td class=&quot;org-left&quot;&gt;&lt;code&gt;dired-video-thumbnail-sort&lt;/code&gt;&lt;/td&gt;
&lt;td class=&quot;org-left&quot;&gt;Interactive sort menu&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td class=&quot;org-left&quot;&gt;&lt;code&gt;sn&lt;/code&gt;&lt;/td&gt;
&lt;td class=&quot;org-left&quot;&gt;&lt;code&gt;dired-video-thumbnail-sort-by-name&lt;/code&gt;&lt;/td&gt;
&lt;td class=&quot;org-left&quot;&gt;Sort by filename&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td class=&quot;org-left&quot;&gt;&lt;code&gt;sd&lt;/code&gt;&lt;/td&gt;
&lt;td class=&quot;org-left&quot;&gt;&lt;code&gt;dired-video-thumbnail-sort-by-date&lt;/code&gt;&lt;/td&gt;
&lt;td class=&quot;org-left&quot;&gt;Sort by modification date&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td class=&quot;org-left&quot;&gt;&lt;code&gt;ss&lt;/code&gt;&lt;/td&gt;
&lt;td class=&quot;org-left&quot;&gt;&lt;code&gt;dired-video-thumbnail-sort-by-size&lt;/code&gt;&lt;/td&gt;
&lt;td class=&quot;org-left&quot;&gt;Sort by file size&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td class=&quot;org-left&quot;&gt;&lt;code&gt;sD&lt;/code&gt;&lt;/td&gt;
&lt;td class=&quot;org-left&quot;&gt;&lt;code&gt;dired-video-thumbnail-sort-by-duration&lt;/code&gt;&lt;/td&gt;
&lt;td class=&quot;org-left&quot;&gt;Sort by video duration&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td class=&quot;org-left&quot;&gt;&lt;code&gt;sr&lt;/code&gt;&lt;/td&gt;
&lt;td class=&quot;org-left&quot;&gt;&lt;code&gt;dired-video-thumbnail-sort-reverse&lt;/code&gt;&lt;/td&gt;
&lt;td class=&quot;org-left&quot;&gt;Reverse sort order&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;outline-container-org1fd0231&quot; class=&quot;outline-3&quot;&gt;
&lt;h3 id=&quot;org1fd0231&quot;&gt;Filtering&lt;/h3&gt;
&lt;div class=&quot;outline-text-3&quot; id=&quot;text-org1fd0231&quot;&gt;
&lt;table class=&quot;table table-striped&quot; border=&quot;2&quot; cellspacing=&quot;0&quot; cellpadding=&quot;6&quot; rules=&quot;groups&quot; frame=&quot;hsides&quot;&gt;


&lt;colgroup&gt;
&lt;col  class=&quot;org-left&quot; /&gt;

&lt;col  class=&quot;org-left&quot; /&gt;

&lt;col  class=&quot;org-left&quot; /&gt;
&lt;/colgroup&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th scope=&quot;col&quot; class=&quot;org-left&quot;&gt;Key&lt;/th&gt;
&lt;th scope=&quot;col&quot; class=&quot;org-left&quot;&gt;Command&lt;/th&gt;
&lt;th scope=&quot;col&quot; class=&quot;org-left&quot;&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td class=&quot;org-left&quot;&gt;&lt;code&gt;\&lt;/code&gt;&lt;/td&gt;
&lt;td class=&quot;org-left&quot;&gt;&lt;code&gt;dired-video-thumbnail-filter&lt;/code&gt;&lt;/td&gt;
&lt;td class=&quot;org-left&quot;&gt;Interactive filter menu&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td class=&quot;org-left&quot;&gt;&lt;code&gt;/n&lt;/code&gt;&lt;/td&gt;
&lt;td class=&quot;org-left&quot;&gt;&lt;code&gt;dired-video-thumbnail-filter-by-name&lt;/code&gt;&lt;/td&gt;
&lt;td class=&quot;org-left&quot;&gt;Filter by name regexp&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td class=&quot;org-left&quot;&gt;&lt;code&gt;/d&lt;/code&gt;&lt;/td&gt;
&lt;td class=&quot;org-left&quot;&gt;&lt;code&gt;dired-video-thumbnail-filter-by-duration&lt;/code&gt;&lt;/td&gt;
&lt;td class=&quot;org-left&quot;&gt;Filter by duration range&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td class=&quot;org-left&quot;&gt;&lt;code&gt;/s&lt;/code&gt;&lt;/td&gt;
&lt;td class=&quot;org-left&quot;&gt;&lt;code&gt;dired-video-thumbnail-filter-by-size&lt;/code&gt;&lt;/td&gt;
&lt;td class=&quot;org-left&quot;&gt;Filter by size range&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td class=&quot;org-left&quot;&gt;&lt;code&gt;/c&lt;/code&gt;&lt;/td&gt;
&lt;td class=&quot;org-left&quot;&gt;&lt;code&gt;dired-video-thumbnail-filter-clear&lt;/code&gt;&lt;/td&gt;
&lt;td class=&quot;org-left&quot;&gt;Clear all filters&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td class=&quot;org-left&quot;&gt;&lt;code&gt;//&lt;/code&gt;&lt;/td&gt;
&lt;td class=&quot;org-left&quot;&gt;&lt;code&gt;dired-video-thumbnail-filter-clear&lt;/code&gt;&lt;/td&gt;
&lt;td class=&quot;org-left&quot;&gt;Clear all filters&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;outline-container-org50f9351&quot; class=&quot;outline-3&quot;&gt;
&lt;h3 id=&quot;org50f9351&quot;&gt;Help&lt;/h3&gt;
&lt;div class=&quot;outline-text-3&quot; id=&quot;text-org50f9351&quot;&gt;
&lt;table class=&quot;table table-striped&quot; border=&quot;2&quot; cellspacing=&quot;0&quot; cellpadding=&quot;6&quot; rules=&quot;groups&quot; frame=&quot;hsides&quot;&gt;


&lt;colgroup&gt;
&lt;col  class=&quot;org-left&quot; /&gt;

&lt;col  class=&quot;org-left&quot; /&gt;

&lt;col  class=&quot;org-left&quot; /&gt;
&lt;/colgroup&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th scope=&quot;col&quot; class=&quot;org-left&quot;&gt;Key&lt;/th&gt;
&lt;th scope=&quot;col&quot; class=&quot;org-left&quot;&gt;Command&lt;/th&gt;
&lt;th scope=&quot;col&quot; class=&quot;org-left&quot;&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td class=&quot;org-left&quot;&gt;&lt;code&gt;h&lt;/code&gt;&lt;/td&gt;
&lt;td class=&quot;org-left&quot;&gt;&lt;code&gt;dired-video-thumbnail-help&lt;/code&gt;&lt;/td&gt;
&lt;td class=&quot;org-left&quot;&gt;Show help&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td class=&quot;org-left&quot;&gt;&lt;code&gt;?&lt;/code&gt;&lt;/td&gt;
&lt;td class=&quot;org-left&quot;&gt;&lt;code&gt;dired-video-thumbnail-help&lt;/code&gt;&lt;/td&gt;
&lt;td class=&quot;org-left&quot;&gt;Show help&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/section&gt;
&lt;section id=&quot;outline-container-orgd5c3b69&quot; class=&quot;outline-2&quot;&gt;
&lt;h2 id=&quot;orgd5c3b69&quot;&gt;Customisation&lt;/h2&gt;
&lt;div class=&quot;outline-text-2&quot; id=&quot;text-orgd5c3b69&quot;&gt;
&lt;p&gt;
All customisation options are in the &lt;code&gt;dired-video-thumbnail&lt;/code&gt; group. Use &lt;code&gt;M-x customize-group RET dired-video-thumbnail RET&lt;/code&gt; to browse them interactively.
&lt;/p&gt;
&lt;/div&gt;
&lt;div id=&quot;outline-container-org186de3b&quot; class=&quot;outline-3&quot;&gt;
&lt;h3 id=&quot;org186de3b&quot;&gt;Thumbnail Cache Location&lt;/h3&gt;
&lt;div class=&quot;outline-text-3&quot; id=&quot;text-org186de3b&quot;&gt;
&lt;p&gt;
Thumbnails are stored in &lt;code&gt;~/.emacs.d/dired-video-thumbnails/&lt;/code&gt; by default:
&lt;/p&gt;

&lt;div class=&quot;org-src-container&quot;&gt;
&lt;pre&gt;&lt;code class=&quot;language-emacs-lisp&quot;&gt;(setq dired-video-thumbnail-cache-dir &quot;~/path/to/cache/&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;outline-container-org3c14c5e&quot; class=&quot;outline-3&quot;&gt;
&lt;h3 id=&quot;org3c14c5e&quot;&gt;Thumbnail Size&lt;/h3&gt;
&lt;div class=&quot;outline-text-3&quot; id=&quot;text-org3c14c5e&quot;&gt;
&lt;p&gt;
Control the generated thumbnail size and display height:
&lt;/p&gt;

&lt;div class=&quot;org-src-container&quot;&gt;
&lt;pre&gt;&lt;code class=&quot;language-emacs-lisp&quot;&gt;(setq dired-video-thumbnail-size 200)           ;; Generated thumbnail size (pixels)
(setq dired-video-thumbnail-display-height 150) ;; Display height in buffer
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;
Thumbnails are generated as squares to ensure consistent grid alignment regardless of video aspect ratio.
&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;outline-container-org587f674&quot; class=&quot;outline-3&quot;&gt;
&lt;h3 id=&quot;org587f674&quot;&gt;Grid Layout&lt;/h3&gt;
&lt;div class=&quot;outline-text-3&quot; id=&quot;text-org587f674&quot;&gt;
&lt;p&gt;
Set the number of columns in the thumbnail grid:
&lt;/p&gt;

&lt;div class=&quot;org-src-container&quot;&gt;
&lt;pre&gt;&lt;code class=&quot;language-emacs-lisp&quot;&gt;(setq dired-video-thumbnail-columns 4)
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;outline-container-org1955c72&quot; class=&quot;outline-3&quot;&gt;
&lt;h3 id=&quot;org1955c72&quot;&gt;Wrap Display&lt;/h3&gt;
&lt;div class=&quot;outline-text-3&quot; id=&quot;text-org1955c72&quot;&gt;
&lt;p&gt;
Control whether thumbnails wrap to fill the window width:
&lt;/p&gt;

&lt;div class=&quot;org-src-container&quot;&gt;
&lt;pre&gt;&lt;code class=&quot;language-emacs-lisp&quot;&gt;(setq dired-video-thumbnail-wrap-display t)   ;; Wrap to window width (default)
(setq dired-video-thumbnail-wrap-display nil) ;; Use fixed columns
(setq dired-video-thumbnail-spacing 4)        ;; Spacing between thumbnails (pixels)
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;outline-container-org12cfc64&quot; class=&quot;outline-3&quot;&gt;
&lt;h3 id=&quot;org12cfc64&quot;&gt;Thumbnail Timestamp&lt;/h3&gt;
&lt;div class=&quot;outline-text-3&quot; id=&quot;text-org12cfc64&quot;&gt;
&lt;p&gt;
By default, thumbnails are extracted at 5 seconds into the video. Change this to get a more representative frame:
&lt;/p&gt;

&lt;div class=&quot;org-src-container&quot;&gt;
&lt;pre&gt;&lt;code class=&quot;language-emacs-lisp&quot;&gt;(setq dired-video-thumbnail-timestamp &quot;00:00:10&quot;)  ;; 10 seconds in
(setq dired-video-thumbnail-timestamp &quot;00:01:00&quot;)  ;; 1 minute in
(setq dired-video-thumbnail-timestamp nil)         ;; Let ffmpeg choose
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;outline-container-orgb212b3d&quot; class=&quot;outline-3&quot;&gt;
&lt;h3 id=&quot;orgb212b3d&quot;&gt;Video Player&lt;/h3&gt;
&lt;div class=&quot;outline-text-3&quot; id=&quot;text-orgb212b3d&quot;&gt;
&lt;p&gt;
Set your preferred video player:
&lt;/p&gt;

&lt;div class=&quot;org-src-container&quot;&gt;
&lt;pre&gt;&lt;code class=&quot;language-emacs-lisp&quot;&gt;(setq dired-video-thumbnail-video-player &quot;mpv&quot;)
(setq dired-video-thumbnail-video-player &quot;vlc&quot;)
(setq dired-video-thumbnail-video-player nil)  ;; Use system default
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;
When set to &lt;code&gt;nil&lt;/code&gt; (the default), videos open with:
&lt;/p&gt;
&lt;ul class=&quot;org-ul&quot;&gt;
&lt;li&gt;Linux: &lt;code&gt;xdg-open&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;macOS: &lt;code&gt;open&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Windows: System default player (e.g., Films &amp;amp; TV)&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;outline-container-orgd15c407&quot; class=&quot;outline-3&quot;&gt;
&lt;h3 id=&quot;orgd15c407&quot;&gt;Video Extensions&lt;/h3&gt;
&lt;div class=&quot;outline-text-3&quot; id=&quot;text-orgd15c407&quot;&gt;
&lt;p&gt;
Add or modify recognised video file extensions:
&lt;/p&gt;

&lt;div class=&quot;org-src-container&quot;&gt;
&lt;pre&gt;&lt;code class=&quot;language-emacs-lisp&quot;&gt;(setq dired-video-thumbnail-video-extensions
      &apos;(&quot;mp4&quot; &quot;mkv&quot; &quot;avi&quot; &quot;mov&quot; &quot;webm&quot; &quot;m4v&quot; &quot;wmv&quot; &quot;flv&quot; &quot;mpeg&quot; &quot;mpg&quot; &quot;ogv&quot; &quot;3gp&quot;))
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;outline-container-orgcd594d5&quot; class=&quot;outline-3&quot;&gt;
&lt;h3 id=&quot;orgcd594d5&quot;&gt;Mark Border Appearance&lt;/h3&gt;
&lt;div class=&quot;outline-text-3&quot; id=&quot;text-orgcd594d5&quot;&gt;
&lt;p&gt;
Marked thumbnails are indicated with a coloured border. Customise the border width and colour:
&lt;/p&gt;

&lt;div class=&quot;org-src-container&quot;&gt;
&lt;pre&gt;&lt;code class=&quot;language-emacs-lisp&quot;&gt;(setq dired-video-thumbnail-mark-border-width 4)  ;; Border width in pixels

;; Change border colour via the face
(set-face-foreground &apos;dired-video-thumbnail-mark &quot;blue&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;outline-container-org91117e8&quot; class=&quot;outline-3&quot;&gt;
&lt;h3 id=&quot;org91117e8&quot;&gt;Default Sorting&lt;/h3&gt;
&lt;div class=&quot;outline-text-3&quot; id=&quot;text-org91117e8&quot;&gt;
&lt;p&gt;
Set the default sort criteria and order:
&lt;/p&gt;

&lt;div class=&quot;org-src-container&quot;&gt;
&lt;pre&gt;&lt;code class=&quot;language-emacs-lisp&quot;&gt;(setq dired-video-thumbnail-sort-by &apos;name)       ;; Options: name, date, size, duration
(setq dired-video-thumbnail-sort-order &apos;ascending) ;; Options: ascending, descending
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;outline-container-orga5465ef&quot; class=&quot;outline-3&quot;&gt;
&lt;h3 id=&quot;orga5465ef&quot;&gt;Recursive Search&lt;/h3&gt;
&lt;div class=&quot;outline-text-3&quot; id=&quot;text-orga5465ef&quot;&gt;
&lt;p&gt;
Control recursive directory searching behaviour:
&lt;/p&gt;

&lt;div class=&quot;org-src-container&quot;&gt;
&lt;pre&gt;&lt;code class=&quot;language-emacs-lisp&quot;&gt;(setq dired-video-thumbnail-recursive nil)       ;; Always search recursively
(setq dired-video-thumbnail-auto-recursive t)    ;; Auto-recursive when no local videos (default)
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;
When &lt;code&gt;dired-video-thumbnail-auto-recursive&lt;/code&gt; is enabled and the current directory has no video files but has subdirectories, the package automatically searches recursively.
&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;outline-container-org64ee6a0&quot; class=&quot;outline-3&quot;&gt;
&lt;h3 id=&quot;org64ee6a0&quot;&gt;ffmpeg Path&lt;/h3&gt;
&lt;div class=&quot;outline-text-3&quot; id=&quot;text-org64ee6a0&quot;&gt;
&lt;p&gt;
If ffmpeg/ffprobe are not in your PATH:
&lt;/p&gt;

&lt;div class=&quot;org-src-container&quot;&gt;
&lt;pre&gt;&lt;code class=&quot;language-emacs-lisp&quot;&gt;(setq dired-video-thumbnail-ffmpeg-program &quot;/usr/local/bin/ffmpeg&quot;)
(setq dired-video-thumbnail-ffprobe-program &quot;/usr/local/bin/ffprobe&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/section&gt;
&lt;section id=&quot;outline-container-org76941c9&quot; class=&quot;outline-2&quot;&gt;
&lt;h2 id=&quot;org76941c9&quot;&gt;Example Configuration&lt;/h2&gt;
&lt;div class=&quot;outline-text-2&quot; id=&quot;text-org76941c9&quot;&gt;
&lt;div class=&quot;org-src-container&quot;&gt;
&lt;pre&gt;&lt;code class=&quot;language-emacs-lisp&quot;&gt;(use-package dired-video-thumbnail
  :load-path &quot;/path/to/dired-video-thumbnail/&quot;
  :bind (:map dired-mode-map
         (&quot;C-t v&quot; . dired-video-thumbnail))
  :custom
  (dired-video-thumbnail-size 250)
  (dired-video-thumbnail-display-height 180)
  (dired-video-thumbnail-columns 5)
  (dired-video-thumbnail-timestamp &quot;00:00:10&quot;)
  (dired-video-thumbnail-video-player nil)  ;; Use system default
  (dired-video-thumbnail-mark-border-width 5)
  (dired-video-thumbnail-sort-by &apos;date)
  (dired-video-thumbnail-sort-order &apos;descending)
  (dired-video-thumbnail-auto-recursive t)
  :custom-face
  (dired-video-thumbnail-mark ((t (:foreground &quot;orange&quot;)))))
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/section&gt;
&lt;section id=&quot;outline-container-org444c171&quot; class=&quot;outline-2&quot;&gt;
&lt;h2 id=&quot;org444c171&quot;&gt;Cache Management&lt;/h2&gt;
&lt;div class=&quot;outline-text-2&quot; id=&quot;text-org444c171&quot;&gt;
&lt;p&gt;
Thumbnails are cached based on the file path and modification time. If you modify a video file, the thumbnail will be automatically regenerated on next view.
&lt;/p&gt;

&lt;p&gt;
Video metadata (dimensions, duration) is also cached in memory to avoid repeated calls to &lt;code&gt;ffprobe&lt;/code&gt;.
&lt;/p&gt;

&lt;p&gt;
To manually clear the thumbnail cache:
&lt;/p&gt;

&lt;div class=&quot;org-src-container&quot;&gt;
&lt;pre&gt;&lt;code class=&quot;language-emacs-lisp&quot;&gt;M-x dired-video-thumbnail-clear-cache
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/section&gt;
&lt;section id=&quot;outline-container-org811b993&quot; class=&quot;outline-2&quot;&gt;
&lt;h2 id=&quot;org811b993&quot;&gt;Workflow Examples&lt;/h2&gt;
&lt;div class=&quot;outline-text-2&quot; id=&quot;text-org811b993&quot;&gt;
&lt;/div&gt;
&lt;div id=&quot;outline-container-org18fb3d0&quot; class=&quot;outline-3&quot;&gt;
&lt;h3 id=&quot;org18fb3d0&quot;&gt;Reviewing and Deleting Unwanted Videos&lt;/h3&gt;
&lt;div class=&quot;outline-text-3&quot; id=&quot;text-org18fb3d0&quot;&gt;
&lt;ol class=&quot;org-ol&quot;&gt;
&lt;li&gt;Open a directory with videos in dired&lt;/li&gt;
&lt;li&gt;&lt;code&gt;C-t v&lt;/code&gt; to open thumbnail view&lt;/li&gt;
&lt;li&gt;Browse thumbnails with &lt;code&gt;n&lt;/code&gt;, &lt;code&gt;p&lt;/code&gt;, &lt;code&gt;SPC&lt;/code&gt;, or arrow keys&lt;/li&gt;
&lt;li&gt;Press &lt;code&gt;D&lt;/code&gt; to delete individual videos, or mark with &lt;code&gt;m&lt;/code&gt; and delete with &lt;code&gt;x&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;outline-container-orga8281c2&quot; class=&quot;outline-3&quot;&gt;
&lt;h3 id=&quot;orga8281c2&quot;&gt;Selecting Videos for Processing&lt;/h3&gt;
&lt;div class=&quot;outline-text-3&quot; id=&quot;text-orga8281c2&quot;&gt;
&lt;ol class=&quot;org-ol&quot;&gt;
&lt;li&gt;Open thumbnail view with &lt;code&gt;C-t v&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Mark videos you want to process with &lt;code&gt;m&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Press &lt;code&gt;d&lt;/code&gt; to switch to dired&lt;/li&gt;
&lt;li&gt;Your marked videos are already selected in dired&lt;/li&gt;
&lt;li&gt;Use any dired command (&lt;code&gt;C&lt;/code&gt;, &lt;code&gt;R&lt;/code&gt;, &lt;code&gt;!&lt;/code&gt;, etc.) on marked files&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;outline-container-org3ca1acf&quot; class=&quot;outline-3&quot;&gt;
&lt;h3 id=&quot;org3ca1acf&quot;&gt;Quick Video Preview&lt;/h3&gt;
&lt;div class=&quot;outline-text-3&quot; id=&quot;text-org3ca1acf&quot;&gt;
&lt;ol class=&quot;org-ol&quot;&gt;
&lt;li&gt;In dired, position cursor on a video file&lt;/li&gt;
&lt;li&gt;&lt;code&gt;C-t v&lt;/code&gt; opens thumbnail view&lt;/li&gt;
&lt;li&gt;&lt;code&gt;RET&lt;/code&gt; to play the video&lt;/li&gt;
&lt;li&gt;&lt;code&gt;q&lt;/code&gt; to return to dired&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;outline-container-orge26ce02&quot; class=&quot;outline-3&quot;&gt;
&lt;h3 id=&quot;orge26ce02&quot;&gt;Finding Large Videos&lt;/h3&gt;
&lt;div class=&quot;outline-text-3&quot; id=&quot;text-orge26ce02&quot;&gt;
&lt;ol class=&quot;org-ol&quot;&gt;
&lt;li&gt;Open thumbnail view with &lt;code&gt;C-t v&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Press &lt;code&gt;.&lt;/code&gt; to open the transient menu&lt;/li&gt;
&lt;li&gt;Press &lt;code&gt;s&lt;/code&gt; then &lt;code&gt;s&lt;/code&gt; to sort by size&lt;/li&gt;
&lt;li&gt;Press &lt;code&gt;r&lt;/code&gt; to reverse order (largest first)&lt;/li&gt;
&lt;li&gt;Or use &lt;code&gt;/&lt;/code&gt; then &lt;code&gt;s&lt;/code&gt; to filter by size range&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;outline-container-orgb5e73fc&quot; class=&quot;outline-3&quot;&gt;
&lt;h3 id=&quot;orgb5e73fc&quot;&gt;Finding Long Videos&lt;/h3&gt;
&lt;div class=&quot;outline-text-3&quot; id=&quot;text-orgb5e73fc&quot;&gt;
&lt;ol class=&quot;org-ol&quot;&gt;
&lt;li&gt;Press &lt;code&gt;.&lt;/code&gt; to open the transient menu&lt;/li&gt;
&lt;li&gt;Press &lt;code&gt;s&lt;/code&gt; then &lt;code&gt;D&lt;/code&gt; to sort by duration&lt;/li&gt;
&lt;li&gt;Or use &lt;code&gt;/&lt;/code&gt; then &lt;code&gt;d&lt;/code&gt; to filter by duration range (e.g., &lt;code&gt;5:00&lt;/code&gt; to &lt;code&gt;30:00&lt;/code&gt;)&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;outline-container-org12fbc47&quot; class=&quot;outline-3&quot;&gt;
&lt;h3 id=&quot;org12fbc47&quot;&gt;Searching by Name&lt;/h3&gt;
&lt;div class=&quot;outline-text-3&quot; id=&quot;text-org12fbc47&quot;&gt;
&lt;ol class=&quot;org-ol&quot;&gt;
&lt;li&gt;Press &lt;code&gt;.&lt;/code&gt; to open the transient menu&lt;/li&gt;
&lt;li&gt;Press &lt;code&gt;/&lt;/code&gt; then &lt;code&gt;n&lt;/code&gt; and enter a regexp pattern&lt;/li&gt;
&lt;li&gt;Only matching videos are shown&lt;/li&gt;
&lt;li&gt;Press &lt;code&gt;c&lt;/code&gt; to clear the filter&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/section&gt;
&lt;section id=&quot;outline-container-org1c6f12d&quot; class=&quot;outline-2&quot;&gt;
&lt;h2 id=&quot;org1c6f12d&quot;&gt;Troubleshooting&lt;/h2&gt;
&lt;div class=&quot;outline-text-2&quot; id=&quot;text-org1c6f12d&quot;&gt;
&lt;/div&gt;
&lt;div id=&quot;outline-container-org297ae2f&quot; class=&quot;outline-3&quot;&gt;
&lt;h3 id=&quot;org297ae2f&quot;&gt;Thumbnails not generating&lt;/h3&gt;
&lt;div class=&quot;outline-text-3&quot; id=&quot;text-org297ae2f&quot;&gt;
&lt;ol class=&quot;org-ol&quot;&gt;
&lt;li&gt;Ensure ffmpeg is installed: &lt;code&gt;ffmpeg -version&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Check that ffmpeg is in your PATH or set &lt;code&gt;dired-video-thumbnail-ffmpeg-program&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Try regenerating with &lt;code&gt;g&lt;/code&gt; on a specific thumbnail&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;outline-container-org74ac1cc&quot; class=&quot;outline-3&quot;&gt;
&lt;h3 id=&quot;org74ac1cc&quot;&gt;Placeholder showing instead of thumbnail&lt;/h3&gt;
&lt;div class=&quot;outline-text-3&quot; id=&quot;text-org74ac1cc&quot;&gt;
&lt;p&gt;
Some videos may fail to generate thumbnails if:
&lt;/p&gt;
&lt;ul class=&quot;org-ul&quot;&gt;
&lt;li&gt;The video is corrupted&lt;/li&gt;
&lt;li&gt;The timestamp is beyond the video duration (try setting &lt;code&gt;dired-video-thumbnail-timestamp&lt;/code&gt; to &lt;code&gt;nil&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;ffmpeg doesn&apos;t support the codec&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;
Press &lt;code&gt;g&lt;/code&gt; on the thumbnail to retry generation.
&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;outline-container-orgc7f3a6b&quot; class=&quot;outline-3&quot;&gt;
&lt;h3 id=&quot;orgc7f3a6b&quot;&gt;Video info not showing in header line&lt;/h3&gt;
&lt;div class=&quot;outline-text-3&quot; id=&quot;text-orgc7f3a6b&quot;&gt;
&lt;p&gt;
Ensure &lt;code&gt;ffprobe&lt;/code&gt; is installed (it comes with ffmpeg). Set &lt;code&gt;dired-video-thumbnail-ffprobe-program&lt;/code&gt; if it&apos;s not in your PATH.
&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;outline-container-org29832c2&quot; class=&quot;outline-3&quot;&gt;
&lt;h3 id=&quot;org29832c2&quot;&gt;Marks not syncing with dired&lt;/h3&gt;
&lt;div class=&quot;outline-text-3&quot; id=&quot;text-org29832c2&quot;&gt;
&lt;p&gt;
Run &lt;code&gt;M-x dired-video-thumbnail-debug&lt;/code&gt; to check if the dired buffer is properly associated. The output should show a live dired buffer reference.
&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;outline-container-org4b9bf30&quot; class=&quot;outline-3&quot;&gt;
&lt;h3 id=&quot;org4b9bf30&quot;&gt;Performance with many videos&lt;/h3&gt;
&lt;div class=&quot;outline-text-3&quot; id=&quot;text-org4b9bf30&quot;&gt;
&lt;p&gt;
The package processes up to 4 videos concurrently by default. For directories with hundreds of videos, initial thumbnail generation may take some time, but Emacs remains responsive and thumbnails appear as they complete.
&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/section&gt;
&lt;section id=&quot;outline-container-org45e281c&quot; class=&quot;outline-2&quot;&gt;
&lt;h2 id=&quot;org45e281c&quot;&gt;Related Packages&lt;/h2&gt;
&lt;div class=&quot;outline-text-2&quot; id=&quot;text-org45e281c&quot;&gt;
&lt;ul class=&quot;org-ul&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://www.gnu.org/software/emacs/manual/html_node/emacs/Image_002dDired.html&quot;&gt;image-dired&lt;/a&gt; - Built-in image thumbnail browser for dired&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/alexluigit/dirvish&quot;&gt;dirvish&lt;/a&gt; - A modern file manager for Emacs with preview support&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/section&gt;
</content>
</entry>
<entry>
<title>Photos Evie 2025-12-31</title>
<link href="https://www.dyerdwelling.family/blog/20251231183000-blog--evie/"/>
<id>https://www.dyerdwelling.family/blog/20251231183000-blog--evie/</id>
<updated>2025-12-31T18:30:00+0000</updated>
<content type="html">&lt;p&gt;
Roaming around Kates parents house!
&lt;/p&gt;
</content>
</entry>
<entry>
<title>Setting Up Emacs for C# Development on Windows</title>
<link href="https://www.dyerdwelling.family/emacs/20251216082551-emacs--setting-up-emacs-for-c#-development-on-windows/"/>
<id>https://www.dyerdwelling.family/emacs/20251216082551-emacs--setting-up-emacs-for-c#-development-on-windows/</id>
<updated>2025-12-16T08:25:00+0000</updated>
<content type="html">&lt;section id=&quot;outline-container-orgbf88c50&quot; class=&quot;outline-2&quot;&gt;
&lt;h2 id=&quot;orgbf88c50&quot;&gt;Introduction&lt;/h2&gt;
&lt;div class=&quot;outline-text-2&quot; id=&quot;text-orgbf88c50&quot;&gt;
&lt;p&gt;
I have been developing C# with .NET 9.0 for the last year on Windows and I thought it was probably time to write down my current setup, and maybe someone might even find this useful!
&lt;/p&gt;

&lt;p&gt;
So, this guide documents my setup for running Emacs 30.1 on Windows with full C# development support, including LSP, debugging (through DAPE), and all the ancillary tools you&apos;d expect from a modern development environment. The setup is designed to be portable and self-contained, which is particularly useful in air-gapped or restricted environments.
&lt;/p&gt;

&lt;p&gt;
A version of this can be found at &lt;a href=&quot;https://github.com/captainflasmr/Emacs-on-windows&quot;&gt;https://github.com/captainflasmr/Emacs-on-windows&lt;/a&gt; which will be a living continually updated version!
&lt;/p&gt;


&lt;div id=&quot;orgf0d6a41&quot; class=&quot;figure&quot;&gt;
&lt;p&gt;&lt;img class=&quot;img-fluid rounded&quot; src=&quot;/static/emacs/20251216082551-emacs--Setting-Up-Emacs-for-Csharp-Development-on-Windows.jpg&quot; alt=&quot;20251216082551-emacs--Setting-Up-Emacs-for-Csharp-Development-on-Windows.jpg&quot; width=&quot;100%&quot; /&gt;
&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/section&gt;
&lt;section id=&quot;outline-container-orgfd4a9fa&quot; class=&quot;outline-2&quot;&gt;
&lt;h2 id=&quot;orgfd4a9fa&quot;&gt;Prerequisites&lt;/h2&gt;
&lt;div class=&quot;outline-text-2&quot; id=&quot;text-orgfd4a9fa&quot;&gt;
&lt;p&gt;
Before we begin, you&apos;ll need:
&lt;/p&gt;

&lt;ul class=&quot;org-ul&quot;&gt;
&lt;li&gt;&lt;b&gt;Windows 10 or 11&lt;/b&gt; (64-bit)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;.NET 9.0 SDK&lt;/b&gt; - Required for csharp-ls and building .NET projects&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Visual Studio 2022&lt;/b&gt; (optional) - Useful for MSBuild and if you need the full IDE occasionally&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Administrator access&lt;/b&gt; - For initial setup only&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;
You can verify your .NET installation by opening a command prompt and running:
&lt;/p&gt;

&lt;div class=&quot;org-src-container&quot;&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;dotnet --version
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;
If you see version 9.0.x or later, you&apos;re ready to proceed.
&lt;/p&gt;
&lt;/div&gt;
&lt;/section&gt;
&lt;section id=&quot;outline-container-orge2c3fe7&quot; class=&quot;outline-2&quot;&gt;
&lt;h2 id=&quot;orge2c3fe7&quot;&gt;The Big Picture&lt;/h2&gt;
&lt;div class=&quot;outline-text-2&quot; id=&quot;text-orge2c3fe7&quot;&gt;
&lt;p&gt;
Here&apos;s what we&apos;re building:
&lt;/p&gt;

&lt;pre class=&quot;example&quot; id=&quot;orgda89c8a&quot;&gt;
D:\source\emacs-30.1\
├── bin\
│   ├── emacs.exe, runemacs.exe, etc.
│   ├── PortableGit\          # Git for version control
│   ├── Apache-Subversion\    # SVN (if needed)
│   ├── csharp-ls\            # C# Language Server
│   ├── netcoredbg\           # .NET debugger
│   ├── omnisharp-win-x64\    # Alternative C# LSP
│   ├── hunspell\             # Spell checking
│   ├── find\                 # ripgrep for fast searching
│   ├── ffmpeg-7.1.1-.../     # Video processing
│   └── ImageMagick-.../      # Image processing
└── share\
    └── emacs\...
&lt;/pre&gt;

&lt;p&gt;
The key insight here is keeping everything within the Emacs installation directory. This makes the whole setup portable-you can copy it to another machine or keep it on a USB drive.
&lt;/p&gt;
&lt;/div&gt;
&lt;/section&gt;
&lt;section id=&quot;outline-container-orgc8e23e3&quot; class=&quot;outline-2&quot;&gt;
&lt;h2 id=&quot;orgc8e23e3&quot;&gt;Step 1: Installing Emacs&lt;/h2&gt;
&lt;div class=&quot;outline-text-2&quot; id=&quot;text-orgc8e23e3&quot;&gt;
&lt;p&gt;
Download Emacs 30.1 from the &lt;a href=&quot;https://www.gnu.org/software/emacs/download.html&quot;&gt;GNU Emacs download page&lt;/a&gt;. For Windows, grab the installer or the zip archive.
&lt;/p&gt;

&lt;p&gt;
I install to an external drive &lt;code&gt;D:\source\emacs-30.1&lt;/code&gt; rather than Program Files-it avoids permission issues and keeps everything in one place.
&lt;/p&gt;

&lt;p&gt;
Test your installation by running &lt;code&gt;bin\runemacs.exe&lt;/code&gt;. You should see a fresh Emacs frame.
&lt;/p&gt;
&lt;/div&gt;
&lt;/section&gt;
&lt;section id=&quot;outline-container-org51cd2f0&quot; class=&quot;outline-2&quot;&gt;
&lt;h2 id=&quot;org51cd2f0&quot;&gt;Step 2: Setting Up csharp-ls (The C# Language Server)&lt;/h2&gt;
&lt;div class=&quot;outline-text-2&quot; id=&quot;text-org51cd2f0&quot;&gt;
&lt;p&gt;
This is the heart of the C# development experience. &lt;code&gt;csharp-ls&lt;/code&gt; provides code completion, go-to-definition, find references, diagnostics, and more through the Language Server Protocol (LSP).
&lt;/p&gt;
&lt;/div&gt;
&lt;div id=&quot;outline-container-orgd705688&quot; class=&quot;outline-3&quot;&gt;
&lt;h3 id=&quot;orgd705688&quot;&gt;Option A: Installing via dotnet tool (Recommended for Internet Access)&lt;/h3&gt;
&lt;div class=&quot;outline-text-3&quot; id=&quot;text-orgd705688&quot;&gt;
&lt;p&gt;
If you have internet access, the easiest way to install csharp-ls is as a .NET global tool:
&lt;/p&gt;

&lt;div class=&quot;org-src-container&quot;&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;# Install the latest version globally
dotnet tool install --global csharp-ls

# Or install a specific version
dotnet tool install --global csharp-ls --version 0.20.0

# Verify installation
csharp-ls --version
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;
By default, global tools are installed to:
&lt;/p&gt;
&lt;ul class=&quot;org-ul&quot;&gt;
&lt;li&gt;Windows: &lt;code&gt;%USERPROFILE%\.dotnet\tools&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;
The executable will be &lt;code&gt;csharp-ls.exe&lt;/code&gt; and can be called directly once the tools directory is in your PATH.
&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;outline-container-org9d4d6a7&quot; class=&quot;outline-3&quot;&gt;
&lt;h3 id=&quot;org9d4d6a7&quot;&gt;Option B: Offline Installation via NuGet Package&lt;/h3&gt;
&lt;div class=&quot;outline-text-3&quot; id=&quot;text-org9d4d6a7&quot;&gt;
&lt;p&gt;
For air-gapped environments, you can download the NuGet package and extract it manually:
&lt;/p&gt;

&lt;ol class=&quot;org-ol&quot;&gt;
&lt;li&gt;&lt;p&gt;
On a machine with internet, download the package:
&lt;/p&gt;
&lt;div class=&quot;org-src-container&quot;&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;# Download the nupkg file
nuget install csharp-ls -Version 0.20.0 -OutputDirectory ./packages

# Or download directly from NuGet Gallery:
# https://www.nuget.org/packages/csharp-ls/
# Click &quot;Download package&quot; on the right side
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;
The &lt;code&gt;.nupkg&lt;/code&gt; file is just a ZIP archive. Extract it:
&lt;/p&gt;
&lt;div class=&quot;org-src-container&quot;&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;# Rename to .zip and extract, or use 7-Zip
# The DLLs are in tools/net9.0/any/
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;
Copy the &lt;code&gt;tools/net9.0/any/&lt;/code&gt; directory to your Emacs bin:
&lt;/p&gt;
&lt;div class=&quot;org-src-container&quot;&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;xcopy /E packages\csharp-ls.0.20.0\tools\net9.0\any D:\source\emacs-30.1\bin\csharp-ls\
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;/li&gt;

&lt;li&gt;The language server is now at:
&lt;code&gt;D:\source\emacs-30.1\bin\csharp-ls\CSharpLanguageServer.dll&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;outline-container-orga63bd95&quot; class=&quot;outline-3&quot;&gt;
&lt;h3 id=&quot;orga63bd95&quot;&gt;Configuring Eglot for csharp-ls&lt;/h3&gt;
&lt;div class=&quot;outline-text-3&quot; id=&quot;text-orga63bd95&quot;&gt;
&lt;p&gt;
In your &lt;code&gt;init.el&lt;/code&gt;, configure Eglot to use csharp-ls:
&lt;/p&gt;

&lt;div class=&quot;org-src-container&quot;&gt;
&lt;pre&gt;&lt;code class=&quot;language-emacs-lisp&quot;&gt;(require &apos;eglot)

;; Option A: If installed as a global tool
(setq eglot-server-programs
      &apos;((csharp-mode . (&quot;csharp-ls&quot;))))

;; Option B: If running from extracted DLL
(setq eglot-server-programs
      &apos;((csharp-mode . (&quot;dotnet&quot; 
                        &quot;D:/source/emacs-30.1/bin/csharp-ls/CSharpLanguageServer.dll&quot;))))
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;
I also have the following commented out if there are some eglot functions that causes slowdowns or I just think I don&apos;t need:
&lt;/p&gt;

&lt;div class=&quot;org-src-container&quot;&gt;
&lt;pre&gt;&lt;code class=&quot;language-elisp&quot;&gt;(setq eglot-ignored-server-capabilities
      &apos;(
        ;; :hoverProvider                    ; Documentation on hover
        ;; :completionProvider               ; Code completion
        ;; :signatureHelpProvider            ; Function signature help
        ;; :definitionProvider               ; Go to definition
        ;; :typeDefinitionProvider           ; Go to type definition
        ;; :implementationProvider           ; Go to implementation
        ;; :declarationProvider              ; Go to declaration
        ;; :referencesProvider               ; Find references
        ;; :documentHighlightProvider        ; Highlight symbols automatically
        ;; :documentSymbolProvider           ; List symbols in buffer
        ;; :workspaceSymbolProvider          ; List symbols in workspace
        ;; :codeActionProvider               ; Execute code actions
        ;; :codeLensProvider                 ; Code lens
        ;; :documentFormattingProvider       ; Format buffer
        ;; :documentRangeFormattingProvider  ; Format portion of buffer
        ;; :documentOnTypeFormattingProvider ; On-type formatting
        ;; :renameProvider                   ; Rename symbol
        ;; :documentLinkProvider             ; Highlight links in document
        ;; :colorProvider                    ; Decorate color references
        ;; :foldingRangeProvider             ; Fold regions of buffer
        ;; :executeCommandProvider           ; Execute custom commands
        ;; :inlayHintProvider                ; Inlay hints
        ))
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/section&gt;
&lt;section id=&quot;outline-container-orgcc1233e&quot; class=&quot;outline-2&quot;&gt;
&lt;h2 id=&quot;orgcc1233e&quot;&gt;Step 3: Setting Up the Debugger (netcoredbg)&lt;/h2&gt;
&lt;div class=&quot;outline-text-2&quot; id=&quot;text-orgcc1233e&quot;&gt;
&lt;p&gt;
For debugging .NET applications, we&apos;ll use &lt;code&gt;netcoredbg&lt;/code&gt;, which implements the Debug Adapter Protocol (DAP).
&lt;/p&gt;
&lt;/div&gt;
&lt;div id=&quot;outline-container-org851afad&quot; class=&quot;outline-3&quot;&gt;
&lt;h3 id=&quot;org851afad&quot;&gt;Installing netcoredbg&lt;/h3&gt;
&lt;div class=&quot;outline-text-3&quot; id=&quot;text-org851afad&quot;&gt;
&lt;ol class=&quot;org-ol&quot;&gt;
&lt;li&gt;Download from &lt;a href=&quot;https://github.com/Samsung/netcoredbg/releases&quot;&gt;Samsung&apos;s GitHub releases&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Extract to &lt;code&gt;D:\source\emacs-30.1\bin\netcoredbg\&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Verify: &lt;code&gt;netcoredbg.exe --version&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;outline-container-org127aa67&quot; class=&quot;outline-3&quot;&gt;
&lt;h3 id=&quot;org127aa67&quot;&gt;Configuring dape for Debugging&lt;/h3&gt;
&lt;div class=&quot;outline-text-3&quot; id=&quot;text-org127aa67&quot;&gt;
&lt;p&gt;
&lt;code&gt;dape&lt;/code&gt; is an excellent DAP client for Emacs. Here&apos;s my configuration:
&lt;/p&gt;

&lt;div class=&quot;org-src-container&quot;&gt;
&lt;pre&gt;&lt;code class=&quot;language-emacs-lisp&quot;&gt;(use-package dape
  :load-path &quot;z:/SharedVM/source/dape-master&quot;
  :init
  ;; Set key prefix BEFORE loading dape
  (setq dape-key-prefix (kbd &quot;C-c d&quot;))
  :config
  ;; Define common configuration
  (defvar project-netcoredbg-path &quot;d:/source/emacs-30.1/bin/netcoredbg/netcoredbg.exe&quot;
    &quot;Path to netcoredbg executable.&quot;)
  (defvar project-netcoredbg-log &quot;d:/source/emacs-30.1/bin/netcoredbg/netcoredbg.log&quot;
    &quot;Path to netcoredbg log file.&quot;)
  (defvar project-project-root &quot;d:/source/PROJECT&quot;
    &quot;Root directory of PROJECT project.&quot;)
  (defvar project-build-config &quot;Debug&quot;
    &quot;Build configuration (Debug or Release).&quot;)
  (defvar project-target-arch &quot;x64&quot;
    &quot;Target architecture (x64, x86, or AnyCPU).&quot;)

  ;; Helper function to create component configs
  (defun project-dape-config (component-name dll-name &amp;amp;optional stop-at-entry)
    &quot;Create a dape configuration for a component.
COMPONENT-NAME is the component directory name
DLL-NAME is the DLL filename without extension.
STOP-AT-ENTRY if non-nil, stops at program entry point.&quot;
    (let* ((component-dir (format &quot;%s/%s&quot; project-project-root component-name))
           (bin-path (format &quot;%s/bin/%s/%s/net9.0&quot;
                             component-dir
                             project-target-arch
                             project-build-config))
           (dll-path (format &quot;%s/%s.dll&quot; bin-path dll-name))
           (config-name (intern (format &quot;netcoredbg-launch-%s&quot; 
                                        (downcase component-name)))))
      `(,config-name
        modes (csharp-mode csharp-ts-mode)
        command ,project-netcoredbg-path
        command-args (,(format &quot;--interpreter=vscode&quot;)
                      ,(format &quot;--engineLogging=%s&quot; project-netcoredbg-log))
        normalize-path-separator &apos;windows
        :type &quot;coreclr&quot;
        :request &quot;launch&quot;
        :program ,dll-path
        :cwd ,component-dir
        :console &quot;externalTerminal&quot;
        :internalConsoleOptions &quot;neverOpen&quot;
        :suppressJITOptimizations t
        :requireExactSource nil
        :justMyCode t
        :stopAtEntry ,(if stop-at-entry t :json-false))))

  ;; Register all component configurations
  (dolist (config (list
                   (project-dape-config &quot;DM&quot; &quot;DM.MSS&quot; t)
                   (project-dape-config &quot;Demo&quot; &quot;Demo.MSS&quot; t)
                   (project-dape-config &quot;Test_001&quot; &quot;Test&quot; t)))
    (add-to-list &apos;dape-configs config))
  
  ;; Set buffer arrangement and other options
  (setq dape-buffer-window-arrangement &apos;gud)
  (setq dape-debug t)
  (setq dape-repl-echo-shell-output t))
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;
Now you can start debugging with &lt;code&gt;M-x dape&lt;/code&gt; and selecting your configuration.
&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/section&gt;
&lt;section id=&quot;outline-container-org4893365&quot; class=&quot;outline-2&quot;&gt;
&lt;h2 id=&quot;org4893365&quot;&gt;Step 4: Installing Supporting Tools&lt;/h2&gt;
&lt;div class=&quot;outline-text-2&quot; id=&quot;text-org4893365&quot;&gt;
&lt;/div&gt;
&lt;div id=&quot;outline-container-orgeaf19e2&quot; class=&quot;outline-3&quot;&gt;
&lt;h3 id=&quot;orgeaf19e2&quot;&gt;Portable Git&lt;/h3&gt;
&lt;div class=&quot;outline-text-3&quot; id=&quot;text-orgeaf19e2&quot;&gt;
&lt;ol class=&quot;org-ol&quot;&gt;
&lt;li&gt;Download &lt;code&gt;PortableGit-2.50.0-64-bit.7z.exe&lt;/code&gt; from &lt;a href=&quot;https://git-scm.com/download/win&quot;&gt;git-scm.com&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Run and extract to &lt;code&gt;D:\source\emacs-30.1\bin\PortableGit\&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;
This gives you &lt;code&gt;git.exe&lt;/code&gt;, &lt;code&gt;bash.exe&lt;/code&gt;, and a whole Unix-like environment.
&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;outline-container-orgcd7b2c1&quot; class=&quot;outline-3&quot;&gt;
&lt;h3 id=&quot;orgcd7b2c1&quot;&gt;ripgrep (Fast Searching)&lt;/h3&gt;
&lt;div class=&quot;outline-text-3&quot; id=&quot;text-orgcd7b2c1&quot;&gt;
&lt;ol class=&quot;org-ol&quot;&gt;
&lt;li&gt;Download from &lt;a href=&quot;https://github.com/BurntSushi/ripgrep/releases&quot;&gt;ripgrep releases&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Extract &lt;code&gt;rg.exe&lt;/code&gt; to &lt;code&gt;D:\source\emacs-30.1\bin\find\&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;
ripgrep is dramatically faster than grep for searching codebases.
&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;outline-container-orgce751d8&quot; class=&quot;outline-3&quot;&gt;
&lt;h3 id=&quot;orgce751d8&quot;&gt;Hunspell (Spell Checking)&lt;/h3&gt;
&lt;div class=&quot;outline-text-3&quot; id=&quot;text-orgce751d8&quot;&gt;
&lt;ol class=&quot;org-ol&quot;&gt;
&lt;li&gt;Download &lt;code&gt;hunspell-1.3.2-3-w32-bin.zip&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Extract to &lt;code&gt;D:\source\emacs-30.1\bin\hunspell\&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Download dictionary files (&lt;code&gt;en_GB.dic&lt;/code&gt; and &lt;code&gt;en_GB.aff&lt;/code&gt;) and place in &lt;code&gt;hunspell\share\hunspell\&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;outline-container-orgd6c8a23&quot; class=&quot;outline-3&quot;&gt;
&lt;h3 id=&quot;orgd6c8a23&quot;&gt;ImageMagick (Image Processing)&lt;/h3&gt;
&lt;div class=&quot;outline-text-3&quot; id=&quot;text-orgd6c8a23&quot;&gt;
&lt;ol class=&quot;org-ol&quot;&gt;
&lt;li&gt;Download the portable Q16 x64 version from &lt;a href=&quot;https://imagemagick.org/script/download.php&quot;&gt;imagemagick.org&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Extract to &lt;code&gt;D:\source\emacs-30.1\bin\ImageMagick-7.1.2-9-portable-Q16-x64\&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;
This enables &lt;code&gt;image-dired&lt;/code&gt; thumbnail generation.
&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;outline-container-org1b0a9b8&quot; class=&quot;outline-3&quot;&gt;
&lt;h3 id=&quot;org1b0a9b8&quot;&gt;FFmpeg (Video Processing)&lt;/h3&gt;
&lt;div class=&quot;outline-text-3&quot; id=&quot;text-org1b0a9b8&quot;&gt;
&lt;ol class=&quot;org-ol&quot;&gt;
&lt;li&gt;Download from &lt;a href=&quot;https://ffmpeg.org/download.html&quot;&gt;ffmpeg.org&lt;/a&gt; (essentials build is fine)&lt;/li&gt;
&lt;li&gt;Extract to &lt;code&gt;D:\source\emacs-30.1\bin\ffmpeg-7.1.1-essentials_build\&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;
Useful for video thumbnails in dired and media processing.
&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/section&gt;
&lt;section id=&quot;outline-container-org4314db4&quot; class=&quot;outline-2&quot;&gt;
&lt;h2 id=&quot;org4314db4&quot;&gt;Step 5: Configuring the PATH&lt;/h2&gt;
&lt;div class=&quot;outline-text-2&quot; id=&quot;text-org4314db4&quot;&gt;
&lt;p&gt;
This is crucial-Emacs needs to find all these tools. Here&apos;s the PATH configuration from my &lt;code&gt;init.el&lt;/code&gt;:
&lt;/p&gt;

&lt;div class=&quot;org-src-container&quot;&gt;
&lt;pre&gt;&lt;code class=&quot;language-emacs-lisp&quot;&gt;(when (eq system-type &apos;windows-nt)
  (let* ((emacs-bin &quot;d:/source/emacs-30.1/bin&quot;)
         (xPaths
          `(,emacs-bin
            ,(concat emacs-bin &quot;/PortableGit/bin&quot;)
            ,(concat emacs-bin &quot;/PortableGit/usr/bin&quot;)
            ,(concat emacs-bin &quot;/hunspell/bin&quot;)
            ,(concat emacs-bin &quot;/find&quot;)
            ,(concat emacs-bin &quot;/netcoredbg&quot;)
            ,(concat emacs-bin &quot;/csharp-ls/tools/net9.0/any&quot;)
            ,(concat emacs-bin &quot;/ffmpeg-7.1.1-essentials_build/bin&quot;)
            ,(concat emacs-bin &quot;/ImageMagick-7.1.2-9-portable-Q16-x64&quot;)))
         (winPaths (getenv &quot;PATH&quot;)))
    (setenv &quot;PATH&quot; (concat (mapconcat &apos;identity xPaths &quot;;&quot;) &quot;;&quot; winPaths))
    (setq exec-path (append xPaths (parse-colon-path winPaths)))))
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/section&gt;
&lt;section id=&quot;outline-container-org6d59a7a&quot; class=&quot;outline-2&quot;&gt;
&lt;h2 id=&quot;org6d59a7a&quot;&gt;Step 6: Installing Emacs Packages&lt;/h2&gt;
&lt;div class=&quot;outline-text-2&quot; id=&quot;text-org6d59a7a&quot;&gt;
&lt;p&gt;
Extract these to a shared location or download from MELPA
&lt;/p&gt;

&lt;table class=&quot;table table-striped&quot; border=&quot;2&quot; cellspacing=&quot;0&quot; cellpadding=&quot;6&quot; rules=&quot;groups&quot; frame=&quot;hsides&quot;&gt;


&lt;colgroup&gt;
&lt;col  class=&quot;org-left&quot; /&gt;

&lt;col  class=&quot;org-left&quot; /&gt;
&lt;/colgroup&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th scope=&quot;col&quot; class=&quot;org-left&quot;&gt;Package&lt;/th&gt;
&lt;th scope=&quot;col&quot; class=&quot;org-left&quot;&gt;Purpose&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td class=&quot;org-left&quot;&gt;corfu&lt;/td&gt;
&lt;td class=&quot;org-left&quot;&gt;Modern completion UI&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td class=&quot;org-left&quot;&gt;dape&lt;/td&gt;
&lt;td class=&quot;org-left&quot;&gt;Debug Adapter Protocol client&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td class=&quot;org-left&quot;&gt;highlight-indent-guides&lt;/td&gt;
&lt;td class=&quot;org-left&quot;&gt;Visual indentation guides&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td class=&quot;org-left&quot;&gt;ztree&lt;/td&gt;
&lt;td class=&quot;org-left&quot;&gt;Directory tree comparison&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td class=&quot;org-left&quot;&gt;web-mode&lt;/td&gt;
&lt;td class=&quot;org-left&quot;&gt;Web template editing&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;
Example package configuration:
&lt;/p&gt;

&lt;div class=&quot;org-src-container&quot;&gt;
&lt;pre&gt;&lt;code class=&quot;language-emacs-lisp&quot;&gt;(use-package corfu
  :load-path &quot;z:/SharedVM/source/corfu-main&quot;
  :custom
  (corfu-auto nil)         ; Manual completion trigger
  (corfu-cycle t)          ; Cycle through candidates
  (corfu-preselect &apos;first))

(use-package ztree
  :load-path &quot;z:/SharedVM/source/ztree&quot;
  :config
  (setq ztree-diff-filter-list
        &apos;(&quot;build&quot; &quot;\\.dll&quot; &quot;\\.git&quot; &quot;bin&quot; &quot;obj&quot;))
  (global-set-key (kbd &quot;C-c z d&quot;) &apos;ztree-diff))

(use-package web-mode
  :load-path &quot;z:/SharedVM/source/web-mode-master&quot;
  :mode &quot;\\.cshtml?\\&apos;&quot;
  :hook (html-mode . web-mode)
  :bind (:map web-mode-map (&quot;M-;&quot; . nil)))
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;
Note that I turn off autocomplete for corfu and complete using &lt;code&gt;complete-symbol&lt;/code&gt; manually, otherwise the LSP is constantly accessed with slowdown.
&lt;/p&gt;

&lt;p&gt;
I often use &lt;code&gt;Meld&lt;/code&gt; but am currently am looking to adapt &lt;code&gt;ztree&lt;/code&gt; to perform better for directory comparisons.
&lt;/p&gt;

&lt;p&gt;
Web-mode is the best package I have found for html type file navigation and folding, very useful when developing Razor pages for example.
&lt;/p&gt;
&lt;/div&gt;
&lt;/section&gt;
&lt;section id=&quot;outline-container-orge2c07f7&quot; class=&quot;outline-2&quot;&gt;
&lt;h2 id=&quot;orge2c07f7&quot;&gt;Step 7: auto open file modes&lt;/h2&gt;
&lt;div class=&quot;outline-text-2&quot; id=&quot;text-orge2c07f7&quot;&gt;
&lt;p&gt;
Of course running and building in windows means in Emacs probably having to open .csproj files from time to time, well &lt;code&gt;nxml-mode&lt;/code&gt; comes in useful for this:
&lt;/p&gt;

&lt;div class=&quot;org-src-container&quot;&gt;
&lt;pre&gt;&lt;code class=&quot;language-elisp&quot;&gt;(add-to-list &apos;auto-mode-alist &apos;(&quot;\\.csproj\\&apos;&quot; . nxml-mode))
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/section&gt;
&lt;section id=&quot;outline-container-org688f96a&quot; class=&quot;outline-2&quot;&gt;
&lt;h2 id=&quot;org688f96a&quot;&gt;Step 8: build script&lt;/h2&gt;
&lt;div class=&quot;outline-text-2&quot; id=&quot;text-org688f96a&quot;&gt;
&lt;p&gt;
Here is my general build script, leveraging msbuild and running generally from &lt;code&gt;eshell&lt;/code&gt;
&lt;/p&gt;

&lt;p&gt;
New projects are added to :
&lt;/p&gt;

&lt;div class=&quot;org-src-container&quot;&gt;
&lt;pre&gt;&lt;code class=&quot;language-bat&quot;&gt;set PROJECTS
set PROJECT_NAMES
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;div class=&quot;org-src-container&quot;&gt;
&lt;pre&gt;&lt;code class=&quot;language-bat&quot;&gt;@echo off
setlocal

REM =================================================================
REM Build Management Script
REM =================================================================
REM Usage: build-selected.bat [action] [verbosity] [configuration] [platform]
REM   action: build, clean, restore, rebuild (default: build)
REM   verbosity: quiet, minimal, normal, detailed, diagnostic (default: minimal)
REM   configuration: Debug, Release (default: Debug)
REM   platform: x64, x86, &quot;Any CPU&quot; (default: x64)
REM =================================================================

REM Set defaults
set ACTION=%1
set VERBOSITY=%2
set CONFIGURATION=%3
set PLATFORM=%4

if &quot;%ACTION%&quot;==&quot;&quot; set ACTION=build
if &quot;%VERBOSITY%&quot;==&quot;&quot; set VERBOSITY=minimal
if &quot;%CONFIGURATION%&quot;==&quot;&quot; set CONFIGURATION=Debug
if &quot;%PLATFORM%&quot;==&quot;&quot; set PLATFORM=x64

echo Build Script - Action=%ACTION%, Verbosity=%VERBOSITY%, Config=%CONFIGURATION%, Platform=%PLATFORM%
echo.

REM Common build parameters
set BUILD_PARAMS=/p:Configuration=%CONFIGURATION% /p:Platform=&quot;%PLATFORM%&quot; /verbosity:%VERBOSITY%

REM Set MSBuild target based on action
if /I &quot;%ACTION%&quot;==&quot;build&quot; set TARGET=Build
if /I &quot;%ACTION%&quot;==&quot;clean&quot; set TARGET=Clean
if /I &quot;%ACTION%&quot;==&quot;restore&quot; set TARGET=Restore
if /I &quot;%ACTION%&quot;==&quot;rebuild&quot; set TARGET=Rebuild

if &quot;%TARGET%&quot;==&quot;&quot; (
    echo Error: Invalid action &apos;%ACTION%&apos;. Use: build, clean, restore, or rebuild
    exit /b 1
)

echo Executing %ACTION% action...
echo.

set PROJECTS[1]=Demo/Demo.csproj
set PROJECT_NAMES[1]=Demo

set PROJECTS[2]=Test/Test.csproj
set PROJECT_NAMES[2]=Test

set PROJECT_COUNT=2

REM Special handling for rebuild (clean then build)
if /I &quot;%ACTION%&quot;==&quot;rebuild&quot; (
    echo === CLEANING PHASE ===
    for /L %%i in (1,1,%PROJECT_COUNT%) do (
        call :process_project %%i Clean
        if errorlevel 1 goto :error
    )
    echo.
    echo === BUILDING PHASE ===
    set TARGET=Build
)

REM Process all active projects
for /L %%i in (1,1,%PROJECT_COUNT%) do (
    call :process_project %%i %TARGET%
    if errorlevel 1 goto :error
)

echo.
if /I &quot;%ACTION%&quot;==&quot;clean&quot; (
    echo All selected components cleaned successfully!
) else if /I &quot;%ACTION%&quot;==&quot;restore&quot; (
    echo All selected components restored successfully!
) else if /I &quot;%ACTION%&quot;==&quot;rebuild&quot; (
    echo All selected components rebuilt successfully!
) else (
    echo All selected components built successfully!
)
goto :end

:process_project
    setlocal EnableDelayedExpansion
    set idx=%1
    set target=%2
    
    REM Get project path and name using the index
    for /f &quot;tokens=2 delims==&quot; %%a in (&apos;set PROJECTS[%idx%] 2^&amp;gt;nul&apos;) do set PROJECT_PATH=%%a
    for /f &quot;tokens=2 delims==&quot; %%a in (&apos;set PROJECT_NAMES[%idx%] 2^&amp;gt;nul&apos;) do set PROJECT_NAME=%%a
    
    if &quot;!PROJECT_PATH!&quot;==&quot;&quot; goto :eof

    echo ----------------------------------------
    echo [%idx%/%PROJECT_COUNT%] %target%ing !PROJECT_NAME!...

    REM Build the project normally
    msbuild &quot;!PROJECT_PATH!&quot; /t:%target% %BUILD_PARAMS%
    if errorlevel 1 exit /b 1
    
goto :eof

:error
echo.
echo %ACTION% failed! Check the output above for errors.
exit /b 1

:end
echo %ACTION% completed at %time%
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;
to launch applications of course, if it is a pure DOTNET project you would use &lt;code&gt;dotnet run&lt;/code&gt;
&lt;/p&gt;
&lt;/div&gt;
&lt;/section&gt;
&lt;section id=&quot;outline-container-orgc142245&quot; class=&quot;outline-2&quot;&gt;
&lt;h2 id=&quot;orgc142245&quot;&gt;Troubleshooting&lt;/h2&gt;
&lt;div class=&quot;outline-text-2&quot; id=&quot;text-orgc142245&quot;&gt;
&lt;/div&gt;
&lt;div id=&quot;outline-container-orgbfe2580&quot; class=&quot;outline-3&quot;&gt;
&lt;h3 id=&quot;orgbfe2580&quot;&gt;&quot;Cannot find csharp-ls&quot; or Eglot won&apos;t start&lt;/h3&gt;
&lt;div class=&quot;outline-text-3&quot; id=&quot;text-orgbfe2580&quot;&gt;
&lt;ol class=&quot;org-ol&quot;&gt;
&lt;li&gt;Check the PATH: &lt;code&gt;M-x getenv RET PATH&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Verify the DLL exists at the configured location&lt;/li&gt;
&lt;li&gt;Try running manually: &lt;code&gt;dotnet path\to\CSharpLanguageServer.dll --version&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Check &lt;code&gt;*eglot-events*&lt;/code&gt; buffer for detailed error messages&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;outline-container-orgbc5b903&quot; class=&quot;outline-3&quot;&gt;
&lt;h3 id=&quot;orgbc5b903&quot;&gt;LSP is slow or uses too much memory&lt;/h3&gt;
&lt;div class=&quot;outline-text-3&quot; id=&quot;text-orgbc5b903&quot;&gt;
&lt;p&gt;
Try adding to your configuration:
&lt;/p&gt;

&lt;div class=&quot;org-src-container&quot;&gt;
&lt;pre&gt;&lt;code class=&quot;language-emacs-lisp&quot;&gt;;; Increase garbage collection threshold during LSP operations
(setq gc-cons-threshold 100000000)  ; 100MB
(setq read-process-output-max (* 1024 1024))  ; 1MB
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;outline-container-orge304aee&quot; class=&quot;outline-3&quot;&gt;
&lt;h3 id=&quot;orge304aee&quot;&gt;Debugger won&apos;t attach&lt;/h3&gt;
&lt;div class=&quot;outline-text-3&quot; id=&quot;text-orge304aee&quot;&gt;
&lt;ol class=&quot;org-ol&quot;&gt;
&lt;li&gt;Ensure the project is built in Debug configuration&lt;/li&gt;
&lt;li&gt;Check the DLL path matches your build output&lt;/li&gt;
&lt;li&gt;Look at &lt;code&gt;*dape-repl*&lt;/code&gt; for error messages&lt;/li&gt;
&lt;li&gt;Verify netcoredbg runs: &lt;code&gt;netcoredbg.exe --version&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/section&gt;
&lt;section id=&quot;outline-container-orgcc0ce73&quot; class=&quot;outline-2&quot;&gt;
&lt;h2 id=&quot;orgcc0ce73&quot;&gt;Conclusion&lt;/h2&gt;
&lt;div class=&quot;outline-text-2&quot; id=&quot;text-orgcc0ce73&quot;&gt;
&lt;p&gt;
This setup has served me well for my windows .NET 9.0 projects and various other C# work. The key benefits:
&lt;/p&gt;

&lt;ul class=&quot;org-ul&quot;&gt;
&lt;li&gt;&lt;b&gt;Portability&lt;/b&gt;: Everything lives in one directory&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Speed&lt;/b&gt;: csharp-ls is notably faster than OmniSharp&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Flexibility&lt;/b&gt;: Easy to customise and extend&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Offline-capable&lt;/b&gt;: Works in air-gapped environments&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;
The initial setup takes some effort, but once it&apos;s done, you have a powerful, consistent development environment that travels with you.
&lt;/p&gt;
&lt;/div&gt;
&lt;/section&gt;
</content>
</entry>
</feed>
