aboutsummaryrefslogtreecommitdiff
path: root/modules
diff options
context:
space:
mode:
Diffstat (limited to 'modules')
-rw-r--r--modules/ai-term.el103
-rw-r--r--modules/calibredb-epub-config.el10
-rw-r--r--modules/dirvish-config.el16
-rw-r--r--modules/eshell-config.el10
-rw-r--r--modules/eww-config.el7
-rw-r--r--modules/music-config.el2
-rw-r--r--modules/org-faces-config.el56
-rw-r--r--modules/org-noter-config.el2
-rw-r--r--modules/pdf-config.el1
-rw-r--r--modules/prog-general.el11
10 files changed, 130 insertions, 88 deletions
diff --git a/modules/ai-term.el b/modules/ai-term.el
index 04ff9f6c7..b463da90b 100644
--- a/modules/ai-term.el
+++ b/modules/ai-term.el
@@ -52,12 +52,14 @@
;; picker, even when an agent buffer is currently displayed.
;; Used when the user wants to start a new project session
;; instead of toggling the current one.
-;; - s-F9 `cj/ai-term-next' -- step to the next open agent in the
-;; queue. The queue is the live agent buffers in buffer-name
-;; order (a stable rotation). When an agent window is on
-;; screen, swap it to the next agent and focus it, wrapping
-;; after the last; when none is shown but agents exist, show
-;; the first. This is the "switch among existing agents"
+;; - s-F9 `cj/ai-term-next' -- step to the next active agent in the
+;; queue. The queue is every active agent in buffer-name order
+;; (a stable rotation): attached agents (a live buffer) and
+;; detached ones (a live tmux session with no Emacs buffer).
+;; Stepping onto a detached agent attaches it. When an agent
+;; window is on screen, swap it to the next agent and focus it,
+;; wrapping after the last; when none is shown but agents exist,
+;; show the first. This is the "switch among existing agents"
;; surface F9 deliberately doesn't provide.
;; - M-F9 `cj/ai-term-close' -- gracefully close an agent: kill its
;; tmux session (stopping the agent process), then its terminal
@@ -186,20 +188,39 @@ recently-selected first. Non-AI-term buffers are filtered out via
`cj/--ai-term-buffer-p'."
(seq-filter #'cj/--ai-term-buffer-p (buffer-list)))
-(defun cj/--ai-term-next-agent-buffer (current buffers)
- "Return the agent buffer after CURRENT in BUFFERS, wrapping to the first.
+(defun cj/--ai-term-next-agent-dir (current dirs)
+ "Return the project dir after CURRENT in DIRS, wrapping to the first.
-BUFFERS is an ordered list of live agent buffers. When CURRENT is the
-last element, wrap to the first. When CURRENT is nil or not a member of
-BUFFERS, return the first buffer. Returns nil when BUFFERS is empty.
+DIRS is an ordered list of active-agent project dirs. When CURRENT is
+the last element, wrap to the first. When CURRENT is nil or not a member
+of DIRS, return the first dir. Returns nil when DIRS is empty. Matches
+with `member' (string equality) since dirs are paths.
Pure decision helper (no buffer or window side effects) so the cycle
-order driving `cj/ai-term-next' (s-F9) is exercisable in tests."
- (when buffers
- (if (memq current buffers)
- (or (cadr (memq current buffers))
- (car buffers))
- (car buffers))))
+order driving `cj/ai-term-next' is exercisable in tests."
+ (when dirs
+ (if (member current dirs)
+ (or (cadr (member current dirs))
+ (car dirs))
+ (car dirs))))
+
+(defun cj/--ai-term-active-agent-dirs ()
+ "Return project dirs that have a live agent buffer or a live tmux session.
+
+Sorted by the agent buffer name, so the rotation is stable and matches
+what the picker shows. This is the queue `cj/ai-term-next' steps through:
+it includes detached sessions (alive in tmux but with no Emacs buffer),
+which the step materializes by attaching."
+ (let* ((sessions (cj/--ai-term-live-tmux-sessions))
+ (live-names (mapcar #'buffer-name (cj/--ai-term-agent-buffers))))
+ (sort
+ (seq-filter
+ (lambda (dir)
+ (or (member (cj/--ai-term-buffer-name dir) live-names)
+ (cj/--ai-term-session-active-p dir sessions)))
+ (cj/--ai-term-candidates))
+ (lambda (a b)
+ (string< (cj/--ai-term-buffer-name a) (cj/--ai-term-buffer-name b))))))
(defun cj/--ai-term-most-recent-non-agent-buffer ()
"Return the most-recently-selected live non-agent buffer, or nil.
@@ -988,35 +1009,43 @@ interrupt work in progress. Bound to M-<f9>."
(defun cj/ai-term-next ()
"Step to the next open AI-term agent in the queue.
-The queue is the live agent buffers ordered by buffer name -- a stable
-rotation, unaffected by which agent was most recently selected. When an
-agent window is on screen, swap it to the next agent in the queue
-\(wrapping after the last) and select it. When no agent is displayed but
-agents exist, show the first. When none are open, open the project picker
-to launch the first agent rather than erroring.
+The queue is every active agent ordered by buffer name -- a stable
+rotation, unaffected by which agent was most recently selected. Active
+means a live agent buffer (attached) OR a live tmux session with no Emacs
+buffer (detached); stepping onto a detached agent attaches it (recreates
+its terminal, which reattaches the session). When an agent window is on
+screen, swap it to the next agent (wrapping after the last) and select it.
+When no agent is displayed but agents exist, show the first. When none
+are open, open the project picker to launch the first agent rather than
+erroring.
Bound to M-SPC. Unlike C-; a a (toggle the most-recent agent on/off), this
is the \"switch among existing agents\" surface; C-; a s opens the project
picker and C-; a k closes an agent."
(interactive)
- (let* ((buffers (sort (cj/--ai-term-agent-buffers)
- (lambda (a b)
- (string< (buffer-name a) (buffer-name b)))))
+ (let* ((dirs (cj/--ai-term-active-agent-dirs))
(win (cj/--ai-term-displayed-agent-window))
- (current (and win (window-buffer win)))
- (next (cj/--ai-term-next-agent-buffer current buffers)))
- (if (not next)
+ (current-name (and win (buffer-name (window-buffer win))))
+ (current-dir (and current-name
+ (seq-find (lambda (d)
+ (equal (cj/--ai-term-buffer-name d) current-name))
+ dirs)))
+ (next-dir (cj/--ai-term-next-agent-dir current-dir dirs)))
+ (if (not next-dir)
;; No agents open: launch the first via the project picker instead of
;; erroring, so the swap key doubles as a "start an agent" key.
(cj/ai-term-pick-project)
- (if win
- (progn
- (set-window-buffer win next)
- (select-window win))
- (display-buffer next)
- (let ((w (get-buffer-window next)))
- (when w (select-window w))))
- (message "Agent: %s" (buffer-name next)))))
+ (let* ((name (cj/--ai-term-buffer-name next-dir))
+ (existing (get-buffer name)))
+ ;; Live agent and an agent window is up: swap it into that window in
+ ;; place (faithful to the prior buffer-only behavior). Detached, or no
+ ;; window yet: show-or-create attaches the tmux session / displays it.
+ (if (and win existing (cj/--ai-term-process-live-p existing))
+ (progn (set-window-buffer win existing) (select-window win))
+ (cj/--ai-term-show-or-create next-dir name)
+ (let ((w (get-buffer-window name)))
+ (when w (select-window w))))
+ (message "Agent: %s" name)))))
;; ai-term lives under the C-; a prefix (vacated when gptel was archived).
;; The frequent "swap to the next agent" also gets M-SPC for a fast chord.
diff --git a/modules/calibredb-epub-config.el b/modules/calibredb-epub-config.el
index 6c69ca0e8..1e6437d26 100644
--- a/modules/calibredb-epub-config.el
+++ b/modules/calibredb-epub-config.el
@@ -313,11 +313,11 @@ A positive DELTA narrows the text column; a negative DELTA widens it."
"Apply preferences after nov-mode has launched."
(interactive)
;; Use Merriweather for comfortable reading with appropriate scaling.
- ;; Darker sepia color (#E8DCC0) is easier on the eyes than pure white.
- (let ((sepia "#E8DCC0"))
- (face-remap-add-relative 'variable-pitch :family "Merriweather" :height 1.0 :foreground sepia)
- (face-remap-add-relative 'default :family "Merriweather" :height 180 :foreground sepia)
- (face-remap-add-relative 'fixed-pitch :height 180 :foreground sepia))
+ ;; (Reading fg color stripped; falls back to the theme default until a
+ ;; themeable reading face exists -- see todo.org.)
+ (face-remap-add-relative 'variable-pitch :family "Merriweather" :height 1.0)
+ (face-remap-add-relative 'default :family "Merriweather" :height 180)
+ (face-remap-add-relative 'fixed-pitch :height 180)
;; Enable visual-line-mode for proper text wrapping
(visual-line-mode 1)
;; Set fill-column as a fallback
diff --git a/modules/dirvish-config.el b/modules/dirvish-config.el
index c4c5f1aae..81d352dbd 100644
--- a/modules/dirvish-config.el
+++ b/modules/dirvish-config.el
@@ -486,6 +486,22 @@ leaves an empty frame behind."
(when (frame-live-p popup) (delete-frame popup)))
(dirvish-quit))))
+(defun cj/--dirvish-popup-reap-on-delete (frame)
+ "Quit the Dirvish session when the Super+F popup FRAME is closed any way.
+`q' runs `cj/dirvish-popup-quit', but closing the Hyprland float directly (or
+letting it lose focus) bypasses that and orphans the session's dired buffers --
+the \"leaves a load of buffers around\" symptom. As a `delete-frame-functions'
+hook this fires on every close path; `dirvish-quit' reaps the session's buffers
+(verified: a navigated session drops back to baseline on quit). Scoped to the
+popup frame so ordinary `C-x d' sessions -- where multiple dired buffers are
+wanted for mark-and-move -- are untouched."
+ (when (and (frame-live-p frame)
+ (equal (frame-parameter frame 'name) "dirvish"))
+ (with-selected-frame frame
+ (ignore-errors (dirvish-quit)))))
+
+(add-hook 'delete-frame-functions #'cj/--dirvish-popup-reap-on-delete)
+
(defun cj/--dirvish-popup-selected-p ()
"Return non-nil when the selected frame is the dirvish popup frame."
(let ((popup (cj/--dirvish-popup-frame)))
diff --git a/modules/eshell-config.el b/modules/eshell-config.el
index 723a7e61e..c2ec6d152 100644
--- a/modules/eshell-config.el
+++ b/modules/eshell-config.el
@@ -101,15 +101,15 @@ pairs where COMMAND is the `cd' string `eshell/alias' should run."
(setq eshell-prompt-function
(lambda ()
(concat
- (propertize (format-time-string "[%d-%m-%y %T]") 'face '(:foreground "gray"))
+ (propertize (format-time-string "[%d-%m-%y %T]") 'face 'default)
" "
- (propertize (user-login-name) 'face '(:foreground "gray"))
+ (propertize (user-login-name) 'face 'default)
" "
- (propertize (system-name) 'face '(:foreground "gray"))
+ (propertize (system-name) 'face 'default)
":"
- (propertize (abbreviate-file-name (eshell/pwd)) 'face '(:foreground "gray"))
+ (propertize (abbreviate-file-name (eshell/pwd)) 'face 'default)
"\n"
- (propertize "%" 'face '(:foreground "white"))
+ (propertize "%" 'face 'default)
" ")))
(add-hook
diff --git a/modules/eww-config.el b/modules/eww-config.el
index a5271f6bc..ff7ddc211 100644
--- a/modules/eww-config.el
+++ b/modules/eww-config.el
@@ -44,6 +44,13 @@
:type 'string
:group 'my-eww-user-agent)
+;; This file is lexical-binding, so `let'-binding url.el's special var below
+;; needs it declared special at compile time. Without this the byte-compiled
+;; advice binds `url-request-extra-headers' lexically and the injected
+;; User-Agent never reaches `url-retrieve' (it reads the dynamic value) -- the
+;; UA injection silently no-ops in compiled production, and the test sees nil.
+(defvar url-request-extra-headers)
+
(defun my-eww--inject-user-agent (orig-fun &rest args)
"Set a User-Agent only when making requests from an EWW buffer."
(if (derived-mode-p 'eww-mode)
diff --git a/modules/music-config.el b/modules/music-config.el
index 0874c4982..76fff283b 100644
--- a/modules/music-config.el
+++ b/modules/music-config.el
@@ -824,7 +824,7 @@ For URL tracks: decoded URL."
(cond
((and active (not cj/music--bg-remap-cookie))
(setq cj/music--bg-remap-cookie
- (face-remap-add-relative 'default :background "#1d1b19")))
+ (face-remap-add-relative 'default)))
((and (not active) cj/music--bg-remap-cookie)
(face-remap-remove-relative cj/music--bg-remap-cookie)
(setq cj/music--bg-remap-cookie nil)))))))
diff --git a/modules/org-faces-config.el b/modules/org-faces-config.el
index e0dfa83fd..dfbfe9d0d 100644
--- a/modules/org-faces-config.el
+++ b/modules/org-faces-config.el
@@ -35,72 +35,72 @@
;; --------------------------- Keyword faces (focused) -------------------------
-(defface org-faces-todo '((t (:foreground "#8fbf73" :weight bold)))
+(defface org-faces-todo '((t (:weight bold)))
"Face for the TODO keyword." :group 'org-faces-config)
-(defface org-faces-project '((t (:foreground "#7a9abe" :weight bold)))
+(defface org-faces-project '((t (:weight bold)))
"Face for the PROJECT keyword." :group 'org-faces-config)
-(defface org-faces-doing '((t (:foreground "#e8c668" :weight bold)))
+(defface org-faces-doing '((t (:weight bold)))
"Face for the DOING keyword." :group 'org-faces-config)
-(defface org-faces-waiting '((t (:foreground "#c9b08a" :weight bold)))
+(defface org-faces-waiting '((t (:weight bold)))
"Face for the WAITING keyword." :group 'org-faces-config)
-(defface org-faces-verify '((t (:foreground "#d98a5a" :weight bold)))
+(defface org-faces-verify '((t (:weight bold)))
"Face for the VERIFY keyword." :group 'org-faces-config)
-(defface org-faces-stalled '((t (:foreground "#9a8fb0" :weight bold)))
+(defface org-faces-stalled '((t (:weight bold)))
"Face for the STALLED keyword." :group 'org-faces-config)
-(defface org-faces-delegated '((t (:foreground "#7fc0a8" :weight bold)))
+(defface org-faces-delegated '((t (:weight bold)))
"Face for the DELEGATED keyword." :group 'org-faces-config)
-(defface org-faces-failed '((t (:foreground "#d05a5a" :weight bold)))
+(defface org-faces-failed '((t (:weight bold)))
"Face for the FAILED keyword." :group 'org-faces-config)
-(defface org-faces-done '((t (:foreground "#6f7a82" :weight bold)))
+(defface org-faces-done '((t (:weight bold)))
"Face for the DONE keyword." :group 'org-faces-config)
-(defface org-faces-cancelled '((t (:foreground "#6f7a82" :weight bold :strike-through t)))
+(defface org-faces-cancelled '((t (:weight bold :strike-through t)))
"Face for the CANCELLED keyword." :group 'org-faces-config)
;; -------------------------- Priority faces (focused) -------------------------
-(defface org-faces-priority-a '((t (:foreground "#7aa0d0" :weight bold)))
+(defface org-faces-priority-a '((t (:weight bold)))
"Face for the [#A] priority cookie." :group 'org-faces-config)
-(defface org-faces-priority-b '((t (:foreground "#e8c668")))
+(defface org-faces-priority-b '((t ()))
"Face for the [#B] priority cookie." :group 'org-faces-config)
-(defface org-faces-priority-c '((t (:foreground "#8fbf73")))
+(defface org-faces-priority-c '((t ()))
"Face for the [#C] priority cookie." :group 'org-faces-config)
-(defface org-faces-priority-d '((t (:foreground "#8a8a8a")))
+(defface org-faces-priority-d '((t ()))
"Face for the [#D] priority cookie." :group 'org-faces-config)
;; ----------------------------- Keyword faces (dim) ---------------------------
;; auto-dim-config.el remaps the focused faces above to these in non-selected
;; windows; a darker shade of the same hue keeps the keyword recognizable.
-(defface org-faces-todo-dim '((t (:foreground "#5f7a4d" :weight bold)))
+(defface org-faces-todo-dim '((t (:weight bold)))
"Dimmed TODO keyword for non-selected windows." :group 'org-faces-config)
-(defface org-faces-project-dim '((t (:foreground "#4f6680" :weight bold)))
+(defface org-faces-project-dim '((t (:weight bold)))
"Dimmed PROJECT keyword for non-selected windows." :group 'org-faces-config)
-(defface org-faces-doing-dim '((t (:foreground "#9a8544" :weight bold)))
+(defface org-faces-doing-dim '((t (:weight bold)))
"Dimmed DOING keyword for non-selected windows." :group 'org-faces-config)
-(defface org-faces-waiting-dim '((t (:foreground "#87745c" :weight bold)))
+(defface org-faces-waiting-dim '((t (:weight bold)))
"Dimmed WAITING keyword for non-selected windows." :group 'org-faces-config)
-(defface org-faces-verify-dim '((t (:foreground "#8f5a3c" :weight bold)))
+(defface org-faces-verify-dim '((t (:weight bold)))
"Dimmed VERIFY keyword for non-selected windows." :group 'org-faces-config)
-(defface org-faces-stalled-dim '((t (:foreground "#665e75" :weight bold)))
+(defface org-faces-stalled-dim '((t (:weight bold)))
"Dimmed STALLED keyword for non-selected windows." :group 'org-faces-config)
-(defface org-faces-delegated-dim '((t (:foreground "#547d6c" :weight bold)))
+(defface org-faces-delegated-dim '((t (:weight bold)))
"Dimmed DELEGATED keyword for non-selected windows." :group 'org-faces-config)
-(defface org-faces-failed-dim '((t (:foreground "#8a3c3c" :weight bold)))
+(defface org-faces-failed-dim '((t (:weight bold)))
"Dimmed FAILED keyword for non-selected windows." :group 'org-faces-config)
-(defface org-faces-done-dim '((t (:foreground "#4a5158" :weight bold)))
+(defface org-faces-done-dim '((t (:weight bold)))
"Dimmed DONE keyword for non-selected windows." :group 'org-faces-config)
-(defface org-faces-cancelled-dim '((t (:foreground "#4a5158" :weight bold :strike-through t)))
+(defface org-faces-cancelled-dim '((t (:weight bold :strike-through t)))
"Dimmed CANCELLED keyword for non-selected windows." :group 'org-faces-config)
;; ---------------------------- Priority faces (dim) ---------------------------
-(defface org-faces-priority-a-dim '((t (:foreground "#4f6a8a" :weight bold)))
+(defface org-faces-priority-a-dim '((t (:weight bold)))
"Dimmed [#A] priority cookie for non-selected windows." :group 'org-faces-config)
-(defface org-faces-priority-b-dim '((t (:foreground "#9a8544")))
+(defface org-faces-priority-b-dim '((t ()))
"Dimmed [#B] priority cookie for non-selected windows." :group 'org-faces-config)
-(defface org-faces-priority-c-dim '((t (:foreground "#5f7a4d")))
+(defface org-faces-priority-c-dim '((t ()))
"Dimmed [#C] priority cookie for non-selected windows." :group 'org-faces-config)
-(defface org-faces-priority-d-dim '((t (:foreground "#5a5a5a")))
+(defface org-faces-priority-d-dim '((t ()))
"Dimmed [#D] priority cookie for non-selected windows." :group 'org-faces-config)
;; ---------------------------------- Wiring -----------------------------------
diff --git a/modules/org-noter-config.el b/modules/org-noter-config.el
index b9b7bbff2..f28f61bb7 100644
--- a/modules/org-noter-config.el
+++ b/modules/org-noter-config.el
@@ -307,7 +307,7 @@ From a PDF/EPUB: starts org-noter session if inactive, then inserts note."
(cond
((and active (not cj/org-noter--bg-remap-cookie))
(setq cj/org-noter--bg-remap-cookie
- (face-remap-add-relative 'default :background "#1d1b19")))
+ (face-remap-add-relative 'default)))
((and (not active) cj/org-noter--bg-remap-cookie)
(face-remap-remove-relative cj/org-noter--bg-remap-cookie)
(setq cj/org-noter--bg-remap-cookie nil))))))))
diff --git a/modules/pdf-config.el b/modules/pdf-config.el
index 233a610d5..56b397df3 100644
--- a/modules/pdf-config.el
+++ b/modules/pdf-config.el
@@ -40,7 +40,6 @@
:custom
(pdf-view-display-size 'fit-page)
(pdf-view-resize-factor 1.1)
- (pdf-view-midnight-colors '("#F1D5AC" . "#0F0E06")) ;; fg . bg
;; Avoid searching for unicodes to speed up pdf-tools.
;; ... and yes, 'ligther' is not a typo
(pdf-view-use-unicode-ligther nil)
diff --git a/modules/prog-general.el b/modules/prog-general.el
index 8e317413c..f22f89923 100644
--- a/modules/prog-general.el
+++ b/modules/prog-general.el
@@ -384,16 +384,7 @@ defer to `electric-pair-default-inhibit' for any other CHAR."
(use-package hl-todo
:defer 1
:hook
- (prog-mode . hl-todo-mode)
- :config
- (setq hl-todo-keyword-faces
- '(("FIXME" . "#FF0000")
- ("BUG" . "#FF0000")
- ("HACK" . "#FF0000")
- ("ISSUE" . "#DAA520")
- ("TASK" . "#DAA520")
- ("NOTE" . "#2C780E")
- ("WIP" . "#1E90FF"))))
+ (prog-mode . hl-todo-mode))
;; --------------------------- Whitespace Management ---------------------------
;; trims trailing whitespace only from lines you've modified when saving buffer