diff options
Diffstat (limited to 'modules')
| -rw-r--r-- | modules/ai-term.el | 103 | ||||
| -rw-r--r-- | modules/calibredb-epub-config.el | 10 | ||||
| -rw-r--r-- | modules/dirvish-config.el | 16 | ||||
| -rw-r--r-- | modules/eshell-config.el | 10 | ||||
| -rw-r--r-- | modules/eww-config.el | 7 | ||||
| -rw-r--r-- | modules/music-config.el | 2 | ||||
| -rw-r--r-- | modules/org-faces-config.el | 56 | ||||
| -rw-r--r-- | modules/org-noter-config.el | 2 | ||||
| -rw-r--r-- | modules/pdf-config.el | 1 | ||||
| -rw-r--r-- | modules/prog-general.el | 11 |
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 |
