<feed xmlns='http://www.w3.org/2005/Atom'>
<title>dotemacs/modules/dirvish-config.el, branch load-graph-classify-start</title>
<subtitle>My Emacs configuration
</subtitle>
<id>https://git.cjennings.net/dotemacs/atom?h=load-graph-classify-start</id>
<link rel='self' href='https://git.cjennings.net/dotemacs/atom?h=load-graph-classify-start'/>
<link rel='alternate' type='text/html' href='https://git.cjennings.net/dotemacs/'/>
<updated>2026-05-23T08:31:04+00:00</updated>
<entry>
<title>fix(dirvish): guard nil file and reject path-traversal playlist names</title>
<updated>2026-05-23T08:31:04+00:00</updated>
<author>
<name>Craig Jennings</name>
<email>c@cjennings.net</email>
</author>
<published>2026-05-23T08:31:04+00:00</published>
<link rel='alternate' type='text/html' href='https://git.cjennings.net/dotemacs/commit/?id=8fc6432d44e41787fb7f69ad792f50cc906393d5'/>
<id>urn:sha1:8fc6432d44e41787fb7f69ad792f50cc906393d5</id>
<content type='text'>
cj/set-wallpaper passed `(dired-file-name-at-point)` straight to `expand-file-name`, so running it with no file at point raised a bare `wrong-type-argument` instead of a clear error. cj/dired-create-playlist-from-marked expanded the raw playlist name under `music-dir` without checking it, so a name like "../foo" or "/etc/foo" would write outside the music directory.

I added a nil-file guard to set-wallpaper and a `cj/--playlist-name-safe-p` check that rejects any name carrying a directory separator before the path is built. Both paths now fail cleanly with a user-error. Regression tests went into the existing wrapper and playlist test files.
</content>
</entry>
<entry>
<title>fix(dirvish): declare runtime constant/util deps with plain require</title>
<updated>2026-05-23T08:21:07+00:00</updated>
<author>
<name>Craig Jennings</name>
<email>c@cjennings.net</email>
</author>
<published>2026-05-23T08:21:07+00:00</published>
<link rel='alternate' type='text/html' href='https://git.cjennings.net/dotemacs/commit/?id=b63c4f83081ec50074307cdbaff68525869d7294'/>
<id>urn:sha1:b63c4f83081ec50074307cdbaff68525869d7294</id>
<content type='text'>
dirvish-config builds `dirvish-quick-access-entries` from `code-dir`, `music-dir`, `pix-dir`, and the recording dirs at load time, and binds keys to `cj/xdg-open` and `cj/open-file-with-command`. Those come from user-constants and system-utils, but the module only required them under `eval-when-compile`, so the compiled module carries no runtime require and leans on init order having loaded them first.

I switched both to plain requires, matching host-environment, system-lib, and external-open-lib right below. Added a dependency-contract smoke test that fails if the requires are dropped.
</content>
</entry>
<entry>
<title>feat(dirvish): start org-drill on the .org file at point with S</title>
<updated>2026-05-12T17:49:33+00:00</updated>
<author>
<name>Craig Jennings</name>
<email>c@cjennings.net</email>
</author>
<published>2026-05-12T17:49:33+00:00</published>
<link rel='alternate' type='text/html' href='https://git.cjennings.net/dotemacs/commit/?id=a15c6b3e92b8d7be6be6dc4dd6802a18ccf52326'/>
<id>urn:sha1:a15c6b3e92b8d7be6be6dc4dd6802a18ccf52326</id>
<content type='text'>
`S` ("study") in `dirvish-mode-map` opens the `.org` file at point and runs `cj/drill-this-file` on it, so I can drill any deck straight from the file list. It `user-error`s on no file, on a directory, or on a non-`.org` file.

`D` and `O` were already taken (duplicate-file, open-with-command), so I went with `S`. It shadows dired's `dired-do-symlink`, which I never use from dirvish and which stays on `M-x` anyway. New `tests/test-dirvish-config-drill.el`: 6 ERT tests with `dired-get-filename`, `find-file`, and `cj/drill-this-file` mocked. I also fixed the stale `P` line in the module commentary — `P` is the print command now, not copy-path.
</content>
</entry>
<entry>
<title>feat(dirvish): print the file at point with P</title>
<updated>2026-05-12T09:45:47+00:00</updated>
<author>
<name>Craig Jennings</name>
<email>c@cjennings.net</email>
</author>
<published>2026-05-12T09:45:47+00:00</published>
<link rel='alternate' type='text/html' href='https://git.cjennings.net/dotemacs/commit/?id=2063e9dffff751d5f2cbca2817aee5daaa463cf3'/>
<id>urn:sha1:2063e9dffff751d5f2cbca2817aee5daaa463cf3</id>
<content type='text'>
`P' in dirvish/dired now runs `cj/dirvish-print-file': it sends the file at point to the default printer via CUPS (`lp', falling back to `lpr'), after a confirmation prompt. It refuses directories and file types outside `cj/dirvish-print-extensions' (pdf, txt, org, images, source files, ...). CUPS filters handle PDFs directly, so `lp &lt;file&gt;' covers everything in the list, and no separate print dialog is needed.

