diff options
| -rw-r--r-- | modules/mu4e-attachments.el | 22 | ||||
| -rw-r--r-- | tests/test-mu4e-attachments.el | 51 |
2 files changed, 70 insertions, 3 deletions
diff --git a/modules/mu4e-attachments.el b/modules/mu4e-attachments.el index 4acdfd6a..6c2be6fb 100644 --- a/modules/mu4e-attachments.el +++ b/modules/mu4e-attachments.el @@ -15,6 +15,7 @@ ;;; Code: (require 'seq) +(require 'system-lib) ;; cj/completion-table-annotated (defvar mu4e-uniquify-save-file-name-function) (defvar-local cj/mu4e-attachment-selection-directory nil @@ -67,6 +68,19 @@ The result is an alist of display labels to MIME part plists." (cons (cj/mu4e--attachment-label part duplicates) part)) parts))) +(defun cj/mu4e--attachment-annotator (candidates) + "Return an annotation function over attachment CANDIDATES. +CANDIDATES is the label->part alist from `cj/mu4e--attachment-candidates'. +The annotation shows the part's MIME type and human-readable decoded +size; an unknown candidate annotates as nil so marginalia shows nothing." + (lambda (cand) + (when-let* ((part (cdr (assoc cand candidates)))) + (let ((mime (or (plist-get part :mime-type) "")) + (size (if-let* ((bytes (plist-get part :decoded-size-approx))) + (file-size-human-readable bytes) + ""))) + (format " %-24s %s" mime size))))) + (defun cj/mu4e--attachment-default-directory (parts) "Return a sensible default save directory for attachment PARTS." (file-name-as-directory @@ -126,7 +140,13 @@ The result is an alist of display labels to MIME part plists." (user-error "No attachments for this message")) (let* ((directory (cj/mu4e--read-attachment-directory parts)) (candidates (cj/mu4e--attachment-candidates parts)) - (choice (completing-read "Save attachment: " candidates nil t)) + (choice (completing-read + "Save attachment: " + (cj/completion-table-annotated + 'mu4e-attachment + (cj/mu4e--attachment-annotator candidates) + candidates) + nil t)) (part (cdr (assoc choice candidates))) (path (cj/mu4e--save-attachment-part part directory))) (message "Saved attachment to %s" path) diff --git a/tests/test-mu4e-attachments.el b/tests/test-mu4e-attachments.el index 21336f75..0a780977 100644 --- a/tests/test-mu4e-attachments.el +++ b/tests/test-mu4e-attachments.el @@ -108,8 +108,10 @@ so this fails the same way whether or not mu4e's MIME support is loadable." ((symbol-function 'read-directory-name) (lambda (&rest _) "/downloads/")) ((symbol-function 'completing-read) - (lambda (_prompt candidates &rest _) - (should (equal (mapcar #'car candidates) + ;; the collection is an annotated function table now, so + ;; query it instead of car-mapping an alist + (lambda (_prompt collection &rest _) + (should (equal (all-completions "" collection) '("a.pdf" "b.pdf"))) "b.pdf")) ((symbol-function 'cj/mu4e--save-attachment-part) @@ -252,5 +254,50 @@ so this fails the same way whether or not mu4e's MIME support is loadable." :type 'user-error)) (kill-buffer buffer)))) +;; ------------------------- Picker Annotations -------------------------------- + +(ert-deftest test-mu4e-attachments-annotator-shows-mime-and-size () + "Normal: the annotator yields MIME type and human-readable size." + (let* ((part (plist-put (test-mu4e-attachments--part "invoice.pdf" 1) + :decoded-size-approx 2048)) + (candidates (list (cons "invoice.pdf" part))) + (annotate (cj/mu4e--attachment-annotator candidates)) + (suffix (funcall annotate "invoice.pdf"))) + (should (stringp suffix)) + (should (string-match-p "application/pdf" suffix)) + (should (string-match-p "2k" suffix)))) + +(ert-deftest test-mu4e-attachments-annotator-no-size () + "Boundary: a part without a decoded size still annotates the MIME type." + (let* ((candidates (list (cons "invoice.pdf" + (test-mu4e-attachments--part "invoice.pdf" 1)))) + (annotate (cj/mu4e--attachment-annotator candidates)) + (suffix (funcall annotate "invoice.pdf"))) + (should (stringp suffix)) + (should (string-match-p "application/pdf" suffix)))) + +(ert-deftest test-mu4e-attachments-annotator-unknown-candidate-nil () + "Error: an unknown candidate annotates as nil (marginalia shows nothing)." + (let ((annotate (cj/mu4e--attachment-annotator nil))) + (should-not (funcall annotate "nope.txt")))) + +(ert-deftest test-mu4e-attachments-picker-uses-annotated-category () + "Normal: the save-here picker's collection carries category + annotator." + (let ((captured-collection nil)) + (cl-letf (((symbol-function 'cj/mu4e--attachment-parts) + (lambda (&rest _) (list (test-mu4e-attachments--part "a.pdf" 1)))) + ((symbol-function 'cj/mu4e--read-attachment-directory) + (lambda (&rest _) "/tmp/x/")) + ((symbol-function 'cj/mu4e--save-attachment-part) + (lambda (&rest _) "/tmp/x/a.pdf")) + ((symbol-function 'completing-read) + (lambda (_prompt collection &rest _) + (setq captured-collection collection) + "a.pdf"))) + (cj/mu4e-save-attachment-here) + (let ((md (funcall captured-collection "" nil 'metadata))) + (should (eq (alist-get 'category (cdr md)) 'mu4e-attachment)) + (should (functionp (alist-get 'annotation-function (cdr md)))))))) + (provide 'test-mu4e-attachments) ;;; test-mu4e-attachments.el ends here |
