| Commit message (Collapse) | Author | Age | Files | Lines |
| |
|
|
| |
`cj/mu4e--save-attachment-part' called `cj/mu4e--ensure-attachment-save-functions' first, so a part with no MIME handle triggered the `mu4e-mime-parts' load before the handle check could fail. The handle check now runs first, so the malformed input is caught right away and the user-error fires the same way whether or not mu4e's MIME support is loadable. The test for that case drops the mu4e stubs it only needed because the load used to come first.
|
| |
|
|
|
|
| |
The attachment-save UI (the MIME-part filters, the three save commands, and the `special-mode'-derived selection buffer) was ~230 lines in `mail-config.el' and depended on nothing else there. It moves to `modules/mu4e-attachments.el', which `mail-config' now requires. `cj/email-map' and its C-; e bindings stay put. The keymap just points at commands that now live next door.
The unit tests move with it: `test-mail-config-attachments.el' becomes `test-mu4e-attachments.el' and requires the new module directly instead of pulling in the whole mu4e and org-msg use-package stack. The two tests that check `cj/email-map' wiring move to a new `test-mail-config.el', since that map belongs to `mail-config'. One of the moved tests quietly relied on a real mu4e install (it loaded `mu4e-mime-parts' through a load-path entry that loading `mail-config' happens to add), so it now stubs that path itself.
|
| |
|
|
| |
The Makefile's `COVERAGE_EXCLUDE' comment said why `test-all-comp-errors.el' is excluded but not why `test-lorem-optimum-benchmark.el' is. It now notes that undercover's instrumentation slows execution enough to fail the benchmark's wall-clock assertions. And `cj/markov-generate' now has a comment explaining why `tokens' is seeded reversed (`(list w2 w1)'): the list is built with `push' and `nreverse'd at the end, so without the note the reversed seed reads like a bug at a glance.
|
| |
|
|
| |
`cj/org-capture--goto-file-headline' stored the marker under `(list (expand-file-name (buffer-file-name)) headline)' while the lookup helper `cj/org-capture--file-headline-cache-key' uses `(list (org-capture-expand-file path) headline)'. They agree for plain absolute paths, but a symlinked target (`expand-file-name' doesn't follow symlinks, `find-file' canonicalizes the buffer name) or a capture path that needs `org-capture-expand-file's special handling would make the put-key and get-key diverge, so the cache would silently never hit. Production now goes through the helper too, so the key is built one way.
|
| |
|
|
| |
Three project-owned commands that reuse mu4e's MIME metadata (`mu4e-view-mime-parts') and save primitives (`mm-save-part-to-file', `mu4e-uniquify-save-file-name-function') directly instead of driving mu4e's completion UI. `cj/mu4e-save-all-attachments' (`C-; e S') prompts once for a directory and saves every attachment-like part. `cj/mu4e-save-attachment-here' (`C-; e s') saves one attachment, picked by display label, with duplicate filenames shown as "name <part N>" so they don't collapse into one completion candidate. `cj/mu4e-save-some-attachments' (`C-; e m') opens a `*mu4e attachments*' selection buffer showing mark state, label, MIME type, and size per row, where `RET' toggles a row, `a' / `u' mark / unmark all, `s' saves the marked ones, and `q' quits. Replaced the old Embark/Vertico-workaround comment. Tests cover the attachment filtering, the duplicate-filename disambiguation, save-path construction, the no-handle error, command prompting, and the email-map bindings.
|
| |
|
|
| |
Dropped the 1-second `:defer' from the calibredb use-package and the redundant explicit `nov-render-document' call in `cj/nov-apply-preferences'. Nov / visual-fill-column text width now recalculates on `window-configuration-change-hook'. `cj/nov--text-width-for-window' computes the (clamped, minimum-readable) width and `cj/nov-update-layout' installs it buffer-locally. Lowered `calibredb-search-page-max-rows' from 20000 to 500 (pagination was effectively disabled). Replaced the anonymous zathura keybinding with `cj/nov-open-external'. Tests cover the width computation and the external-open binding.
|
| |
|
|
| |
Two problems with `C-; S !'. First, emacs-slack's `slack-reaction-echo-description' runs in a buffer-local `post-command-hook' and can error on every keystroke when a reaction widget's text properties are malformed, which makes it hard to leave the Slack buffer or recover with C-g. It's now wrapped in `condition-case' that, on error, removes the hook from that buffer's local `post-command-hook' and messages once. Second, without emojify the upstream reaction picker is a flat completing-read over 1600+ names. `cj/slack-message-add-reaction' now offers a curated common list (`thumbsup', `pray', `eyes', `white_check_mark', `heart', `joy', `thinking_face', `rocket', `tada') with "Other..." delegating back to `slack-message-reaction-input' for the full set. Tests cover the hook hardening, the curated picker, and the `C-; S !' rebinding.
|
| |
|
|
| |
`cj/markov-join-tokens' collects tokens in a list and `mapconcat's once instead of repeated string concatenation. `cj/markov-generate' uses `push'/`nreverse' instead of repeated `append'. The Markov keys are cached as a vector so random key selection is O(1). Re-enabled the benchmark tests (the `:slow' tags were stale) and added a `cj/lipsum-title' test after byte-compilation flagged a malformed form there. `assets/liber-primus.txt' is left as-is (36 KB / 5,374 words, small enough not to need trimming). 100K-word learning now measures about 196 ms.
|
| |
|
|
| |
lsp-mode 9.0.0 made `lsp-eldoc-hook' an obsolete alias for Emacs's `eldoc-documentation-functions', and `lsp-managed-mode' already adds `lsp-eldoc-function' to that buffer-local hook. Dropped the obsolete `(setq lsp-eldoc-hook nil)'. `cj/lsp--disable-eldoc-hover' now removes `lsp-eldoc-function' from the buffer-local `eldoc-documentation-functions' via `lsp-managed-mode-hook', which clears the obsolete-variable byte-compile warning. Tests cover the hook removal, leaving the default `eldoc-documentation-functions' value alone, and the module no longer naming `lsp-eldoc-hook'.
|
| |
|
|
| |
A task capture took 15-20 seconds: Org resolves a `(file+headline FILE "Headline")' target by opening the file, widening, and regex-scanning from the top for the headline, and the inbox template captures to `(file+headline inbox-file "Inbox")' over and over. An `:around' advice on `org-capture-set-target-location' caches a marker per resolved file/headline. On the next capture it validates the marker (still live, still in an Org buffer, still at a heading, headline text still matches) and jumps straight there. On any mismatch it falls back to the normal scan/create and refreshes the cache. `M-x cj/org-capture-clear-target-cache' resets it. Tests cover the cache hit, marker invalidation after the headline text changes, and creating a missing headline.
|
| |
|
|
| |
The async .ics-to-Org worker runs `emacs --batch --no-site-file --no-site-lisp' and loads `calendar-sync.el' by absolute path, but that doesn't make its sibling `(require 'cj-org-text-lib)' resolvable, so the conversion died with "Cannot open load file: cj-org-text-lib". `calendar-sync--worker-command' now inserts `-L <module-dir>' before `-l calendar-sync.el', which keeps the worker isolated from `init.el' while letting it load its local module deps. Updated the worker-command test and added a regression test that runs the real no-init worker shape.
|
| |
|
|
| |
`cj/kill-other-window-buffer' (in undead-buffers.el, on `C-; b K') kills or buries the buffer shown in the other window and leaves that window and the split alone. The window just shows whatever bury/kill surfaces next. It reuses `cj/kill-buffer-or-bury-alive', so buffers in `cj/undead-buffer-list' (like `*scratch*') get buried. With more than two windows it acts on `next-window'. Sibling of `cj/kill-other-window' (M-S-o), which deletes the other window. This one keeps it.
|
| |
|
|
|
|
| |
`C-; b <left>/<right>/<up>/<down>' moves the active window's divider that way (via `windsize'), then keeps `cj/window-resize-map' active so bare arrows keep nudging until any other key (or `C-g'/`<escape>'). `C-u N C-; b <right>' resizes by N.
windsize was on `C-s-<arrow>' (Ctrl+Super), which a tiling WM intercepts, so those keys were useless. I dropped that binding. The package is now `:commands'-deferred, and `windsize-cols'/`windsize-rows' drop to 2 (8/4 overshoots in a held nudge loop). `cj/window-resize-sticky' dispatches on the arrow that triggered it and arms the loop.
|
| |
|
|
|
|
| |
The picker's active group (projects with a live tmux session) used to sort alphabetically. It now leads with projects opened this session, most-recent first, then the rest of the active group alpha, then the no-session group alpha. An in-session list (`cj/--ai-vterm-mru'), pushed to the front by `cj/--ai-vterm-show-or-create' on every open, drives the order. An empty list reproduces the old alphabetical behavior.
I also pulled in a fix: `cj/--ai-vterm-tmux-session-name' now sanitizes `.' and `:' in the basename to `_'. tmux disallows those chars in session names and silently rewrites them, so `.emacs.d' really runs in session `aiv-_emacs_d', not `aiv-.emacs.d'. The computed name never matched, so `.emacs.d' was wrongly treated as having no session and landed in the no-session picker group. (A crash-recovery would also spawn a duplicate instead of reattaching.) Sanitizing the same way tmux does keeps the names in sync.
|
| |
|
|
| |
`C-; b e' read best for `eval-buffer', but `e' was `cj/view-email-in-buffer' and the requested fallback `C-; b m' is `cj/move-buffer-and-file', so email-view moves to `C-; b E' (docstring and which-key updated).
|
| |
|
|
|
|
|
|
| |
`vterm-copy-mode' and the `C-; x h' tmux-history buffer now share one key story. `M-w' copies the active region and stays put, so I can copy several things in a row. `C-g', `<escape>', or `q' leaves (resuming the live terminal, or closing the history buffer) without copying. `RET' is unbound (no special "copy and exit"). In copy-mode that meant removing vterm's default `RET' -> `vterm-copy-mode-done' binding.
Before, `M-w' exited and copied as it went, which made grabbing more than one selection awkward. The history buffer's `cj/vterm-tmux-history-copy-and-quit' was the copy-and-exit one-shot. It's gone. `M-w' then `q' is the equivalent.
I also moved `cj/vterm-tmux-history' from `C-; x C' to `C-; x h' (unshifted, and it frees `C') and refreshed the file's stale commentary header, which still referenced the old `C-; V' prefix and `<pause>'.
|
| |
|
|
|
|
| |
`server-start' leaves `server-window' nil, so `server-switch-buffer' opens an `emacsclient -n' file in the selected window. When I'm typing in the agent vterm, the selected window is the agent window, so "tell the agent to open something" replaced the agent buffer with that file. I wired `server-window' to a function. When the selected window shows an `agent [...]' buffer, it puts the file in a non-agent window instead, splitting one off to the left of the agent when the agent is the only window. emacsclient invocations from anywhere else still go through `pop-to-buffer' unchanged.
`cj/--ai-vterm-non-agent-window' picks the target window. It skips the minibuffer, dedicated windows, and any window already showing an agent buffer.
|
| |
|
|
|
|
| |
`vterm-mode' sets `buffer-read-only', so `cj/set-cursor-color-according-to-mode' painted the cursor with the read-only color (orange) whenever point was in a vterm. That includes the live terminal, not just `vterm-copy-mode'. But a live terminal takes input: keystrokes go to the process, not the buffer. So a live vterm now reports `unmodified' instead. `vterm-copy-mode' still reports `read-only': there it really is a read-only Emacs buffer the user navigates, and the orange cursor is the right signal.
I pulled the state cond out of `cj/set-cursor-color-according-to-mode' into `cj/--buffer-cursor-state' so it's unit-testable without a real frame or `set-cursor-color'.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
I may add other terminal agents to this launcher (aider, an open-source LLM TUI), so the buffer prefix, the user knob, and the internal helpers shouldn't say "Claude". The module name (ai-vterm) and the `cj/ai-vterm-*` customs were already generic. This finishes the job:
- buffer prefix `claude [<basename>]` -> `agent [<basename>]` (the `defconst` and the matching display-buffer-alist regex move together)
- `cj/ai-vterm-claude-command` -> `cj/ai-vterm-agent-command` (the default still runs the `claude` CLI, with a docstring note on swapping it)
- `cj/--ai-vterm-claude-buffers` / `-displayed-claude-window` / `-reuse-existing-claude` -> `-agent-*`, and their test files renamed to match
- prose in the module commentary and docstrings, plus the matching test docstrings and buffer-name literals
`vterm-config.el` hardcodes the same buffer prefix in `cj/--vterm-toggle-buffer-p` (F12 excludes agent buffers from its candidate set), so that literal moved too. Collapsing it into the shared `cj/--ai-vterm-name-prefix` is a cleanup for another day.
After a reload, a project's buffer opens as `agent [foo]` instead of `claude [foo]`. Old buffers keep their names until killed. I also corrected two stale `eshell-vterm-config.el` references in ai-vterm.el docstrings (that module was split into `vterm-config.el`).
Two things keep saying "Claude": the `cj/ai-vterm-agent-command` default value (the actual CLI), and the "Claude Code" example in `vterm-config.el`'s cursor-restore docstring (a concrete TUI example, not branding).
90 tests pass. `make validate-modules` clean.
|
| |
|
|
| |
I pass `tmux new-session -n` so the window running the AI tool shows up as "ai" in the window list instead of auto-naming after the running program. A shell opened by hand in a later window still auto-names (e.g. "zsh"), so the two read distinctly. The name is a new `defcustom` (`cj/ai-vterm-tmux-window-name`), symmetric with the session-prefix custom.
|
| |
|
|
|
|
| |
Each project's tmux session is now named `<cj/ai-vterm-tmux-session-prefix><basename>` (default `aiv-`), so `tmux ls` can be filtered to AI-vterm's own sessions. After an Emacs crash the C-F9 project picker reads `tmux list-sessions`, matches surviving sessions back to their directories, and sorts those to the top: `[detached]` when only the tmux session is alive, `[running]` when a vterm buffer exists. The rest follow alphabetically. With tmux missing or no server running, it falls back to a plain alphabetical list. The picker's collection is a completion table that pins display order so Vertico doesn't re-sort and undo the active-first grouping.
The prefix is a new `defcustom` rather than `claude-`, which collides with hand-rolled tmux sessions. Sessions named before this change use the bare basename and won't be matched afterward. One `tmux kill-server` clears any orphans.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
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-<library>-<feature>.el):
- test-external-open-command.el -> test-external-open-lib-command.el
- test-external-open-launcher-p.el -> test-external-open-lib-launcher-p.el
No behavior change.
|
| |
|
|
|
|
|
|
|
|
|
|
| |
Same naming-convention fix as the other library renames in this series.
Rename modules/cj-window-geometry.el -> modules/cj-window-geometry-lib.el
and tests/test-cj-window-geometry.el -> tests/test-cj-window-geometry-lib.el.
Update provide forms, file headers, and the (require 'cj-window-geometry)
call sites in cj-window-toggle-lib.el, ai-vterm.el, vterm-config.el,
and the test file.
No behavior change.
|
| |
|
|
|
|
|
|
|
|
|
|
| |
consistency
Rename modules/cj-window-toggle.el -> modules/cj-window-toggle-lib.el
and tests/test-cj-window-toggle.el -> tests/test-cj-window-toggle-lib.el.
Update provide forms, file headers, and the (require 'cj-window-toggle)
call sites in ai-vterm.el, vterm-config.el, and the test file.
Same rationale as the cj-cache and cj-org-text renames -- library files
in this codebase are suffixed `-lib'. No behavior change.
|
| |
|
|
|
|
|
|
|
|
|
| |
Same naming-convention fix as the cj-cache rename. Org-safe text
sanitizers extracted in Phase 3 went into modules/cj-org-text.el,
should have followed the established `-lib' suffix convention.
Rename modules/cj-org-text.el -> modules/cj-org-text-lib.el; update
provide form, file header, and the two (require 'cj-org-text) call
sites in calendar-sync and test-cj-org-text-sanitize. No behavior
change.
|
| |
|
|
|
|
|
|
|
|
|
|
|
| |
Library files in this codebase are suffixed `-lib' (system-lib.el is
the established precedent). The Phase 5 cache helper landed as
cj-cache.el; the spec's table proposed names without the suffix and
I followed it without checking against convention. Fix the
inconsistency now while there are only two consumers and one test.
Rename modules/cj-cache.el -> modules/cj-cache-lib.el; update
provide form, file header, and the three (require 'cj-cache) call
sites in org-agenda-config, org-refile-config, and test-cj-cache.
No behavior change.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
| |
Phase 5 step 3 of utility-consolidation, second of two cache migrations. Same shape as the agenda migration: drop four state vars, replace the build-with-cache function with a thin wrapper around `cj/cache-value-or-rebuild', extract the slow scan into a pure-ish helper.
Add `cj/--org-refile-scan-targets' as the slow filesystem walk (org-roam node enumeration plus 30,000+ todo.org files across code-dir and projects-dir). `cj/build-org-refile-targets' now reads as: detect background-build state, ask the cache helper for the value with the scan helper as BUILD-FN, route the original log lines through :on-hit / :on-build-success.
Drop four module-level state vars:
- `cj/org-refile-targets-cache'
- `cj/org-refile-targets-cache-time'
- `cj/org-refile-targets-cache-ttl'
- `cj/org-refile-targets-building'
Rewrite the existing test file to test wrapper behavior at the contract level (stub the scan helper, verify wrapper outcomes). 8 tests parallel the agenda test set: first-call builds, second-call uses cache, force-rebuild bypass, TTL expiration, empty scan, building-flag cleanup on success and error, and error propagation.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Phase 5 step 2 of utility-consolidation. The agenda-files cache (four module-level vars + 35-line build-with-cache function) now delegates to `cj-cache.el'. Behavior preserved: cache-hit logging, "Building..." background message, building-flag cleanup on error.
Add `cj/--org-agenda-scan-files' as a pure-ish helper that returns the file list (the slow filesystem walk). `cj/build-org-agenda-list' becomes a thin wrapper that calls `cj/cache-value-or-rebuild' with the scan helper as BUILD-FN and routes the original log lines through :on-hit / :on-build-success.
Drop four module-level state vars:
- `cj/org-agenda-files-cache'
- `cj/org-agenda-files-cache-time'
- `cj/org-agenda-files-cache-ttl'
- `cj/org-agenda-files-building'
Replace with a single `cj/--org-agenda-files-cache' plist held by the helper.
Rewrite the existing test file to test wrapper behavior at the contract level (stub the scan helper, verify wrapper outcomes) instead of poking at internal state vars. 8 tests cover first-call builds, second-call uses cache, force-rebuild bypass, TTL expiration, empty scan, building-flag cleanup on success and error, and error propagation.
|
| |
|
|
|
|
|
|
| |
Phase 5 step 2 of utility-consolidation. Add `modules/cj-cache.el' implementing the API specified in `docs/design/cache-helper-design.org': `cj/cache-make' / `cj/cache-valid-p' / `cj/cache-value-or-rebuild' / `cj/cache-building-p' / `cj/cache-invalidate'.
The helper captures the TTL+building-guard pattern that org-agenda-config and org-refile-config currently hand-roll. Both consumers will migrate in follow-up commits. No call-site changes in this commit -- helper plus its 15 tests only.
Tests cover: default and custom TTL, fresh/recent/expired/nil-value validity, miss calls build / hit skips build, force-rebuild overrides hit, the four log callbacks (on-hit / on-build-start / on-build-success / on-build-error), error-rethrow and building-flag cleanup on both success and error paths.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
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) -> `cj/external-open-launcher-p' (external-open). Public name now matches its module.
- `cj/identify-external-open-command' (system-utils) -> `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 <-> 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).
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
| |
Phase 3 of utility-consolidation. Three sanitizers moved from calendar-sync.el into a new cj-org-text.el module so other consumers (web-clipper, AI conversation, mail-to-org capture) can compose Org content from external text without depending on calendar:
- `calendar-sync--sanitize-org-body' -> `cj/org-sanitize-body-text'
- `calendar-sync--sanitize-org-property-value' -> `cj/org-sanitize-property-value'
- `calendar-sync--sanitize-org-heading' -> `cj/org-sanitize-heading'
The helpers stay pure (string in, string out, nil-safe) and have no Org-mode dependency, so they work in batch and in tests without loading Org.
Migrate calendar-sync.el to use the new public names: drop the three local defuns, add `(require \='cj-org-text)', update the six call sites in `calendar-sync--make-event-entry'.
Move the existing 17-test file to `tests/test-cj-org-text-sanitize.el', rename test names to match the new helpers, add 1 nil-input test for `cj/org-sanitize-heading' that wasn't in the original file. Total: 18 Normal/Boundary tests across the three helpers.
|
| |
|
|
|
|
|
|
| |
Phase 2.4 of utility-consolidation, the last item in the spec's recommended order. `cj/--file-from-context' resolves "the current file" via a three-step fallback chain (explicit arg, `buffer-file-name', dired file at point) -- a useful pattern for any command that operates on the current file regardless of which kind of buffer the user is in. Promote to public `cj/file-from-context' and re-home in system-lib.el so other modules (mail capture, external-open, AI conversation, dirvish helpers) can use it without an awkward dependency on system-utils.
Migrate the two callers in system-utils.el (`cj/open-this-file-with' and `cj/open-file-with-command') and add `(require \='system-lib)' there per the Phase 2 exit criterion.
Move the existing 7-test file to `tests/test-system-lib-file-from-context.el' and update its references to the new public name. The test shape is unchanged: 4 Normal + 3 Boundary cases covering explicit-arg precedence, buffer-file-name fallback, dired fallback, and the all-nil case.
|
| |
|
|
|
|
|
|
|
|
|
|
| |
cj/git-output-or-error from coverage-core
Phase 2.3 of utility-consolidation. `cj/--coverage-git-string' was a generic argv-based runner ("run program, return stdout, raise user-error on non-zero with status+output in the message") trapped inside coverage-core. Lift the generic shape into `cj/process-output-or-error' and add `cj/git-output-or-error' as a one-line wrapper that supplies "git" as the program. Both live in system-lib.el.
Future callers I have in mind: reconcile-open-repos shell-style git calls (the high-priority data-safety task), vc-config clipboard cloning, mail integrations that touch git for commit signatures.
Six Normal/Boundary/Error tests cover success/no-args/non-zero-exit for the generic runner, the user-error message content (program name, exit status, trimmed output), and the git wrapper's program argument routing.
Migrate coverage-core's `cj/--coverage-git-merge-base' and `cj/--coverage-git-diff' to call the new git wrapper. Drop the local `cj/--coverage-git-string' definition. Add `(require \='system-lib)' to coverage-core.el per the Phase 2 exit criterion.
|
| |
|
|
|
|
|
|
| |
Phase 2.2 of utility-consolidation. The "quote only when shell-unsafe characters appear, otherwise leave the argument readable" pattern was trapped in dev-fkeys as `cj/--f6-shell-quote-argument' alongside its `cj/--f6-shell-safe-argument-regexp' constant. Lift both into system-lib.el under their generic names; the F6 branding hid that the same shape is useful for any generated compile/test command where the surrounding line ends up in a *compilation* buffer the user reads.
Six Normal/Boundary tests cover safe inputs that pass through unchanged (alphanumeric paths, test regexes, `FLAG=value', `host:port'), unsafe inputs that get quoted (spaces, `$', `;', `&', backticks, `*'), and the empty-string boundary.
Migrate dev-fkeys's five callers to the new name and add `(require \='system-lib)' per the Phase 2 exit criterion.
|
| |
|
|
|
|
|
|
| |
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.
|
| |
|
|
|
|
|
|
|
|
| |
Phase 2 of utility-consolidation, first commit per the spec's recommended order. `cj/mail--executable-or-warn' was the right pattern -- check executable-find, return the path, otherwise emit a clear `display-warning' naming the feature -- but it was trapped in mail-config and only mail callers benefited. Lift it into `cj/executable-find-or-warn' in system-lib.el with one new argument: an optional GROUP symbol that flows through to `display-warning' (defaulting to `cj/system-lib') so per-feature warning filters keep working. Mail callers pass `mail-config' explicitly.
Migrate `cj/mail--mbsync-command' and `cj/mail-configure-smtpmail' to the new helper. Drop the local definition. Add `(require \='system-lib)' to mail-config.el per the spec's Phase 2 exit criterion ("consumer modules explicitly require system-lib").
Five Normal/Boundary tests cover the four return-shape cases (program found / program missing / warning content / default vs explicit group).
Other consumers (prog-*.el, dirvish-config.el, browser-config.el) still call `executable-find' directly. Migrating them is a follow-up commit, audited per call site -- the spec flags some `:if' silent checks as intentional and they should NOT switch to the warning helper.
|
| |
|
|
|
|
| |
`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').
|
| |
|
|
|
|
| |
`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).
|
| |
|
|
|
|
|
|
| |
`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.
|
| |
|
|
|
|
|
|
|
| |
`cj/dired-create-playlist-from-marked' had its audio-file filtering and trailing-`.m3u' stripping inline among the dired marking, prompting, overwrite-loop, and file-write logic. Lift each into its own pure helper:
- `cj/--playlist-filter-audio (files extensions)' returns only files whose extension matches one of EXTENSIONS (lowercase, no dot). Case-insensitive on the file side.
- `cj/--playlist-sanitize-name (name)' strips a trailing `.m3u' suffix; embedded `.m3u' that isn't at the end is preserved.
Ten Normal/Boundary tests cover keep-only-audio, case-insensitivity, files-without-extension excluded, empty inputs, and the sanitize edge cases (bare name, embedded `.m3u', empty string, just `.m3u').
|
| |
|
|
|
|
| |
`cj/dirvish-open-file-manager-here' had its platform-dispatch -- xdg-open / open / explorer / shell fallback -- inline as a four-arm cond branching on `executable-find' and `system-type'. Lift the choice into `cj/--file-manager-program-for', a pure function from (HAS-XDG-OPEN-P SYSTEM-TYPE) to a program-name string or nil. The wrapper resolves the live values, asks the helper, and either calls the program or falls back to a shell-command.
Six Normal/Boundary tests cover the four return shapes (xdg-open across system types, darwin/windows-nt without xdg-open, the nil fallback for plain Linux without xdg-open and for unknown system-types like Haiku).
|
| |
|
|
| |
`cj/set-wallpaper' had two parallel cond arms hardcoding the X11/Wayland dispatch and the success/failure messages inline. Lift the program-and-args choice into `cj/--wallpaper-program-for' -- a pcase from a display-server symbol to a (PROGRAM ARG...) list, or nil for unknown environments. The wrapper now: detect env, ask helper for the command, surface the right message (unknown server / executable missing / success). Adding a third backend (e.g. xdg-desktop-portal) becomes one pcase clause + one test.
|
| |
|
|
|
|
| |
`cj/dired-copy-path-as-kill' was a 57-line procedural body with the path-resolution branching mixed into the kill-ring write and the user-visible message. Lift the four-way decision (force-absolute / project-relative / home-relative / bare absolute) into `cj/--dired-resolve-display-path', a pure function from (FILE PROJECT-ROOT HOME-DIR FORCE-ABSOLUTE) to (PATH . PATH-TYPE). The wrapper now reads as: get the dired file, ask the helper how to display it, format-or-pass-through, kill-new, message.
Seven Normal/Boundary tests cover each branch: project-relative, home-relative, the home-itself "~" glyph, absolute fallback, force-absolute beating both project and home, and project taking precedence over home when both apply.
|
| |
|
|
|
|
| |
The name-mangling logic in `cj/dirvish-duplicate-file' was inline -- inseparable from the dired side effects (existence check, copy, revert). Extract to `cj/--duplicate-file-name', a pure function from FILE to FILE-WITH-COPY-SUFFIX. Seven Normal/Boundary tests cover the cases I care about: typical extension, elisp file, no extension, multi-dot extensions (only the last dot counts), leading-dot dotfiles, relative paths, spaces in the base name.
The wrapper retains the dired-mode interactive shape and now reads as a thin shell over the pure helper.
|
| |
|
|
|
|
| |
Pressing `g' in dirvish (`dirvish-quick-access') failed with `Wrong type argument: command, (keymap (107 . transient:dirvish-quick-access::N))'. Transient auto-groups multi-character keys into nested prefix keymaps; when one entry's KEY is also the prefix of a longer entry's KEY, the shorter slot resolves to the auto-generated sub-prefix keymap rather than to the intended leaf command. The "wallpaper" entry on `pw' and the "project work" entry on `pwk' (added in 4ece1eb) collided. Rename `pw' to `wp'; `pwk' is now the only entry under the `pw'-prefix, so transient builds a clean leaf.
Add `tests/test-dirvish-config-quick-access.el' with five tests: four cover a small `test-dirvish-config--key-collisions' helper against synthetic inputs (collision detected, clean list, empty, single-entry), and one regression test asserts the live `dirvish-quick-access-entries' contains no shorter-key-is-prefix-of-longer-key pair.
|
| |
|
|
|
|
|
|
|
|
| |
The personal vterm map was on `C-; V'. The capital V costs a Shift on every keystroke into the menu, which adds up for the daily `C-; V c' / `C-; V C' bindings. Move the prefix to lowercase `C-; x' -- free, no Shift, faintly mnemonic (xterm/execute). The lowercase `C-; v' stays the version-control menu.
Wire `vterm-next-prompt' and `vterm-previous-prompt' into the menu so they're reachable everywhere, not only inside vterm-copy-mode-map. Lowercase `n' and `p' match Emacs's idiom for next/previous; bump "new vterm" up to capital `N' for the rare new-buffer case.
Drop the `<pause>' binding for `vterm-copy-mode' from `vterm-mode-map'. Modern keyboards rarely have a Pause key and `C-; x c' is the canonical entry now.
Update which-key labels and tests; `test-vterm-keymap-includes-history-and-copy-bindings' now asserts the new prefix, and two new tests cover prompt-nav bindings and the dropped `<pause>' binding.
|
| |
|
|
|
|
| |
The 3-pixel bar was visible but a block matches the rest of my Emacs cursor and lets the standard cursor color and `blink-cursor-mode' behavior carry through unchanged. Same enter/exit semantics: forced visible on entry, buffer-local override killed on exit so the live terminal goes back to the TUI's chosen state.
Update the test expectations and rename the "prior-was-box" boundary test to "prior-was-hbar" so it still proves the override does something (the prior and the override would otherwise both be `box').
|
| |
|
|
|
|
|
|
| |
vterm's C module sets `cursor-type' to nil whenever the underlying TUI sends DECTCEM (`\e[?25l') to hide the terminal cursor. Most full-screen TUIs do this on startup — Claude Code in an ai-vterm being a daily example. Once the cursor is hidden at the buffer level, vterm-copy-mode inherits that nil and the user can't see where point is when navigating to select text. Selection still works, but you're flying blind.
Add a `vterm-copy-mode-hook' that forces `cursor-type' to a 3-pixel bar on entry and kills the buffer-local override on exit. The bar shape is drawn between characters rather than by inverting one, so heavy TUI face properties don't hide it either. On exit the live terminal goes back to whatever vterm's tracking says, so the TUI's chosen cursor state resumes.
4 ERT tests cover the hook's enter/exit behavior and confirm registration on `vterm-copy-mode-hook'.
|
| |
|
|
|
|
| |
The combined module had grown to 573 lines covering two unrelated subsystems with no shared state — the eshell shell-mode commands and the vterm/F12 toggle. The header even rendered this with two `;; ----` dividers. Split into two focused modules. eshell-config.el keeps the eshell user commands and package wiring (~170 lines). vterm-config.el keeps the vterm package, the tmux history capture command, the F12 toggle, and the C-; V keymap (~400 lines). Update init.el to require both, point the four vterm test files at vterm-config, and refresh the cross-module commentary in cj-window-geometry.el and cj-window-toggle.el.
No behavior change. Full test suite green; validate-modules clean.
|
| |
|
|
|
|
| |
The F12 commit (554b32d) flagged this as a follow-up: ~120 lines of capture-state and display-saved logic were duplicated between modules/ai-vterm.el and modules/eshell-vterm-config.el. The only differences were the default direction (right for F9, below for F12) and the customization name for the fallback size. Extract the shared logic into modules/cj-window-toggle.el so both consumers reduce to thin delegates that pass their state-var symbols and defaults. The state vars stay where they were, so existing tests against each consumer's helpers keep working.
10 new tests cover the parameterized helpers in isolation. All consumer tests still pass.
|