`P' was the project/home-relative variant of `cj/dired-copy-path-as-kill', now dropped (the `p' absolute-path and `l' org-link bindings stay, and `M-x cj/dired-copy-path-as-kill' still works). Plain dired's built-in `P' was `dired-do-print', which `dirvish-mode-map' had already shadowed, so this just replaces it with a type-aware version. Tests cover the predicates and the command's confirm / decline / no-file / directory / non-printable / no-printer / print-failure paths.
</content>
</entry>
<entry>
<title>refactor(external-open): extract external-open-lib for shared helpers</title>
<updated>2026-05-10T20:37:36+00:00</updated>
<author>
<name>Craig Jennings</name>
<email>c@cjennings.net</email>
</author>
<published>2026-05-10T20:37:36+00:00</published>
<link rel='alternate' type='text/html' href='https://git.cjennings.net/dotemacs/commit/?id=618bc7813b9acfcf1dfccc9c6590f6f5aece86cf'/>
<id>urn:sha1:618bc7813b9acfcf1dfccc9c6590f6f5aece86cf</id>
<content type='text'>
Same shared-helpers split-pattern that ai-vterm/vterm-config use
through cj-window-toggle-lib and that calendar-sync uses through
cj-org-text-lib.  Pull the two pure dispatch helpers out of the
external-open feature module into a sibling library so consumers
that only need the dispatch don't have to require the whole feature.

