summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--modules/custom-buffer-file.el23
-rw-r--r--tests/test-custom-buffer-file-copy-buffer-source.el54
-rw-r--r--todo.org4
3 files changed, 79 insertions, 2 deletions
diff --git a/modules/custom-buffer-file.el b/modules/custom-buffer-file.el
index 8c6014d3..d8ce6bee 100644
--- a/modules/custom-buffer-file.el
+++ b/modules/custom-buffer-file.el
@@ -218,7 +218,28 @@ When called interactively, prompts for confirmation if target file exists."
'((eww-mode . (lambda () (eww-current-url)))
(elfeed-show-mode . (lambda () (elfeed-entry-link elfeed-show-entry)))
(dired-mode . (lambda () (dired-get-filename nil t)))
- (dirvish-mode . (lambda () (dired-get-filename nil t))))
+ (dirvish-mode . (lambda () (dired-get-filename nil t)))
+ (mu4e-view-mode . (lambda ()
+ (when-let* ((msg (mu4e-message-at-point))
+ (id (plist-get msg :message-id)))
+ (format "mu4e:msgid:%s" id))))
+ (Info-mode . (lambda ()
+ (when (and (boundp 'Info-current-file)
+ (boundp 'Info-current-node)
+ Info-current-file
+ Info-current-node)
+ ;; Strip the compression suffix (via
+ ;; file-name-base) AND the .info suffix.
+ ;; "emacs.info.gz" -> base "emacs.info" ->
+ ;; manual "emacs".
+ (let* ((base (file-name-base Info-current-file))
+ (manual (if (string-suffix-p ".info" base)
+ (substring base 0 -5)
+ base))
+ (node Info-current-node))
+ (when (and (not (string-empty-p manual))
+ (not (string-empty-p node)))
+ (format "info:(%s)%s" manual node)))))))
"Alist mapping major-mode -> thunk returning the buffer's \"source\".
Each thunk is called with no arguments and should return a string
diff --git a/tests/test-custom-buffer-file-copy-buffer-source.el b/tests/test-custom-buffer-file-copy-buffer-source.el
index c4e073ae..f4afd109 100644
--- a/tests/test-custom-buffer-file-copy-buffer-source.el
+++ b/tests/test-custom-buffer-file-copy-buffer-source.el
@@ -125,6 +125,60 @@ to `buffer-file-name'."
(cj/copy-buffer-source-as-kill))
(should (equal (car kill-ring) "/home/u/books/manual.pdf")))))
+;;; mu4e-view-mode dispatch
+
+(ert-deftest test-copy-buffer-source-mu4e-view-copies-msgid-link ()
+ "Normal: in mu4e-view-mode, copy a `mu4e:msgid:<id>' link form.
+The URL-shaped string pastes into org as a clickable link and
+matches mu4e's own org-protocol handler."
+ (let (kill-ring)
+ (cl-letf (((symbol-function 'mu4e-message-at-point)
+ (lambda () (list :message-id "abc123@example.test"
+ :subject "Re: lunch"))))
+ (with-temp-buffer
+ (setq major-mode 'mu4e-view-mode)
+ (cl-letf (((symbol-function 'message) #'ignore))
+ (cj/copy-buffer-source-as-kill))
+ (should (equal (car kill-ring) "mu4e:msgid:abc123@example.test"))))))
+
+(ert-deftest test-copy-buffer-source-mu4e-view-without-message-falls-through ()
+ "Boundary: when `mu4e-message-at-point' returns nil (e.g. point
+isn't on a real message), the dispatcher falls back to
+`buffer-file-name' rather than erroring."
+ (let (kill-ring)
+ (cl-letf (((symbol-function 'mu4e-message-at-point) (lambda () nil)))
+ (with-temp-buffer
+ (setq major-mode 'mu4e-view-mode
+ buffer-file-name "/tmp/fallback.eml")
+ (cl-letf (((symbol-function 'message) #'ignore))
+ (cj/copy-buffer-source-as-kill))
+ (should (equal (car kill-ring) "/tmp/fallback.eml"))))))
+
+;;; Info-mode dispatch
+
+(ert-deftest test-copy-buffer-source-info-mode-formats-as-org-info-link ()
+ "Normal: in Info-mode, return `info:(manual)node' -- the form
+`org-info-store-link' produces, which org renders as a clickable
+link target."
+ (let (kill-ring)
+ (with-temp-buffer
+ (setq major-mode 'Info-mode)
+ (setq-local Info-current-file "/usr/share/info/emacs.info.gz")
+ (setq-local Info-current-node "Buffers")
+ (cl-letf (((symbol-function 'message) #'ignore))
+ (cj/copy-buffer-source-as-kill))
+ (should (equal (car kill-ring) "info:(emacs)Buffers")))))
+
+(ert-deftest test-copy-buffer-source-info-mode-without-context-falls-through ()
+ "Boundary: when Info hasn't populated `Info-current-file' or
+`Info-current-node' (uninitialized), the dispatcher falls through
+to `buffer-file-name' (here nil → user-error)."
+ (with-temp-buffer
+ (setq major-mode 'Info-mode)
+ (setq-local Info-current-file nil)
+ (setq-local Info-current-node nil)
+ (should-error (cj/copy-buffer-source-as-kill) :type 'user-error)))
+
;;; Backwards-compat alias
(ert-deftest test-copy-path-old-name-aliases-new-command ()
diff --git a/todo.org b/todo.org
index da6eeb1f..25c93cb8 100644
--- a/todo.org
+++ b/todo.org
@@ -39,6 +39,7 @@ Tags are additive. For example, a small wrong-behavior fix can be
* Emacs Open Work
+** TODO [#B] Write spec on what's needed for music not to depend on EMMS
** DONE [#B] Update gptel models :chore:
CLOSED: [2026-05-14 Thu]
Anthropic side: bumped Opus 4.6 → 4.7 (current frontier); Sonnet 4.6
@@ -106,7 +107,8 @@ heading, =help-mode=, =Info-mode=, =magit-log-mode= /
These need format decisions (Message-ID vs link vs subject, id link
vs CUSTOM_ID vs heading text, etc.) before implementation.
-** TODO [#C] Extend cj/buffer-source-functions to more modes :feature:
+** DONE [#C] Extend cj/buffer-source-functions to more modes :feature:
+CLOSED: [2026-05-15 Fri]
Followup to =Modify C-; b p=. The first batch covered eww,
elfeed-show, dired/dirvish, and doc-view/pdf-view (via the
buffer-file-name fallback). These modes still need a decision +