New `modules/external-open-lib.el' carries:
- `cj/external-open-command'
- `cj/external-open-launcher-p'

`modules/external-open.el' stays as the feature module: the
`default-open-extensions' defcustom, the `find-file' advice
(`cj/find-file-auto'), and the interactive commands (`cj/xdg-open',
`cj/open-this-file-with').  It now requires external-open-lib for
the dispatch helpers.

Migrate consumers:
- system-utils.el used to require `external-open' for
  `cj/external-open-launcher-p' alone -- now requires
  `external-open-lib' directly.
- dirvish-config.el calls `cj/external-open-command' from
  `cj/dirvish-open-file-manager-here' -- add an explicit
  `(require \='external-open-lib)'.

Test files renamed to match the system-lib naming pattern
(test-&lt;library&gt;-&lt;feature&gt;.el):
- test-external-open-command.el -&gt; test-external-open-lib-command.el
- test-external-open-launcher-p.el -&gt; test-external-open-lib-launcher-p.el

No behavior change.
</content>
</entry>
<entry>
<title>refactor(external-open): consolidate OS-open dispatch in external-open.el</title>
<updated>2026-05-10T19:42:04+00:00</updated>
<author>
<name>Craig Jennings</name>
<email>c@cjennings.net</email>
</author>
<published>2026-05-10T19:42:04+00:00</published>
<link rel='alternate' type='text/html' href='https://git.cjennings.net/dotemacs/commit/?id=16396d25c2795bd7f8822a695de111d07f588b26'/>
<id>urn:sha1:16396d25c2795bd7f8822a695de111d07f588b26</id>
<content type='text'>
Phase 4 of utility-consolidation. Three previously-overlapping helpers (system-utils' `cj/identify-external-open-command' and `cj/--open-with-is-launcher-p', plus the dirvish-only `cj/--file-manager-program-for' shipped earlier today) all answered "which OS-open program should I run?".  Pull the answer into one place: external-open.el.

Move and rename:

- `cj/--open-with-is-launcher-p' (system-utils) -&gt; `cj/external-open-launcher-p' (external-open). Public name now matches its module.
- `cj/identify-external-open-command' (system-utils) -&gt; `cj/external-open-command' (external-open). Returns nil for unsupported hosts instead of signaling -- callers that need a command must handle nil explicitly. The wrapper `cj/xdg-open' (also moved into external-open) converts nil to a `user-error' with a clear message, preserving the user-facing failure shape.
- Delete dirvish's `cj/--file-manager-program-for' helper. `cj/dirvish-open-file-manager-here' now calls `cj/external-open-command' directly. The shell-command fallback for nil-program preserves the previous escape hatch.

Break the system-utils &lt;-&gt; external-open recursive require by moving `cj/xdg-open' (the only system-utils function that external-open used) into external-open along with the dispatch.

Tests reorganized to match the move. Two new test files (`test-external-open-command.el', `test-external-open-launcher-p.el') replace the two system-utils-named test files. The dirvish file-manager-program test goes away with the helper. 11 tests covering Normal/Boundary/Error for the dispatch (plus the new "unsupported host returns nil" contract).

Add `(require \='external-open)' to system-utils.el and `(require \='system-lib)' to external-open.el (for `cj/file-from-context' which xdg-open uses).
</content>
</entry>
<entry>
<title>refactor(dirvish): use cj/executable-find-or-warn in cj/set-wallpaper</title>
<updated>2026-05-10T19:08:01+00:00</updated>
<author>
<name>Craig Jennings</name>
<email>c@cjennings.net</email>
</author>
<published>2026-05-10T19:08:01+00:00</published>
<link rel='alternate' type='text/html' href='https://git.cjennings.net/dotemacs/commit/?id=12c2cb141fcfd7ec14e04ce59e6294ffea4e2df9'/>
<id>urn:sha1:12c2cb141fcfd7ec14e04ce59e6294ffea4e2df9</id>
<content type='text'>
Migrate `cj/set-wallpaper' to the new helper.  The old code messaged a bare `"%s not found"' when feh or swww was missing -- the helper produces a better message ("feh not found; wallpaper setter unavailable") that lands in *Warnings* instead of flashing once in the echo area.

Add `(require \='system-lib)' to dirvish-config.el per the Phase 2 exit criterion.

Audit notes for the other `executable-find' call sites in prog-shell, prog-c, prog-go, prog-python, browser-config: all silent `:if' / LSP-availability checks (spec says keep silent), or interactive commands with platform-specific install hints in their existing messages (`shellcheck`, `mypy`, `delve`).  Migrating those would lose the install hints; leaving them.
</content>
</entry>
<entry>
<title>refactor(dirvish): extract cj/--dired-line-is-directory-p</title>
<updated>2026-05-10T18:44:40+00:00</updated>
<author>
<name>Craig Jennings</name>
<email>c@cjennings.net</email>
</author>
<published>2026-05-10T18:44:40+00:00</published>
<link rel='alternate' type='text/html' href='https://git.cjennings.net/dotemacs/commit/?id=2b19eb175d0664908f76bf7cc8dcc1eb5c140ce1'/>
<id>urn:sha1:2b19eb175d0664908f76bf7cc8dcc1eb5c140ce1</id>
<content type='text'>
`cj/dired-mark-all-visible-files' classified the current line as a directory via `(looking-at "^. d")' inline.  Lift the classification into `cj/--dired-line-is-directory-p', a string predicate that takes a line and returns non-nil when it's a directory listing.  The wrapper still walks the dired buffer line by line and calls `dired-mark' -- that iteration is dired-coupled and stays untested -- but the format-aware predicate is now isolated and verified.

Six Normal/Boundary tests cover unmarked directories, marked directories (`*' prefix), regular files (`-' instead of `d'), symlinks (`l'), empty lines, and dired header lines (`  /path:' and `  total N').
</content>
</entry>
<entry>
<title>refactor(dirvish): extract cj/--html-file-p; match HTML case-insensitively</title>
<updated>2026-05-10T18:42:11+00:00</updated>
<author>
<name>Craig Jennings</name>
<email>c@cjennings.net</email>
</author>
<published>2026-05-10T18:42:11+00:00</published>
<link rel='alternate' type='text/html' href='https://git.cjennings.net/dotemacs/commit/?id=cffdf3b92a97b1af3aedec624a9fb43db1c60ef8'/>
<id>urn:sha1:cffdf3b92a97b1af3aedec624a9fb43db1c60ef8</id>
<content type='text'>
`cj/dirvish-open-html-in-eww' inlined a `string-match-p' against `\.html?\=' to decide whether to hand a file to eww.  The check was case-sensitive, so `.HTML' fell through to the "Not an HTML file" message even though every browser treats it as HTML.  Lift the predicate into `cj/--html-file-p' and bind `case-fold-search' to t so uppercase and mixed-case extensions match.  The trailing-`\=' anchor stays so files like `html-thing.org' still don't match.

Seven Normal/Boundary/Error tests cover lowercase `.html', `.htm', uppercase `.HTML', mixed-case `.Html', embedded `html' (no match), non-html extensions (no match), and no-extension files (no match).
</content>
</entry>
<entry>
<title>refactor(dirvish): extract cj/--ediff-pair-from-files; fix 0-files crash</title>
<updated>2026-05-10T18:41:27+00:00</updated>
<author>
<name>Craig Jennings</name>
<email>c@cjennings.net</email>
</author>
<published>2026-05-10T18:41:27+00:00</published>
<link rel='alternate' type='text/html' href='https://git.cjennings.net/dotemacs/commit/?id=09f3349af22c932a01f0788787cee9ab4f3c38a7'/>
<id>urn:sha1:09f3349af22c932a01f0788787cee9ab4f3c38a7</id>
<content type='text'>
`cj/dired-ediff-files' had its pair-determination logic inline: count check, prompt fallback when only one file was marked, and the older-first ordering for `ediff-files'.  Lift it into `cj/--ediff-pair-from-files' -- pure given the file list, an injected prompt thunk, and a newer-than-p comparator -- so tests stay independent of mtimes and the dired prompt.

While extracting, surface a latent bug: with zero marked files the original code fell through to `(file-newer-than-file-p nil nil)' and crashed with a wrong-type-argument error.  Replace the crash with a clear `user-error' ("No files marked"), and add a regression test.  The 3+ files case keeps its existing user-error message.

Five Normal/Boundary/Error tests cover both ordering directions, the one-file prompt path, and both error counts.
</content>
</entry>
</feed>
