From 783226a55e6e5939d76bb5c400d47d29e1fb1431 Mon Sep 17 00:00:00 2001 From: Armin Darvish Date: Mon, 17 Feb 2025 14:41:30 -0800 Subject: [PATCH] Clean up the code - Fixes the issue with getting headers (better implementation of #46) - Applies the changes from #47 plus some more clean up --- consult-mu-embark.el | 28 +- consult-mu.el | 926 ++++++++++-------- consult-mu.org | 1353 +++++++++++++++----------- extras/consult-mu-compose-embark.el | 16 +- extras/consult-mu-compose.el | 51 +- extras/consult-mu-contacts-embark.el | 33 +- extras/consult-mu-contacts.el | 233 +++-- 7 files changed, 1563 insertions(+), 1077 deletions(-) diff --git a/consult-mu-embark.el b/consult-mu-embark.el index 71a320b..dce4616 100644 --- a/consult-mu-embark.el +++ b/consult-mu-embark.el @@ -51,7 +51,7 @@ ;;; Define Embark Action Functions (defun consult-mu-embark-default-action (cand) - "Run `consult-mu-action' on the candidate." + "Run `consult-mu-action' on the candidate, CAND." (let* ((msg (get-text-property 0 :msg cand)) (query (get-text-property 0 :query cand)) (type (get-text-property 0 :type cand)) @@ -94,7 +94,6 @@ (let* ((msg (get-text-property 0 :msg cand)) (query (get-text-property 0 :query cand)) (type (get-text-property 0 :type cand)) - (newcand (cons cand `(:msg ,msg :query ,query :type ,type))) (msg-id (plist-get msg :message-id))) (if (equal type :async) (consult-mu--update-headers query t msg :async)) @@ -107,14 +106,13 @@ (with-current-buffer consult-mu-view-buffer-name (kill-new (consult-mu--message-get-header-field)) - (consult-mu--pulse-region (point) (point-at-eol))))) + (consult-mu--pulse-region (point) (line-end-position))))) (defun consult-mu-embark-save-attachmnts (cand) "Save attachments of CAND." (let* ((msg (get-text-property 0 :msg cand)) (query (get-text-property 0 :query cand)) (type (get-text-property 0 :type cand)) - (newcand (cons cand `(:msg ,msg :query ,query :type ,type))) (msg-id (plist-get msg :message-id))) (if (equal type :async) @@ -130,7 +128,7 @@ (with-current-buffer consult-mu-view-buffer-name (goto-char (point-min)) (re-search-forward "^\\(Attachment\\|Attachments\\): " nil t) - (consult-mu--pulse-region (point) (point-at-eol)) + (consult-mu--pulse-region (point) (line-end-position)) (mu4e-view-save-attachments t)))) (defun consult-mu-embark-search-messages-from-contact (cand) @@ -184,9 +182,12 @@ ;; add embark functions for marks (defun consult-mu-embark--defun-func-for-marks (marks) - "Runs the macro `consult-mu-embark--defun-mark-for' on a list of marks. + "Run the macro `consult-mu-embark--defun-mark-for' on MARKS. -This is useful for creating embark functions for all the `mu4e-marks' elements." +MARKS is a list of marks. + +This is useful for creating embark functions for all the `mu4e-marks' +elements." (mapcar (lambda (mark) (eval `(consult-mu-embark--defun-mark-for ,mark))) marks)) ;; use consult-mu-embark--defun-func-for-marks to make a function for each `mu4e-marks' element. @@ -216,11 +217,16 @@ This is useful for creating embark functions for all the `mu4e-marks' elements." ;; add mark keys to `consult-mu-embark-messages-actions-map' keymap (defun consult-mu-embark--add-keys-for-marks (marks) - "Adds a key for each mark in MARKS to `consult-mu-embark-messages-actions-map'. + "Add a key for each mark in MARKS to embark map. -Binds the combination “m key”, where key is the :char in mark plist in the `consult-mu-embark-messages-actions-map' to the function defined by the prefix “consult-mu-embark-mark-for-” and mark. +Adds the keys in `consult-mu-embark-messages-actions-map', and binds the +combination “m key”, where key is the :char in mark plist in the +`consult-mu-embark-messages-actions-map' to the function defined by the +prefix “consult-mu-embark-mark-for-” and mark. -This is useful for adding all `mu4e-marks' to embark key bindings under a submenu (called by “m”) ,for example the default mark-for-archive mark that is bound to r in mu4e buffers can be called in embark by “m r”." +This is useful for adding all `mu4e-marks' to embark key bindings under a +submenu (called by “m”), for example, the default mark-for-archive mark +that is bound to r in mu4e buffers can be called in embark by “m r”." (mapcar (lambda (mark) (let* ((key (plist-get (cdr mark) :char)) (key (cond ((consp key) (car key)) ((stringp key) key))) @@ -240,4 +246,4 @@ This is useful for adding all `mu4e-marks' to embark key bindings under a submen (provide 'consult-mu-embark) -;;; consult-mu-embark.el ends here +;;; consult-mu-embark.el ends here diff --git a/consult-mu.el b/consult-mu.el index 9d1b502..e4235a3 100644 --- a/consult-mu.el +++ b/consult-mu.el @@ -1,4 +1,4 @@ -;;; consult-mu.el --- Consult Mu4e asynchronously in GNU Emacs -*- lexical-binding: t -*- +;;; consult-mu.el --- Consult Mu4e asynchronously -*- lexical-binding: t -*- ;; Copyright (C) 2023 Armin Darvish @@ -56,21 +56,25 @@ (defcustom consult-mu-args '("mu") "Command line arguments to call `mu` asynchronously. + The dynamically computed arguments are appended. Can be either a string, or a list of strings or expressions." :group 'consult-mu :type '(choice string (repeat (choice string sexp)))) (defcustom consult-mu-maxnum mu4e-search-results-limit - "Maximum number of results + "Maximum number of results. -This is normally passed to \"--maxnum\" in the command line or is defined by `mu4e-search-results-limit'. By default inherits from `mu4e-search-results-limit'. " +This is normally passed to “--maxnum” in the command line or is defined by +`mu4e-search-results-limit'. By default inherits from +`mu4e-search-results-limit'." :group 'consult-mu :type '(choice (const :tag "Unlimited" -1) (integer :tag "Limit"))) (defcustom consult-mu-search-sort-field mu4e-search-sort-field "What field to sort results by? + By defualt inherits from `mu4e-search-sort-field'." :group 'consult-mu :type '(radio (const :tag "Date" :date) @@ -92,9 +96,9 @@ Each element has the form (HEADER . WIDTH), where HEADER is one of the available headers (see `mu4e-header-info') and WIDTH is the respective width in characters. -A width of nil means \"unrestricted\", and this is best reserved -for the rightmost (last) field. Note that emacs may become very -slow with excessively long lines (1000s of characters), so if you +A width of nil means “unrestricted”, and this is best reserved +for the rightmost \(last\) field. Note that Emacs may become very +slow with excessively long lines \(1000s of characters\), so if you regularly get such messages, you want to avoid fields with nil altogether." :group 'consult-mu @@ -109,31 +113,36 @@ altogether." (defcustom consult-mu-headers-template nil "A template string to make custom header formats. -If non-nil, consult-mu uses this string to format the headers instead of `consult-mu-headers-field'. +If non-nil, `consult-mu' uses this string to format the headers instead of +`consult-mu-headers-field'. -The string should be of the format “%[char][integer]%[char][integer]...”, and allow dynamic insertion of the content. Each “%[char][integer]“ chunk represents a different field and the integer defines the length of the field. for exmaple \"%d15%s50\" means 15 characters for date and 50 charcters for subject. +The string should be of the format “%[char][integer]%[char][integer]...”, +and allow dynamic insertion of the content. Each “%[char][integer]“ chunk +represents a different field and the integer defines the length of the +field. The list of available fields are: - %f sender(s) (e.g. from: field of email) - %t receivers(s) (i.e. to: field of email) - %s subject (i.e. title of email) - %d date (i.e. the date email was sent/received) + %f sender(s) \(e.g. from: field of email\) + %t receivers(s) \(i.e. to: field of email\) + %s subject \(i.e. title of email\) + %d date \(i.e. the date email was sent/received\) %p priority %z size - %i message-id (as defined by mu) - %g flags (as defined by mu) - %G pretty flags (this uses `mu4e~headers-flags-str' to pretify flags) - %x tags (as defined by mu) - %c cc (i.e. cc: field of the email) - %h bcc (i.e. bcc: field of the email) - %r date chaged (as defined by :changed in mu4e) - -For example, the string \"%d13%s50%f17\" would make a header containing 13 characters for Date, 50 characters for Subject, and 20 characters for From field, making a header that looks like this: - -Thu 09 Nov 23 Title of the Email Limited to 50 Characters Onl... example@domain... - -" + %i message-id \(as defined by mu\) + %g flags \(as defined by mu\) + %G pretty flags \(this uses `mu4e~headers-flags-str' to pretify flags\) + %x tags \(as defined by mu\) + %c cc \(i.e. cc: field of the email\) + %h bcc \(i.e. bcc: field of the email\) + %r date chaged \(as defined by :changed in mu4e\) + +For exmaple, “%d15%s50” means 15 characters for date and 50 charcters for +subject, and “%d13%s37%f17” would make a header containing 13 characters +for Date, 37 characters for Subject, and 20 characters for From field, +making a header that looks like this: + +Thu 09 Nov 23 Title of the Email Limited to 50 Char... example@domain..." :group 'consult-mu :type '(choice (const :tag "Fromatted String" :format "%{%%d13%%s50%%f17%}") (function :tag "Custom Function"))) @@ -141,10 +150,8 @@ Thu 09 Nov 23 Title of the Email Limited to 50 Characters Onl... example@domai (defcustom consult-mu-search-sort-direction mu4e-search-sort-direction "Direction to sort by a symbol. -By defualt inherits from 'mu4e-search-sort-direction'. and can either be -`descending' (sorting Z->A) -or -`ascending' (sorting A->Z)." +By defualt inherits from `mu4e-search-sort-direction', and can either be +\='descending (sorting Z->A) or \='ascending (sorting A->Z)." :group 'consult-mu :type '(radio (const ascending) @@ -153,36 +160,37 @@ or (defcustom consult-mu-search-threads mu4e-search-threads "Whether to calculate threads for search results. -By defualt inherits from 'mu4e-search-threads'. + +By defualt inherits from `mu4e-search-threads'. Note that per mu4e docs: When threading is enabled, the headers are exclusively sorted -chronologically (:date) by the newest message in the thread. -" +chronologically (:date) by the newest message in the thread." :group 'consult-mu :type 'boolean) (defcustom consult-mu-group-by nil "What field to use to group the results in the minibuffer. -By default it is set to :date. But can be any of: +By default it is set to :date, but can be any of: :subject group by subject :from group by the name/email the sender(s) :to group by name/email of the reciver(s) :date group by date - :time group by the time of email (i.e. hour, minute, seconds) + :time group by the time of email \(i.e. hour, minute, seconds\) :datetime group by date and time of the email - :year group by the year of the email (i.e. 2023, 2022, ...) - :month group by the month of the email (i.e. Jan, Feb, ..., Dec) - :week group by the week number of the email (.i.e. 1st week, 2nd week, ... 52nd week) + :year group by the year of the email \(i.e. 2023, 2022, ...\) + :month group by the month of the email \(i.e. Jan, Feb, ..., Dec\) + :week group by the week number of the email + \(i.e. 1st week, 2nd week, ... 52nd week\) :day-of-week group by the day email was sent (i.e. Mondays, Tuesdays, ...) :day group by the day email was sent (similar to :day-of-week) :size group by the file size of the email :flags group by flags (as defined by mu) :tags group by tags (as defined by mu) - :changed group by the date changed (as defined by :changed field in mu4e) -" + :changed group by the date changed + \(as defined by :changed field in mu4e\)" :group 'consult-mu :type '(radio (const :date) (const :subject) @@ -212,14 +220,14 @@ By default it is set to :date. But can be any of: :type 'boolean) (defcustom consult-mu-headers-buffer-name "*consult-mu-headers*" - "Default name for HEADERS buffer explicitly for consult-mu. + "Default name for HEADERS buffer explicitly for `consult-mu'. For more info see `mu4e-headers-buffer-name'." :group 'consult-mu :type 'string) (defcustom consult-mu-view-buffer-name "*consult-mu-view*" - "Default name for VIEW buffer explicitly for consult-mu. + "Default name for VIEW buffer explicitly for `consult-mu'. For more info see `mu4e-view-buffer-name'." :group 'consult-mu @@ -228,8 +236,9 @@ For more info see `mu4e-view-buffer-name'." (defcustom consult-mu-preview-key consult-preview-key "Preview key for `consult-mu'. -This is similar to `consult-preview-key' but explicitly for consult-mu." - :type '(choice (const :tag "Any key" any) +This is similar to `consult-preview-key' but explicitly for `consult-mu'." + :group 'consult-mu + :type '(choice (symbol :tag "Any key" 'any) (list :tag "Debounced" (const :debounce) (float :tag "Seconds" 0.1) @@ -249,7 +258,7 @@ This is similar to `consult-preview-key' but explicitly for consult-mu." This defines whether `consult-mu--reply-action' should reply to all or not." :group 'consult-mu - :type '(choice (const :tag "Ask for confirmation" 'ask) + :type '(choice (symbol :tag "Ask for confirmation" 'ask) (const :tag "Do not reply to all" nil) (const :tag "Always reply to all" t))) @@ -257,9 +266,9 @@ This defines whether `consult-mu--reply-action' should reply to all or not." "The function that is used when selecting a message. By default it is bound to `consult-mu--view-action'." :group 'consult-mu - :type '(choice (function :tag "(Default) View Message in Mu4e Buffers" #'consult-mu--view-action) - (function :tag "Reply to Message" #'consult-mu--reply-action) - (function :tag "Forward Message" #'consult-mu--forward-action) + :type '(choice (function :tag "(Default) View Message in Mu4e Buffers" consult-mu--view-action) + (function :tag "Reply to Message" consult-mu--reply-action) + (function :tag "Forward Message" consult-mu--forward-action) (function :tag "Custom Function"))) (defcustom consult-mu-default-command #'consult-mu-dynamic @@ -294,113 +303,120 @@ The idea is Taken from https://github.com/seanfarley/counsel-mu.") "List of Favorite searches for `consult-mu-async'.") (defvar consult-mu--override-group nil -"Override grouping in `consult-mu' based on user input.") + "Override grouping in `consult-mu' based on user input.") + +(defvar consult-mu--mail-headers '("Subject" "From" "To" "From/To" "Cc" "Bcc" "Reply-To" "Date" "Attachments" "Tags" "Flags" "Maildir" "Summary" "List" "Path" "Size" "Message-Id" "List-Id" "Changed") + "List of possible headers in a message.") ;;; Faces (defface consult-mu-highlight-match-face `((t :inherit 'consult-highlight-match)) - "Highlight match face in `consult-mu''s view buffer. + "Highlight match face in `consult-mu' view buffer. By default inherits from `consult-highlight-match'. -This is used to highlight matches of search queries in the minibufffer completion list.") +This is used to highlight matches of search queries in the minibufffer +completion list.") (defface consult-mu-preview-match-face `((t :inherit 'consult-preview-match)) - "Preview match face in `consult-mu''s preview buffers. + "Preview match face in `consult-mu' preview buffers. By default inherits from `consult-preview-match'. -This is used to highlight matches of search query terms in preview buffers \(i.e. `consult-mu-view-buffer-name'\).") +This is used to highlight matches of search query terms in preview buffers +\(i.e. `consult-mu-view-buffer-name'\).") (defface consult-mu-default-face `((t :inherit 'default)) - "Default face in `consult-mu''s minibuffer annotations. + "Default face in `consult-mu' minibuffer annotations. By default inherits from `default' face.") (defface consult-mu-subject-face `((t :inherit 'font-lock-keyword-face)) - "Subject face in `consult-mu''s minibuffer annotations. + "Subject face in `consult-mu' minibuffer annotations. By default inherits from `font-lock-keyword-face'.") (defface consult-mu-sender-face `((t :inherit 'font-lock-variable-name-face)) - "Contact face in `consult-mu''s minibuffer annotations. + "Contact face in `consult-mu' minibuffer annotations. By default inherits from `font-lock-variable-name-face'.") (defface consult-mu-receiver-face `((t :inherit 'font-lock-variable-name-face)) - "Contact face in `consult-mu''s minibuffer annotations. + "Contact face in `consult-mu' minibuffer annotations. By default inherits from `font-lock-variable-name-face'.") (defface consult-mu-date-face `((t :inherit 'font-lock-preprocessor-face)) - "Date face in `consult-mu''s minibuffer annotations. + "Date face in `consult-mu' minibuffer annotations. By default inherits from `font-lock-preprocessor-face'.") (defface consult-mu-count-face `((t :inherit 'font-lock-string-face)) - "Count face in `consult-mu''s minibuffer annotations. + "Count face in `consult-mu' minibuffer annotations. By default inherits from `font-lock-string-face'.") (defface consult-mu-size-face `((t :inherit 'font-lock-string-face)) - "Size face in `consult-mu''s minibuffer annotations. + "Size face in `consult-mu' minibuffer annotations. By default inherits from `font-lock-string-face'.") (defface consult-mu-tags-face `((t :inherit 'font-lock-comment-face)) - "Tags/Comments face in `consult-mu''s minibuffer annotations. + "Tags/Comments face in `consult-mu' minibuffer annotations. By default inherits from `font-lock-comment-face'.") (defface consult-mu-flags-face `((t :inherit 'font-lock-function-call-face)) - "Flags face in `consult-mu''s minibuffer annotations. + "Flags face in `consult-mu' minibuffer annotations. By default inherits from `font-lock-function-call-face'.") (defface consult-mu-url-face `((t :inherit 'link)) - "URL face in `consult-mu''s minibuffer annotations; + "URL face in `consult-mu' minibuffer annotations; By default inherits from `link'.") (defun consult-mu--pulse-regexp (regexp) - "Finds and pulses REGEXP" + "Find and pulse REGEXP." (goto-char (point-min)) (while (re-search-forward regexp nil t) (when-let* ((m (match-data)) - (beg (car m)) - (end (cadr m)) - (ov (make-overlay beg end)) - (pulse-delay 0.075)) + (beg (car m)) + (end (cadr m)) + (ov (make-overlay beg end)) + (pulse-delay 0.075)) (pulse-momentary-highlight-overlay ov 'highlight)))) (defun consult-mu--pulse-region (beg end) - "Finds and pulses region from BEG to END" + "Find and pulse region from BEG to END." (let ((ov (make-overlay beg end)) (pulse-delay 0.075)) - (pulse-momentary-highlight-overlay ov 'highlight))) + (pulse-momentary-highlight-overlay ov 'highlight))) (defun consult-mu--pulse-line () - "Pulses line at point momentarily" + "Pulse line at point momentarily." (let* ((pulse-delay 0.055) (ov (make-overlay (car (bounds-of-thing-at-point 'line)) (cdr (bounds-of-thing-at-point 'line))))) (pulse-momentary-highlight-overlay ov 'highlight))) (defun consult-mu--set-string-width (string width &optional prepend) - "Sets the STRING width to a fixed value, WIDTH. + "Set the STRING width to a fixed value, WIDTH. -If the STRING is longer than WIDTH, it truncates the string and adds ellipsis, \"...\". If the string is shorter it adds whitespace to the string. -If PREPEND is non-nil, it truncates or adds whitespace from the beginning of string, instead of the end." +If the STRING is longer than WIDTH, it truncates the string and adds +ellipsis, “...”. If the string is shorter, it adds whitespace to the +string. If PREPEND is non-nil, it truncates or adds whitespace from the +beginning of string, instead of the end." (let* ((string (format "%s" string)) (w (string-width string))) (when (< w width) @@ -414,21 +430,23 @@ If PREPEND is non-nil, it truncates or adds whitespace from the beginning of str string)) (defun consult-mu--justify-left (string prefix maxwidth) - "Sets the width of STRING+PREFIX justified from left. -It uses `consult-mu--set-string-width' and sets the width of the concatenate of STRING+PREFIX (e.g. `(concat prefix string)`) within MAXWIDTH. This is used for aligning marginalia info in minibuffer when using `consult-mu'." - (let ((s (string-width string)) - (w (string-width prefix))) + "Set the width of STRING+PREFIX justified from left. + +Use `consult-mu--set-string-width' to the width of the concatenate of +STRING+PREFIX \(e.g. “(concat prefix string)”\) within MAXWIDTH. This is +used for aligning marginalia info in the minibuffer." + (let ((w (string-width prefix))) (if (> maxwidth w) (consult-mu--set-string-width string (- maxwidth w) t) string))) (defun consult-mu--highlight-match (regexp str ignore-case) - "Highlights REGEXP in STR. + "Highlight REGEXP in STR. -If a regular expression contains capturing groups, only these are highlighted. -If no capturing groups are used highlight the whole match. Case is ignored -if IGNORE-CASE is non-nil. -(This is adapted from `consult--highlight-regexps'.)" +If a REGEXP contains a capturing group, only the captured group is +highlighted, otherwise, the whole match is highlighted. +Case is ignored if IGNORE-CASE is non-nil. +\(This is adapted from `consult--highlight-regexps'.\)" (let ((i 0)) (while (and (let ((case-fold-search ignore-case)) (string-match regexp str i)) @@ -444,39 +462,44 @@ if IGNORE-CASE is non-nil. str) (defun consult-mu--overlay-match (match-str buffer ignore-case) - "Highlights MATCH-STR in BUFFER using an overlay. + "Highlight MATCH-STR in BUFFER using an overlay. + If IGNORE-CASE is non-nil, it uses case-insensitive match. -This is used to highlight matches to use rqueries when viewing emails in consult-mu. See `consult-mu-overlays-toggle' for toggling highligths on/off." -(with-current-buffer (or (get-buffer buffer) (current-buffer)) - (remove-overlays (point-min) (point-max) 'consult-mu-overlay t) - (goto-char (point-min)) - (let ((case-fold-search ignore-case) - (consult-mu-overlays (list))) - (while (search-forward match-str nil t) - (when-let* ((m (match-data)) - (beg (car m)) - (end (cadr m)) - (overlay (make-overlay beg end))) - (overlay-put overlay 'consult-mu-overlay t) - (overlay-put overlay 'face 'consult-mu-highlight-match-face)))))) +This is used to highlight matches to use queries when viewing emails. See +`consult-mu-overlays-toggle' for toggling highligths on/off." + (with-current-buffer (or (get-buffer buffer) (current-buffer)) + (remove-overlays (point-min) (point-max) 'consult-mu-overlay t) + (goto-char (point-min)) + (let ((case-fold-search ignore-case)) + (while (search-forward match-str nil t) + (when-let* ((m (match-data)) + (beg (car m)) + (end (cadr m)) + (overlay (make-overlay beg end))) + (overlay-put overlay 'consult-mu-overlay t) + (overlay-put overlay 'face 'consult-mu-highlight-match-face)))))) (defun consult-mu-overlays-toggle (&optional buffer) - "Toggles overlay highlights in consult-mu view/preview buffers." -(interactive) -(let ((buffer (or buffer (current-buffer)))) -(with-current-buffer buffer - (dolist (o (overlays-in (point-min) (point-max))) - (when (overlay-get o 'consult-mu-overlay) - (if (and (overlay-get o 'face) (eq (overlay-get o 'face) 'consult-mu-highlight-match-face)) - (overlay-put o 'face nil) - (overlay-put o 'face 'consult-mu-highlight-match-face))))))) + "Toggle overlay highlight in BUFFER. + +BUFFER defaults to `current-buffer'." + (interactive) + (let ((buffer (or buffer (current-buffer)))) + (with-current-buffer buffer + (dolist (o (overlays-in (point-min) (point-max))) + (when (overlay-get o 'consult-mu-overlay) + (if (and (overlay-get o 'face) (eq (overlay-get o 'face) 'consult-mu-highlight-match-face)) + (overlay-put o 'face nil) + (overlay-put o 'face 'consult-mu-highlight-match-face))))))) (defun consult-mu--format-date (string) -"Format the date STRING from mu output. + "Format the date STRING from mu output. -STRING is the output form mu command. for example from `mu find query --fields d` -Returns the date in the format Day-of-Week Month Day Year Time (e.g. Sat Nov 04 2023 09:46:54)" +STRING is the output form a mu command, for example: +`mu find query --fields d` +Returns the date in the format Day-of-Week Month Day Year Time +\(e.g. Sat Nov 04 2023 09:46:54\)" (let ((string (replace-regexp-in-string " " "0" string))) (format "%s %s %s" (substring string 0 10) @@ -484,10 +507,12 @@ Returns the date in the format Day-of-Week Month Day Year Time (e.g. Sat Nov 04 (substring string 11 -4)))) (defun consult-mu-flags-to-string (FLAG) - "Coverts FLAGS, from mu output to strings. + "Covert FLAGS, from mu output to strings. -FLAG is the output form mu command in the terminal. For example `mu find query --fields g`. -This function converts each character in FLAG to an expanded string of the flag and returns the list of these strings." +FLAG is the output form mu command in the terminal, for example: + `mu find query --fields g`. +This function converts each character in FLAG to an expanded string of the +flag and returns the list of these strings." (cl-loop for c across FLAG collect (pcase (string c) @@ -508,64 +533,62 @@ This function converts each character in FLAG to an expanded string of the flag (_ nil)))) (defun consult-mu--message-extract-email-from-string (string) - "Finds and returns the first email address in the STRING" + "Find and return the first email address in the STRING." (when (stringp string) (string-match "[a-zA-Z0-9\_\.\+\-]+@[a-zA-Z0-9\-]+\.[a-zA-Z0-9\-\.]+" string) (match-string 0 string))) (defun consult-mu--message-emails-string-to-list (string) - "Converts comma-separated STRING containing email addresses to list of emails" - (when (stringp string) - (remove '(" " "\s" "\t") (mapcar #'consult-mu--message-extract-email-from-string - (split-string string ",\\|;\\|\t" t))))) + "Convert comma-separated STRING of email addresses to a list." + (when (stringp string) + (remove '(" " "\s" "\t") + (mapcar #'consult-mu--message-extract-email-from-string + (split-string string ",\\|;\\|\t" t))))) (defun consult-mu--message-get-header-field (&optional field) - "Retrive FIELD header from the message/mail in the current buffer" + "Retrive FIELD header from the message/mail in the current buffer." (save-match-data (save-excursion (when (or (derived-mode-p 'message-mode) (derived-mode-p 'mu4e-view-mode) (derived-mode-p 'org-msg-edit-mode) (derived-mode-p 'mu4e-compose-mode)) - (let ((field (or field - (s-lower-camel-case (consult--read '("Subject" "From" "To" "Cc" "Bcc" "Reply-To" "Date" "Attachments" "Tags" "Flags" "Maildir" "Summary") - :prompt "Header Field: "))))) - (if (equal field "attachments") (setq field "\\(attachment\\|attachments\\)")) + (let* ((case-fold-search t) + (header-regexp (mapconcat (lambda (str) (concat "\n" str ": ")) + consult-mu--mail-headers "\\|")) + (field (or (downcase field) + (downcase (consult--read consult-mu--mail-headers + :prompt "Header Field: "))))) + (if (string-prefix-p "attachment" field) (setq field "\\(attachment\\|attachments\\)")) (goto-char (point-min)) - (let* ((match (re-search-forward (concat "^" field ": \\(?1:[[:ascii:][:nonascii:]]+?\\)\n.*?: ") nil t)) - (str (if match (string-trim (match-string 1))))) + (message-goto-body) + (let* ((match (re-search-backward (concat "^" field ": \\(?1:[[:ascii:][:nonascii:]]*?\\)\n\\(.*?:\\|\n\\)") nil t)) + (str (if (and match (match-string 1)) (string-trim (match-string 1))))) (if (string-empty-p str) nil str))))))) (defun consult-mu--headers-append-handler (msglst) - "Overrides `mu4e~headers-append-handler' for `consult-mu'. -This is to ensure that buffer handling is done right for consult-mu. - -From mu4e docs: + "Append one-line descriptions of messages in MSGLST. -Append one-line descriptions of messages in MSGLIST. -Do this at the end of the headers-buffer. -" - (with-current-buffer "*consult-mu-headers*" - (let ((inhibit-read-only t)) - (seq-do - ;; I use mu4e-column-faces and it overrides the default append-handler. To get the same effect I check if mu4e-column-faces is active and enabled. - (if (and (featurep 'mu4e-column-faces) mu4e-column-faces-mode) - (lambda (msg) - (mu4e-column-faces--insert-header msg (point-max))) - (lambda (msg) - (mu4e~headers-insert-header msg (point-max)))) - msglst)))) +This is used to override `mu4e~headers-append-handler' to ensure that +buffer handling is done right for `consult-mu'." + (with-current-buffer "*consult-mu-headers*" + (let ((inhibit-read-only t)) + (seq-do + ;; I use mu4e-column-faces and it overrides the default append-handler. To get the same effect I check if mu4e-column-faces is active and enabled. + (if (and (featurep 'mu4e-column-faces) mu4e-column-faces-mode) + (lambda (msg) + (mu4e-column-faces--insert-header msg (point-max))) + (lambda (msg) + (mu4e~headers-insert-header msg (point-max)))) + msglst)))) (defun consult-mu--view-msg (msg &optional buffername) - "Overrides `mu4e-view' for `consult-mu'. -This is to ensure that buffer handling is done right for consult-mu. + "Display the message MSG in a buffer with BUFFERNAME. -From mu4e docs: +BUFFERNAME defaults to `consult-mu-view-buffer-name'. -Display the message MSG in a new buffer, and keep in sync with `consult-mu-headers-buffer-name' buffer. -\"In sync\" here means that moving to the next/previous message -in the the message view affects `consult-mu-headers-buffer-name', as does marking etc. -" +This s used to overrides `mu4e-view' to ensure that buffer handling is done +right for `consult-mu'." (let* ((linked-headers-buffer (mu4e-get-headers-buffer "*consult-mu-headers*" t)) (mu4e-view-buffer-name (or buffername consult-mu-view-buffer-name))) (setq gnus-article-buffer (mu4e-get-view-buffer linked-headers-buffer t)) @@ -583,13 +606,12 @@ in the the message view affects `consult-mu-headers-buffer-name', as does markin (run-hooks 'mu4e-view-rendered-hook))))) (defun consult-mu--headers-clear (&optional text) - "Overrides `mu4e~headers-clear' for `consult-mu'. -This is to ensure that buffer handling is done right for consult-mu. + "Clear the headers buffer and related data structures. -From mu4e docs: +Optionally, show TEXT. -Clear the headers buffer and related data structures. -Optionally, show TEXT. " +This is used to override `mu4e~headers-clear' to ensure that buffer +handling is done right for `consult-mu'." (setq mu4e~headers-render-start (float-time) mu4e~headers-hidden 0) (with-current-buffer "*consult-mu-headers*" @@ -601,20 +623,23 @@ Optionally, show TEXT. " (insert (propertize text 'face 'mu4e-system-face 'intangible t)))))) (defun consult-mu--set-mu4e-search-sortfield (opts) - "Dynamically sets the `mu4e-search-sort-field' based on user input. + "Dynamically set the `mu4e-search-sort-field' based on user input. + Uses user input (i.e. from `consult-mu' command) to define the sort field. -OPTS is the command line options for mu and can be set by entering options in the minibuffer input. For more details refer to `cpnsult-grep' and consult async documentation. +OPTS is the command line options for mu and can be set by entering options +in the minibuffer input. For more details, refer to `consult-grep' and +consult async documentation. For example if the user enters the following in the minibuffer: - `#query -- --maxnum 400 --sortfield from --reverse --include-related --skip-dups --threads' -mu4e-search-sort-field is set to :from +“#query -- --maxnum 400 --sortfield from” + +`mu4e-search-sort-field' is set to :from Note that per mu4e docs: When threading is enabled, the headers are exclusively sorted -chronologically (:date) by the newest message in the thread. -" +chronologically (:date) by the newest message in the thread." (let* ((sortfield (cond ((member "-s" opts) (nth (+ (cl-position "-s" opts :test 'equal) 1) opts)) ((member "--sortfield" opts) (nth (+ (cl-position "--sortfield" opts :test 'equal) 1) opts)) @@ -642,16 +667,20 @@ chronologically (:date) by the newest message in the thread. consult-mu-search-sort-field)))) (defun consult-mu--set-mu4e-search-sort-direction (opts) -"Dynamically sets the `mu4e-search-sort-direction' based on user input. -Uses user input (i.e. from `consult-mu' command) to define the sort field. + "Dynamically set the `mu4e-search-sort-direction' based on user input. -OPTS is the command line options for mu and can be set by entering options in the minibuffer input. For more details refer to `cpnsult-grep' and consult async documentation. +Uses user input \(i.e. from `consult-mu' command\) to define the sort field. -For example if the user enters the following in the minibuffer: - `#query -- --maxnum 400 --sortfield from --reverse --include-related --skip-dups --threads' +OPTS is the command line options for mu and can be set by entering options +in the minibuffer input. For more details, refer to `consult-grep' and +consult async documentation. + +For example, if the user enters the following in the minibuffer: + +“#query -- --maxnum 400 --sortfield from --reverse” -the `mu4e-search-sort-direction' is reversed; if it is set to 'ascending, it is toggled to 'descending and vise versa. -" +The `mu4e-search-sort-direction' is reversed; If it is set to +\='ascending, it is toggled to \='descending and vise versa." (if (or (member "-z" opts) (member "--reverse" opts)) (pcase consult-mu-search-sort-direction ('descending @@ -661,75 +690,101 @@ the `mu4e-search-sort-direction' is reversed; if it is set to 'ascending, it is consult-mu-search-sort-direction)) (defun consult-mu--set-mu4e-skip-duplicates (opts) - "Dynamically sets the `mu4e-search-skip-duplicates' based on user input. -Uses user input (i.e. from `consult-mu' command) to define the sort field. + "Dynamically set the `mu4e-search-skip-duplicates' based on user input. -OPTS is the command line options for mu and can be set by entering options in the minibuffer input. For more details refer to `cpnsult-grep' and consult async documentation. +Uses user input \(i.e. from `consult-mu' command\) to define whether to +skip duplicates. -For example if the user enters the following in the minibuffer: - `#query -- --maxnum 400 --sortfield from --reverse --include-related --skip-dups --threads' +OPTS is the command line options for mu and can be set by entering options +in the minibuffer input. For more details, refer to `consult-grep' and +consult async documentation. -the `mu4e-search-skip-duplicates' is set to t. -" +For example, if the user enters the following in the minibuffer: + +“#query -- --maxnum 400 --skip-dups” + +The `mu4e-search-skip-duplicates' is set to t." (if (or (member "--skip-dups" opts) mu4e-search-skip-duplicates) t nil)) (defun consult-mu--set-mu4e-results-limit (opts) - "Dynamically sets the `mu4e-search-results-limit' based on user input. -Uses user input (i.e. from `consult-mu' command) to define the maximum number of results. + "Dynamically set the `mu4e-search-results-limit' based on user input. -OPTS is the command line options for mu and can be set by entering options in the minibuffer input. For more details refer to `consult-mu' or `consult-mu-async' documentation. -For example if the user enters the following in the minibuffer: - `#query -- --maxnum 400 --sortfield from --reverse --include-related --skip-dups --threads' +Uses user input \(i.e. from `consult-mu' command\) to define the number of +results shown. -the `mu4e-search-results-limit' is set to 400. -" - (cond - ((member "-n" opts) (string-to-number (nth (+ (cl-position "-n" opts :test 'equal) 1) opts))) - ((member "--maxnum" opts) (string-to-number (nth (+ (cl-position "--maxnum" opts :test 'equal) 1) opts))) - (t consult-mu-maxnum))) +OPTS is the command line options for mu and can be set by entering options +in the minibuffer input. For more details, refer to `consult-grep' and +consult async documentation. +For example, if the user enters the following in the minibuffer: -(defun consult-mu--set-mu4e-include-related (opts) - "Dynamically sets the `mu4e-search-include-related' based on user input. -Uses user input (i.e. from `consult-mu' command) to define the include-related property. +“#query -- --maxnum 400” -OPTS is the command line options for mu and can be set by entering options in the minibuffer input. For more details refer to `consult-mu' or `consult-mu-async' documentation. +The `mu4e-search-results-limit' is set to 400." + (cond + ((member "-n" opts) (string-to-number (nth (+ (cl-position "-n" opts :test 'equal) 1) opts))) + ((member "--maxnum" opts) (string-to-number (nth (+ (cl-position "--maxnum" opts :test 'equal) 1) opts))) + (t consult-mu-maxnum))) -For example if the user enters the following in the minibuffer: - `#query -- --maxnum 400 --sortfield from --reverse --include-related --skip-dups --threads' -the `mu4e-search-include-related' is set to t. -" - (if (or (member "-r" opts) (member "--include-related" opts) mu4e-search-include-related) t nil)) +(defun consult-mu--set-mu4e-include-related (opts) + "Dynamically set the `mu4e-search-include-related' based on user input. +Uses user input \(i.e. from `consult-mu' command\) to define whether to +include related messages. +OPTS is the command line options for mu and can be set by entering options +in the minibuffer input. For more details, refer to `consult-grep' and +consult async documentation. -(defun consult-mu--set-mu4e-threads (opts) -"Sets the `mu4e-search-threads' based on `mu4e-search-sort-field'. +For example if the user enters the following in the minibuffer: -Note that per mu4e docs, when threading is enabled, the headers are exclusively sorted by date. -Here the logic is reversed in order to allow dynamically sorting by fields other than date (even when threads are enabled). +“#query -- --include-related” -In other words if the sort-field is not the :date threading is disabled (because otherwise sort field will be ignored anyway).This allows the user to use command line arguments to sort messages by fields other than the date. For example the user can enter the following in the minibuffer input to sort by subject +The `mu4e-search-include-related' is set to t." + (if (or (member "-r" opts) (member "--include-related" opts) mu4e-search-include-related) t nil)) -`#query -- --sortfield from' -When the sort-field is :date, then `consult-mu-search-threads' is used. If `consult-mu-search-threads' is set to nil, the user can use command line arguments (a.k.a. -t or --thread) to enable it dynamically. -" -(cond - ((not (equal mu4e-search-sort-field :date)) - nil) - ((or (member "-t" opts) (member "--threads" opts) consult-mu-search-threads) - t))) -(defun consult-mu--update-headers (query ignore-history msg type) - "Search for QUERY, and updates `consult-mu-headers-buffer-name' buffer. +(defun consult-mu--set-mu4e-threads (opts) + "Set the `mu4e-search-threads' based on `mu4e-search-sort-field'. + +Uses user input \(i.e. from `consult-mu' command\) to define whether to +show threads. + +OPTS is the command line options for mu and can be set by entering options +in the minibuffer input. For more details, refer to `consult-grep' and +consult async documentation. + +Note that per mu4e docs, when threading is enabled, the headers are +exclusively sorted by date. Here the logic is reversed in order to allow +dynamically sorting by fields other than date \(even when threads are +enabled\). In other words, if the sort-field is not the :date, threading +is disabled because otherwise sort field will be ignored. This allows the +user to use command line arguments to sort messages by fields other than +the date. For example, the user can enter the following in the minibuffer +input to sort by subject + +“#query -- --sortfield subject” + +When the sort-field is :date, the default setting, +`consult-mu-search-threads' is used, and if that is set to nil, the user +can use command line arguments \(a.k.a. -t or --thread\) to enable it +dynamically." + (cond + ((not (equal mu4e-search-sort-field :date)) + nil) + ((or (member "-t" opts) (member "--threads" opts) consult-mu-search-threads) + t))) -If IGNORE-HISTORY is true, does *not* update the query history stack, `mu4e--search-query-past'. +(defun consult-mu--update-headers (query ignore-history msg type) + "Search for QUERY, and update `consult-mu-headers-buffer-name' buffer. -If MSGID is non-nil, put the cursor on message with MSGID. -" +If IGNORE-HISTORY is true, does *not* update the query history stack, +`mu4e--search-query-past'. +If MSG is non-nil, put the cursor on MSG. +TYPE can be either \=':dynamic or \=':async" (consult-mu--execute-all-marks) (cl-letf* (((symbol-function #'mu4e~headers-append-handler) #'consult-mu--headers-append-handler)) (unless (mu4e-running-p) (mu4e--server-start)) @@ -737,7 +792,6 @@ If MSGID is non-nil, put the cursor on message with MSGID. (view-buffer (get-buffer consult-mu-view-buffer-name)) (expr (car (consult--command-split (substring-no-properties query)))) (rewritten-expr (funcall mu4e-query-rewrite-function expr)) - (maxnum (unless mu4e-search-full mu4e-search-results-limit)) (mu4e-headers-fields consult-mu-headers-fields)) (pcase type (:dynamic) @@ -764,7 +818,7 @@ If MSGID is non-nil, put the cursor on message with MSGID. (consult-mu--headers-clear mu4e~search-message) (setq mu4e~headers-search-start (float-time)) - (pcase-let* ((`(,arg . ,opts) (consult--command-split query)) + (pcase-let* ((`(,_arg . ,opts) (consult--command-split query)) (mu4e-search-sort-field (consult-mu--set-mu4e-search-sortfield opts)) (mu4e-search-sort-direction (consult-mu--set-mu4e-search-sort-direction opts)) (mu4e-search-skip-duplicates (consult-mu--set-mu4e-skip-duplicates opts)) @@ -785,12 +839,15 @@ If MSGID is non-nil, put the cursor on message with MSGID. (sleep-for 0.005)))))))) (defun consult-mu--execute-all-marks (&optional no-confirmation) - "Execute the actions for all marked messages in `consult-mu-headers-buffer-name' buffer. + "Execute the actions for all marked messages. + +Executes all actions for marked messages in the buffer +`consult-mu-headers-buffer-name'. If NO-CONFIRMATION is non-nil, don't ask user for confirmation. -This is similar to `mu4e-mark-execute-all' but, with buffer/window handling set accordingly for consult-mu. -" +This is similar to `mu4e-mark-execute-all' but, with buffer/window +handling set accordingly for `consult-mu'." (interactive "P") (when-let* ((buf (get-buffer consult-mu-headers-buffer-name))) (with-current-buffer buf @@ -804,81 +861,81 @@ This is similar to `mu4e-mark-execute-all' but, with buffer/window handling set (quit-window)))))))) (defun consult-mu--headers-goto-message-id (msgid) - "Jumps to message with MSGID + "Jump to message with MSGID. -in `consult-mu-headers-buffer-name' buffer." +This is done in `consult-mu-headers-buffer-name' buffer." (when-let ((buffer consult-mu-headers-buffer-name)) (with-current-buffer buffer (setq mu4e-view-buffer-name consult-mu-view-buffer-name) (mu4e-headers-goto-message-id msgid)))) (defun consult-mu--get-message-by-id (msgid) - "Finds the message with MSGID and returns the mu4e MSG plist for it." + "Find the message with MSGID and return the mu4e MSG plist for it." (cl-letf* (((symbol-function #'mu4e-view) #'consult-mu--view-msg)) - (when-let ((buffer consult-mu-headers-buffer-name)) - (with-current-buffer buffer - (setq mu4e-view-buffer-name consult-mu-view-buffer-name) - (mu4e-headers-goto-message-id msgid) - (mu4e-message-at-point))))) + (when-let ((buffer consult-mu-headers-buffer-name)) + (with-current-buffer buffer + (setq mu4e-view-buffer-name consult-mu-view-buffer-name) + (mu4e-headers-goto-message-id msgid) + (mu4e-message-at-point))))) (defun consult-mu--contact-string-to-plist (string) "Convert STRING for contacts to plist. -STRING is the output form mu command. for example from `mu find query --fields f` -Returns plist with :email and :name keys. +STRING is the output form mu command, for example from: +`mu find query --fields f` + +Returns a plist with \=':email and \':name keys. For example -\"John Doe \" +“John Doe ” will be converted to -(:name \"John Doe\" :email \"john.doe@example.com\") - -" -(let* ((string (replace-regexp-in-string ">,\s\\|>;\s" ">\n" string)) - (list (string-split string "\n" t))) +\(:name “John Doe” :email “john.doe@example.com”\)" + (let* ((string (replace-regexp-in-string ">,\s\\|>;\s" ">\n" string)) + (list (split-string string "\n" t))) (mapcar (lambda (item) (cond ((string-match "\\(?2:.*\\)\s+<\\(?1:.+\\)>" item) (list :email (or (match-string 1 item) nil) :name (or (match-string 2 item) nil))) ((string-match "^\\(?1:[a-zA-Z0-9\_\.\+\-]+@[a-zA-Z0-9\-]+\.[a-zA-Z0-9\-\.]+\\)" item) - (list :email (or (match-string 1 item) nil) :name nil)) + (list :email (or (match-string 1 item) nil) :name nil)) (t (list :email (format "%s" item) :name nil)))) list))) (defun consult-mu--contact-name-or-email (contact) -"Retrieve name or email of CONTACT. + "Retrieve name or email of CONTACT. -Looks at the contact plist (e.g. (:name \"John Doe\" :email \"john.doe@example.com\") ) and returns the name. If the name is missing returns the email address. -" +Looks at the contact plist \(e.g. (:name “John Doe” :email +“john.doe@example.com”)\) and returns the name. If the name is missing, +returns the email address." (cond ((stringp contact) contact) ((listp contact) - (mapconcat (lambda (item) (or (plist-get item :name) (plist-get item :email) "")) contact ",")))) + (mapconcat (lambda (item) (or (plist-get item :name) (plist-get item :email) "")) contact ",")))) (defun consult-mu--headers-template () "Make headers template using `consult-mu-headers-template'." -(if (and consult-mu-headers-template (functionp consult-mu-headers-template)) - (funcall consult-mu-headers-template) - consult-mu-headers-template)) + (if (and consult-mu-headers-template (functionp consult-mu-headers-template)) + (funcall consult-mu-headers-template) + consult-mu-headers-template)) (defun consult-mu--expand-headers-template (msg string) - "Expands STRING to create a custom header format for MSG. + "Expand STRING to create a custom header format for MSG. -See `consult-mu-headers-template' for explanation of the format of STRING. -" +See `consult-mu-headers-template' for explanation of the format of +STRING." - (cl-loop with str = nil - for c in (string-split string "%" t) + (cl-loop for c in (split-string string "%" t) concat (concat (pcase (substring c 0 1) ("f" (let ((sender (consult-mu--contact-name-or-email (plist-get msg :from))) - (length (string-to-number (substring c 1 nil)))) + (length (string-to-number (substring c 1 nil)))) (if sender (propertize (if (> length 0) (consult-mu--set-string-width sender length) sender) 'face 'consult-mu-sender-face)))) ("t" (let ((receiver (consult-mu--contact-name-or-email (plist-get msg :to))) - (length (string-to-number (substring c 1 nil)))) + (length (string-to-number (substring c 1 nil)))) (if receiver (propertize (if (> length 0) (consult-mu--set-string-width receiver length) receiver) 'face 'consult-mu-sender-face)))) ("s" (let ((subject (plist-get msg :subject)) @@ -919,12 +976,12 @@ See `consult-mu-headers-template' for explanation of the format of STRING. (propertize (if (> length 0) (consult-mu--set-string-width tags length) tags) 'face 'consult-mu-tags-face) nil))) ("c" (let ((cc (consult-mu--contact-name-or-email (plist-get msg :cc))) - (length (string-to-number (substring c 1 nil)))) + (length (string-to-number (substring c 1 nil)))) (if cc (propertize (if (> length 0) (consult-mu--set-string-width cc length) cc) 'face 'consult-mu-tags-face)))) ("h" (let ((bcc (consult-mu--contact-name-or-email (plist-get msg :bcc))) - (length (string-to-number (substring c 1 nil)))) + (length (string-to-number (substring c 1 nil)))) (if bcc (propertize (if (> length 0) (consult-mu--set-string-width bcc length) bcc) 'face 'consult-mu-tags-face)))) @@ -938,13 +995,13 @@ See `consult-mu-headers-template' for explanation of the format of STRING. (defun consult-mu--quit-header-buffer () "Quits `consult-mu-headers-buffer-name' buffer." (save-mark-and-excursion - (when-let* ((buf (get-buffer consult-mu-headers-buffer-name))) - (with-current-buffer buf - (if (eq major-mode 'mu4e-headers-mode) - (mu4e-mark-handle-when-leaving) - (quit-window t) - ;; clear the decks before going to the main-view - (mu4e--query-items-refresh 'reset-baseline)))))) + (when-let* ((buf (get-buffer consult-mu-headers-buffer-name))) + (with-current-buffer buf + (if (eq major-mode 'mu4e-headers-mode) + (mu4e-mark-handle-when-leaving) + (quit-window t) + ;; clear the decks before going to the main-view + (mu4e--query-items-refresh 'reset-baseline)))))) (defun consult-mu--quit-view-buffer () "Quits `consult-mu-view-buffer-name' buffer." @@ -954,56 +1011,58 @@ See `consult-mu-headers-template' for explanation of the format of STRING. (mu4e-view-quit))))) (defun consult-mu--quit-main-buffer () - "Quits 'mu4e-main-buffer-name' buffer." + "Quits `mu4e-main-buffer-name' buffer." (when-let* ((buf (get-buffer mu4e-main-buffer-name))) (with-current-buffer buf (if (eq major-mode 'mu4e-main-mode) (mu4e-quit))))) (defun consult-mu--lookup () -"Lookup function for `consult-mu' or `consult-mu-async' minibuffer candidates. + "Lookup function for `consult-mu' or `consult-mu-async' candidates. -This is passed as LOOKUP to `consult--read' on candidates and is used to format the output when a candidate is selected." - (lambda (sel cands &rest args) +This is passed as LOOKUP to `consult--read' on candidates and is used to +format the output when a candidate is selected." + (lambda (sel cands &rest _args) (let* ((info (cdr (assoc sel cands))) (msg (plist-get info :msg)) (subject (plist-get msg :subject))) (cons subject info)))) (defun consult-mu--group-name (cand) - "Gets the group name of CAND using `consult-mu-group-by' -See `consult-mu-group-by' for details of grouping options. -" -(let* ((msg (get-text-property 0 :msg cand)) - (group (or consult-mu--override-group consult-mu-group-by)) - (field (if (not (keywordp group)) (intern (concat ":" (format "%s" group))) group))) - (pcase field - (:date (format-time-string "%a %d %b %y" (plist-get msg field))) - (:from (cond - ((listp (plist-get msg field)) - (mapconcat (lambda (item) (or (plist-get item :name) (plist-get item :email))) (plist-get msg field) ";")) - (stringp (plist-get msg field) (plist-get msg field)))) - (:to (cond - ((listp (plist-get msg field)) - (mapconcat (lambda (item) (or (plist-get item :name) (plist-get item :email))) (plist-get msg field) ";")) - (stringp (plist-get msg field) (plist-get msg field)))) - (:changed (format-time-string "%a %d %b %y" (plist-get msg field))) - (:datetime (format-time-string "%F %r" (plist-get msg :date))) - (:time (format-time-string "%X" (plist-get msg :date))) - (:year (format-time-string "%Y" (plist-get msg :date))) - (:month (format-time-string "%B" (plist-get msg :date))) - (:day-of-week (format-time-string "%A" (plist-get msg :date))) - (:day (format-time-string "%A" (plist-get msg :date))) - (:week (format-time-string "%V" (plist-get msg :date))) - (:size (file-size-human-readable (plist-get msg field))) - (:flags (format "%s" (plist-get msg field))) - (:tags (format "%s" (plist-get msg field))) - (_ (if (plist-get msg field) (format "%s" (plist-get msg field)) nil))))) + "Get the group name of CAND using `consult-mu-group-by'. + +See `consult-mu-group-by' for details of grouping options." + (let* ((msg (get-text-property 0 :msg cand)) + (group (or consult-mu--override-group consult-mu-group-by)) + (field (if (not (keywordp group)) (intern (concat ":" (format "%s" group))) group))) + (pcase field + (:date (format-time-string "%a %d %b %y" (plist-get msg field))) + (:from (cond + ((listp (plist-get msg field)) + (mapconcat (lambda (item) (or (plist-get item :name) (plist-get item :email))) (plist-get msg field) ";")) + ((stringp (plist-get msg field)) (plist-get msg field)))) + (:to (cond + ((listp (plist-get msg field)) + (mapconcat (lambda (item) (or (plist-get item :name) (plist-get item :email))) (plist-get msg field) ";")) + ((stringp (plist-get msg field)) (plist-get msg field)))) + (:changed (format-time-string "%a %d %b %y" (plist-get msg field))) + (:datetime (format-time-string "%F %r" (plist-get msg :date))) + (:time (format-time-string "%X" (plist-get msg :date))) + (:year (format-time-string "%Y" (plist-get msg :date))) + (:month (format-time-string "%B" (plist-get msg :date))) + (:day-of-week (format-time-string "%A" (plist-get msg :date))) + (:day (format-time-string "%A" (plist-get msg :date))) + (:week (format-time-string "%V" (plist-get msg :date))) + (:size (file-size-human-readable (plist-get msg field))) + (:flags (format "%s" (plist-get msg field))) + (:tags (format "%s" (plist-get msg field))) + (_ (if (plist-get msg field) (format "%s" (plist-get msg field)) nil))))) (defun consult-mu--group (cand transform) -"Group function for `consult-mu' or `consult-mu-async' minibuffer candidates. + "Group function for `consult-mu' or `consult-mu-async'. -This is passed as GROUP to `consult--read' on candidates and is used to group emails using `consult-mu--group-name'." +CAND is passed to `consult-mu--group-name' to get the group for CAND. +When TRANSFORM is non-nil, the name of CAND is used for group." (when-let ((name (consult-mu--group-name cand))) (if transform (substring cand) name))) @@ -1011,11 +1070,8 @@ This is passed as GROUP to `consult--read' on candidates and is used to group em "Opens MSG in `consult-mu-headers' and `consult-mu-view'. If NOSELECT is non-nil, does not select the view buffer/window. - If MARK-AS-READ is non-nil, marks the MSG as read. - -If MATCH-STR is non-nil, highlights the MATCH-STR in the view buffer. -" +If MATCH-STR is non-nil, highlights the MATCH-STR in the view buffer." (let ((msgid (plist-get msg :message-id))) (when-let ((buf (mu4e-get-headers-buffer consult-mu-headers-buffer-name t))) (with-current-buffer buf @@ -1030,9 +1086,9 @@ If MATCH-STR is non-nil, highlights the MATCH-STR in the view buffer. (with-current-buffer consult-mu-headers-buffer-name (if msgid (progn - (mu4e-headers-goto-message-id msgid) - (if mark-as-read - (mu4e--server-move (mu4e-message-field-at-point :docid) nil "+S-u-N"))))) + (mu4e-headers-goto-message-id msgid) + (if mark-as-read + (mu4e--server-move (mu4e-message-field-at-point :docid) nil "+S-u-N"))))) (when match-str (add-to-history 'search-ring match-str) @@ -1048,11 +1104,14 @@ If MATCH-STR is non-nil, highlights the MATCH-STR in the view buffer. (defun consult-mu--view-action (cand) - "Opens the candidate, CAND, from consult-mu. + "Open the candidate, CAND. -This is a wrapper function around `consult-mu--view'. It parses CAND to extract relevant MSG plist and other information and passes them to `consult-mu--view'. +This is a wrapper function around `consult-mu--view'. It parses CAND to +extract relevant MSG plist and other information and passes them to +`consult-mu--view'. -To use this as the default action for consult-mu, set `consult-mu-default-action' to #'consult-mu--view-action." +To use this as the default action for `consult-mu', set +`consult-mu-default-action' to \=#'consult-mu--view-action." (let* ((info (cdr cand)) (msg (plist-get info :msg)) @@ -1064,8 +1123,8 @@ To use this as the default action for consult-mu, set `consult-mu-default-action (defun consult-mu--reply (msg &optional wide-reply) "Reply to MSG using `mu4e-compose-reply'. -If WIDE-REPLY is non-nil use wide-reply (a.k.a. reply all) with `mu4e-compose-wide-reply'. -" +If WIDE-REPLY is non-nil use wide-reply \(a.k.a. reply all\) with +`mu4e-compose-wide-reply'." (let ((msgid (plist-get msg :message-id))) (when-let ((buf (mu4e-get-headers-buffer consult-mu-headers-buffer-name t))) (with-current-buffer buf @@ -1080,14 +1139,22 @@ If WIDE-REPLY is non-nil use wide-reply (a.k.a. reply all) with `mu4e-compose-wi (mu4e-compose-wide-reply))))) (defun consult-mu--reply-action (cand &optional wide-reply) + "Reply to CAND. + +This is a wrapper function around `consult-mu--reply'. It passes +relevant message plist, from CAND, as well as WIDE-REPLY to +`consult-mu--reply'. + +To use this as the default action for `consult-mu', set +`consult-mu-default-action' to \=#'consult-mu--reply-action." (let* ((info (cdr cand)) (msg (plist-get info :msg)) (wide-reply (or wide-reply (pcase consult-mu-use-wide-reply - ('ask (y-or-n-p "reply all?")) + ('ask (y-or-n-p "Reply All?")) ('nil nil) ('t t))))) - (consult-mu--reply msg wide-reply))) + (consult-mu--reply msg wide-reply))) (defun consult-mu--forward (msg) "Forward the MSG using `mu4e-compose-forward'." @@ -1101,25 +1168,32 @@ If WIDE-REPLY is non-nil use wide-reply (a.k.a. reply all) with `mu4e-compose-wi (mu4e-compose-forward)))) (defun consult-mu--forward-action (cand) + "Forward CAND. + +This is a wrapper function around `consult-mu--forward'. It passes +the relevant message plist, from CAND to `consult-mu--forward'. + +To use this as the default action for `consult-mu', set +`consult-mu-default-action' to \=#'consult-mu--forward-action." (let* ((info (cdr cand)) (msg (plist-get info :msg))) - (consult-mu--forward msg))) + (consult-mu--forward msg))) (defun consult-mu--get-split-style-character (&optional style) -"Get the character for consult async split STYLE. + "Get the character for consult async split STYLE. STYLE defaults to `consult-async-split-style'." -(let ((style (or style consult-async-split-style 'none))) - (or (char-to-string (plist-get (alist-get style consult-async-split-styles-alist) :initial)) - (char-to-string (plist-get (alist-get style consult-async-split-styles-alist) :separator)) - ""))) + (let ((style (or style consult-async-split-style 'none))) + (or (char-to-string (plist-get (alist-get style consult-async-split-styles-alist) :initial)) + (char-to-string (plist-get (alist-get style consult-async-split-styles-alist) :separator)) + ""))) (defun consult-mu--dynamic-format-candidate (cand highlight) - "Formats minibuffer candidates for `consult-mu'. + "Format minibuffer candidate, CAND. -CAND is the minibuffer completion candidate (a mu4e message collected by `consult-mu--dynamic-collection'). - -if HIGHLIGHT is non-nil, it is highlighted with `consult-mu-highlight-match-face' in the minibuffer completion list." +CAND is the minibuffer completion candidate \(a mu4e message collected by +`consult-mu--dynamic-collection'\). If HIGHLIGHT is non-nil, it is +highlighted with `consult-mu-highlight-match-face'." (let* ((string (car cand)) (info (cadr cand)) @@ -1134,7 +1208,7 @@ if HIGHLIGHT is non-nil, it is highlighted with `consult-mu-highlight-match-face (if (and consult-mu-highlight-matches highlight) (cond ((listp match-str) - (mapcar (lambda (match) (setq str (consult-mu--highlight-match match str t))) match-str)) + (mapc (lambda (match) (setq str (consult-mu--highlight-match match str t))) match-str)) ((stringp match-str) (setq str (consult-mu--highlight-match match-str str t)))) str) @@ -1142,13 +1216,15 @@ if HIGHLIGHT is non-nil, it is highlighted with `consult-mu-highlight-match-face (cons str (list :msg msg :query query :type :dynamic))))) (defun consult-mu--dynamic-collection (input) - "Dynamically collects mu4e search results. + "Dynamically collect mu4e search results. -INPUT is the user input. It is passed as QUERY to `consult-mu--update-headers', appends the result to `consult-mu-headers-buffer-name' and returns the collects list of found messages and returns it as minibuffer completion table. -" +INPUT is the user input. It is passed as QUERY to +`consult-mu--update-headers', appends the result to +`consult-mu-headers-buffer-name' and returns a list of found +messages." (save-excursion - (pcase-let* ((`(,arg . ,opts) (consult--command-split input))) + (pcase-let* ((`(,_arg . ,opts) (consult--command-split input))) (consult-mu--update-headers (substring-no-properties input) nil nil :dynamic) (if (or (member "-g" opts) (member "--group" opts)) (cond @@ -1162,12 +1238,13 @@ INPUT is the user input. It is passed as QUERY to `consult-mu--update-headers', (goto-char (point-min)) (remove nil (cl-loop until (eobp) - collect (consult-mu--dynamic-format-candidate (list (buffer-substring (point) (point-at-eol)) (list :msg (ignore-errors (mu4e-message-at-point)) :query input)) t) + collect (consult-mu--dynamic-format-candidate (list (buffer-substring (point) (line-end-position)) (list :msg (ignore-errors (mu4e-message-at-point)) :query input)) t) do (forward-line 1)))))) (defun consult-mu--dynamic-state () - "State function for consult-mu candidates. -This is passed as STATE to `consult--read' and is used to preview or do other actions on the candidate." + "State function for `consult-mu' candidates. +This is passed as STATE to `consult--read' and is used to preview or do +other actions on the candidate." (lambda (action cand) (let ((preview (consult--buffer-preview))) (pcase action @@ -1196,34 +1273,51 @@ This is passed as STATE to `consult--read' and is used to preview or do other ac (defun consult-mu--dynamic (prompt collection &optional initial) "Query mu4e messages dyunamically. -This is a non-interactive internal function. For the interactive version see `consult-mu'. +This is a non-interactive internal function. For the interactive version +see `consult-mu'. -It runs the `consult-mu--dynamic-collection' to do a `mu4e-search' with user input (e.g. INITIAL) and returns the results (list of messages found) as a completion table in minibuffer. +It runs the `consult-mu--dynamic-collection' to do a `mu4e-search' with +user input \(e.g. INITIAL\) and returns the results \(list of messages +found\) as a completion table in minibuffer. -The completion table gets dynamically updated as the user types in the minibuffer. Each candidate in the minibuffer is formatted by `consult-mu--dynamic-format-candidate' to add annotation and other info to the candidate. +The completion table gets dynamically updated as the user types in the +minibuffer. Each candidate in the minibuffer is formatted by +`consult-mu--dynamic-format-candidate' to add annotation and other info to +the candidate. -PROMPT is the prompt in the minibuffer (passed as PROMPT to `consult--read'.) -COLLECTION is a colection function passed to `consult--dynamic-collection'. -INITIAL is an optional arg for the initial input in the minibuffer. (passed as INITITAL to `consult--read'.) +Description of Arguments: + PROMPT the prompt in the minibuffer + \(passed as PROMPT to `consult--read'\) + COLLECTION a colection function passed to `consult--dynamic-collection'. + INITIAL an optional arg for the initial input in the minibuffer. + \(passed as INITITAL to `consult--read'\) -commandline arguments/options (see `mu find --help` in the command line for details) can be passed to the minibuffer input similar to `consult-grep'. For example the user can enter: +commandline arguments/options \(see `mu find --help` in the command line +for details\) can be passed to the minibuffer input similar to +`consult-grep'. For example the user can enter: -`#paper -- --maxnum 200 --sortfield from --reverse' +“#paper -- --maxnum 200 --sortfield from --reverse” -this will search for mu4e messages with the query \"paper\", retrives a maximum of 200 messagesn sorts them by the \"from:\" field and reverses the sort direction (opposite of `consult-mu-search-sort-field'). +this will search for mu4e messages with the query “paper”, retrives a +maximum of 200 messages and sorts them by the “from:” field and reverses +the sort direction (opposite of `consult-mu-search-sort-field'). -Note that some command line arguments are not supported by mu4e (for example sorting base on cc: field or bcc: field is not supported in `mu4e-search-sort-field') +Note that some command line arguments are not supported by mu4e (for +example sorting based on cc: or bcc: fields are not supported in +`mu4e-search-sort-field') -Also, the results can further be narrowed by entering \"#\" similar to `consult-grep'. +Also, the results can further be narrowed by +`consult-async-split-style' \(e.g. by entering “#” when +`consult-async-split-style' is set to \='perl\). For example: -`#paper -- --maxnum 200 --sortfield from --reverse#accepted' +“#paper -- --maxnum 200 --sortfield from --reverse#accepted” -will retrieve the message as the example above, then narrows down the completion table to candidates that match \"accepted\". -" +will retrieve the message as the example above, then narrows down the +candidates to those that that match “accepted”." (consult--read - (consult--dynamic-collection #'consult-mu--dynamic-collection) + (consult--dynamic-collection (or collection #'consult-mu--dynamic-collection)) :prompt (or prompt "Select: ") :lookup (consult-mu--lookup) :state (funcall #'consult-mu--dynamic-state) @@ -1238,41 +1332,52 @@ will retrieve the message as the example above, then narrows down the completion :sort nil)) (defun consult-mu-dynamic (&optional initial noaction) - "Lists results of `mu4e-search' dynamically. + "Lists results of `mu4e-search' dynamically. -This is an interactive wrapper function around `consult-mu--dynamic'. It queries the user for a search term in the minibuffer, then fetches a list of messages for the entered search term as a minibuffer completion table for selection. The list of candidates in the completion table are dynamically updated as the user changes the entry. +This is an interactive wrapper function around `consult-mu--dynamic'. It +queries the user for a search term in the minibuffer, then fetches a list +of messages for the entered search term as a minibuffer completion table +for selection. The list of candidates in the completion table are +dynamically updated as the user changes the entry. Upon selection of a candidate either - the candidate is returned if NOACTION is non-nil or - the candidate is passed to `consult-mu-action' if NOACTION is nil. -Additional commandline arguments can be passed in the minibuffer entry by typing `--` followed by command line arguments. +Additional commandline arguments can be passed in the minibuffer entry by +typing “--” followed by command line arguments. -For example the user can enter: +For example, the user can enter: -`#consult-mu -- -n 10' +“#consult-mu -- -n 10” -this will run a `mu4e-search' with the query \"consult-my\" and changes the search limit (i.e. `mu4e-search-results-limit' to 10. +this will run a `mu4e-search' with the query “consult-mu” and changes the +search limit \(i.e. `mu4e-search-results-limit' to 10\). -Also, the results can further be narrowed by entering \"#\" similar to `consult-grep'. +Also, the results can further be narrowed by +`consult-async-split-style' \(e.g. by entering “#” when +`consult-async-split-style' is set to \='perl\). For example: -`#consult-mu -- -n 10#github' +“#consult-mu -- -n 10#github” -will retrieve the message as the example above, then narrows down the completion table to candidates that match \"github\". +will retrieve the messages as the example above, then narrows down the +completion table to candidates that match “github”. -INITIAL is an optional arg for the initial input in the minibuffer. (passed as INITITAL to `consult-mu--dynamic') +INITIAL is an optional arg for the initial input in the minibuffer. +\(passed as INITITAL to `consult-mu--dynamic'\) -For more details on consult--async functionalities, see `consult-grep' and the official manual of consult, here: https://github.com/minad/consult. -" +For more details on consult--async functionalities, see `consult-grep' and +the official manual of consult, here: +URL `https://github.com/minad/consult'" (interactive) (save-mark-and-excursion - (consult-mu--execute-all-marks)) + (consult-mu--execute-all-marks)) (let* ((sel - (consult-mu--dynamic (concat "[" (propertize "consult-mu-dynamic" 'face 'consult-mu-sender-face) "]" " Search For: ") #'consult-mu--dynamic-collection initial))) + (consult-mu--dynamic (concat "[" (propertize "consult-mu-dynamic" 'face 'consult-mu-sender-face) "]" " Search For: ") #'consult-mu--dynamic-collection initial))) (save-mark-and-excursion (consult-mu--execute-all-marks)) (if noaction @@ -1286,10 +1391,12 @@ For more details on consult--async functionalities, see `consult-grep' and the o STRING is the output retrieved from `mu find INPUT ...` in the command line. INPUT is the query from the user. -if HIGHLIGHT is t, input is highlighted with `consult-mu-highlight-match-face' in the minibuffer." + +If HIGHLIGHT is t, input is highlighted with +`consult-mu-highlight-match-face' in the minibuffer." (let* ((query input) - (parts (string-split (replace-regexp-in-string "^\\\\->\s\\|^\\\/->\s" "" string) consult-mu-delimiter)) + (parts (split-string (replace-regexp-in-string "^\\\\->\s\\|^\\\/->\s" "" string) consult-mu-delimiter)) (msgid (car parts)) (date (date-to-time (cadr parts))) (sender (cadr (cdr parts))) @@ -1311,9 +1418,10 @@ if HIGHLIGHT is t, input is highlighted with `consult-mu-highlight-match-face' i (headers-template (consult-mu--headers-template)) (str (if headers-template (consult-mu--expand-headers-template msg headers-template) - (format "%s\s\s%s\s\s%s\s\s%s\s\s%s" + (format "%s\s\s%s\s\s%s\s\s%s\s\s%s\s\s%s" (propertize (consult-mu--set-string-width - (format-time-string "%x" date) 10) 'face 'consult-mu-date-face) + (format-time-string "%x" date) 10) + 'face 'consult-mu-date-face) (propertize (consult-mu--set-string-width (consult-mu--contact-name-or-email sender) (floor (* (frame-width) 0.2))) 'face 'consult-mu-sender-face) (propertize (consult-mu--set-string-width subject (floor (* (frame-width) 0.55))) 'face 'consult-mu-subject-face) (propertize (file-size-human-readable size) 'face 'consult-mu-size-face) @@ -1323,7 +1431,7 @@ if HIGHLIGHT is t, input is highlighted with `consult-mu-highlight-match-face' i (if (and consult-mu-highlight-matches highlight) (cond ((listp match-str) - (mapcar (lambda (match) (setq str (consult-mu--highlight-match match str t))) match-str)) + (mapc (lambda (match) (setq str (consult-mu--highlight-match match str t))) match-str)) ((stringp match-str) (setq str (consult-mu--highlight-match match-str str t)))) str) @@ -1332,7 +1440,8 @@ if HIGHLIGHT is t, input is highlighted with `consult-mu-highlight-match-face' i (defun consult-mu--async-state () "State function for `consult-mu-async' candidates. -This is passed as STATE to `consult--read' and is used to preview or do other actions on the candidate." +This is passed as STATE to `consult--read' and is used to preview or do +other actions on the candidate." (lambda (action cand) (let ((preview (consult--buffer-preview))) (pcase action @@ -1390,15 +1499,15 @@ Format each candidates with `consult-gh--repo-format' and INPUT." (setq opts (append opts (list "--fields" (format "i%sd%sf%st%ss%sz%sg%sx%sp%sc%sh%sl" consult-mu-delimiter consult-mu-delimiter consult-mu-delimiter consult-mu-delimiter consult-mu-delimiter consult-mu-delimiter consult-mu-delimiter consult-mu-delimiter consult-mu-delimiter consult-mu-delimiter consult-mu-delimiter)))) (unless (or (member "-s" flags) (member "--sortfiled" flags)) - (setq opts (append opts (list "--sortfield" (substring (symbol-name consult-mu-search-sort-field) 1))))) + (setq opts (append opts (list "--sortfield" (substring (symbol-name consult-mu-search-sort-field) 1))))) (if threads (setq opts (append opts (list "--thread")))) (if skip-dups (setq opts (append opts (list "--skip-dups")))) (if include-related (setq opts (append opts (list "--include-related")))) (cond ((and (member "-n" flags) (< (string-to-number (nth (+ (cl-position "-n" opts :test 'equal) 1) opts)) 0)) - (setq opts (remove "-n" (remove (nth (+ (cl-position "-n" opts :test 'equal) 1) opts) opts)))) + (setq opts (remove "-n" (remove (nth (+ (cl-position "-n" opts :test 'equal) 1) opts) opts)))) ((and (member "--maxnum" flags) (< (string-to-number (nth (+ (cl-position "--maxnum" opts :test 'equal) 1) opts)) 0)) - (setq opts (remove "--maxnum" (remove (nth (+ (cl-position "--maxnum" opts :test 'equal) 1) opts) opts))))) + (setq opts (remove "--maxnum" (remove (nth (+ (cl-position "--maxnum" opts :test 'equal) 1) opts) opts))))) (unless (or (member "-n" flags) (member "--maxnum" flags)) (if (and consult-mu-maxnum (> consult-mu-maxnum 0)) (setq opts (append opts (list "--maxnum" (format "%s" consult-mu-maxnum)))))) @@ -1418,30 +1527,46 @@ Format each candidates with `consult-gh--repo-format' and INPUT." hl))))) (defun consult-mu--async (prompt builder &optional initial) -"Query mu4e messages asynchronously. + "Query mu4e messages asynchronously. + +This is a non-interactive internal function. For the interactive +version, see `consult-mu-async'. -This is a non-interactive internal function. For the interactive version see `consult-mu-async'. +It runs the command line from `consult-mu--async-builder' in an async +process and returns the results (list of messages) as a completion table +in minibuffer that will be passed to `consult--read'. The completion +table gets dynamically updated as the user types in the minibuffer. Each +candidate in the minibuffer is formatted by `consult-mu--async-transform' +to add annotation and other info to the candidate. -It runs the command line from `consult-mu--async-builder' in an async process and returns the results (list of messages) as a completion table in minibuffer that will be passed to `consult--read'. The completion table gets dynamically updated as the user types in the minibuffer. Each candidate in the minibuffer is formatted by `consult-mu--async-transform' to add annotation and other info to the candidate. +Description of Arguments: -PROMPT is the prompt in the minibuffer (passed as PROMPT to `consult--red'.) -BUILDER is an async builder function passed to `consult--async-command'. -INITIAL is an optional arg for the initial input in the minibuffer. (passed as INITITAL to `consult--read'.) +PROMPT the prompt in the minibuffer + \(passed as PROMPT to `consult--red'\) +BUILDER an async builder function passed to `consult--async-command' +INITIAL an optional arg for the initial input in the minibuffer + \(passed as INITITAL to `consult--read'\) -commandline arguments/options (see `mu find --help` in the command line for details) can be passed to the minibuffer input similar to `consult-grep'. For example the user can enter: +commandline arguments/options \(see `mu find --help` in the command line +for details\) can be passed to the minibuffer input similar to +`consult-grep'. For example the user can enter: -`#paper -- --maxnum 200 --sortfield from --reverse' +“#paper -- --maxnum 200 --sortfield from --reverse” -this will search for mu4e messages with the query \"paper\", retrives a maximum of 200 messages sorts them by the \"from:\" field and reverses the sort direction (opposite of `consult-mu-search-sort-field'). +this will search for mu4e messages with the query “paper”, retrives a +maximum of 200 messages sorts them by the “from:” field and reverses the +sort direction (opposite of `consult-mu-search-sort-field'). -Also, the results can further be narrowed by entering \"#\" similar to `consult-grep'. +Also, the results can further be narrowed by +`consult-async-split-style' \(e.g. by entering “#” when +`consult-async-split-style' is set to \='perl\). For example: `#paper -- --maxnum 200 --sortfield from --reverse#accepted' -will retrieve the message as the example above, then narrows down the completion table to candidates that match \"accepted\". -" +will retrieve the message as the example above, then narrows down the +completion table to candidates that match “accepted”." (consult--read (consult--process-collection builder :transform (consult--async-transform-by-input #'consult-mu--async-transform)) @@ -1461,36 +1586,57 @@ will retrieve the message as the example above, then narrows down the completion (defun consult-mu-async (&optional initial noaction) "Lists results of `mu find` Asynchronously. -This is an interactive wrapper function around `consult-mu--async'. It queries the user for a search term in the minibuffer, then fetches a list of messages for the entered search term as a minibuffer completion table for selection. The list of candidates in the completion table are dynamically updated as the user changes the entry. +This is an interactive wrapper function around `consult-mu--async'. It +queries the user for a search term in the minibuffer, then fetches a list +of messages for the entered search term as a minibuffer completion table +for selection. The list of candidates in the completion table are +dynamically updated as the user changes the entry. Upon selection of a candidate either - the candidate is returned if NOACTION is non-nil or - the candidate is passed to `consult-mu-action' if NOACTION is nil. -Additional commandline arguments can be passed in the minibuffer entry by typing `--` followed by command line arguments. +Additional commandline arguments can be passed in the minibuffer entry by +typing `--` followed by command line arguments. For example the user can enter: `#consult-mu -- -n 10' -this will run a `mu4e-search' with the query \"consult-my\" and changes the search limit (i.e. `mu4e-search-results-limit' to 10. +this will run a `mu4e-search' with the query \"consult-my\" and changes the +search limit (i.e. `mu4e-search-results-limit' to 10. -Also, the results can further be narrowed by entering \"#\" similar to `consult-grep'. +Also, the results can further be narrowed by `consult-async-split-style' +\(e.g. by entering “#” when `consult-async-split-style' is set to \='perl\). For example: -`#consult-mu -- -n 10#github' - -will retrieve the message as the example above, then narrows down the completion table to candidates that match \"github\". - -INITIAL is an optional arg for the initial input in the minibuffer. (passed as INITITAL to `consult-mu--async'). - -For more details on consult--async functionalities, see `consult-grep' and the official manual of consult, here: https://github.com/minad/consult. - -Note that this is the async search directly using the commandline `mu` command and not mu4e-search. As a result, mu4e-headers buffers are not created until a single message is selected (or interacted with using embark, etc.) Previews are shown in a mu4e-view buffer (see `consult-mu-view-buffer-name') attached to an empty mu4e-headers buffer (i.e. `consult-mu-headers-buffer-name'). This allows quick retrieval of many messages (tens of thousands) and previews, but not opening the results in a mu4e-headers buffer. If you want ot open the results in a mu4e-headers buffer for other work flow, then you should use the dynamically collected function `consult-mu' which is slower if searching for many emails but allows follow up interactions in a mu4e-headers buffer. -" +“#consult-mu -- -n 10#github” + +will retrieve the message as the example above, then narrows down the +completion table to candidates that match “github”. + +INITIAL is an optional arg for the initial input in the minibuffer. +\(passed as INITITAL to `consult-mu--async'\). + +For more details on consult--async functionalities, see `consult-grep' and +the official manual of consult, here: +URL `https://github.com/minad/consult' + +Note that this is the async search directly using the commandline `mu` +command and not mu4e-search. As a result, mu4e-headers buffers are not +created until a single message is selected \(or interacted with using +embark, etc.\) Previews are shown in a mu4e-view buffer \(see +`consult-mu-view-buffer-name'\) attached to an empty mu4e-headers buffer +\(i.e. `consult-mu-headers-buffer-name'\). This allows quick retrieval of +many messages \(tens of thousands\) and previews, but not opening the +results in a mu4e-headers buffer. If you want ot open the results in a +mu4e-headers buffer for other work flow, then you should use the +dynamically collected function `consult-mu' which is slower if searching +for many emails but allows follow up interactions in a mu4e-headers +buffer." (interactive) (save-mark-and-excursion (consult-mu--execute-all-marks)) @@ -1509,14 +1655,14 @@ Note that this is the async search directly using the commandline `mu` command a sel))) (defun consult-mu (&optional initial noaction) -"Default consult-mu command. + "Default interactive command. -This is a wrapper function that calls `consult-mu-default-command'. +This is a wrapper function that calls `consult-mu-default-command' with +INITIAL and NOACTION. For example, the `consult-mu-default-command can be set to -`#'consult-mu-dynamic' sets the default behavior to dynamic collection -`#'consult-mu-async' sets the default behavior to async collection -" + `#'consult-mu-dynamic' sets the default behavior to dynamic collection + `#'consult-mu-async' sets the default behavior to async collection" (interactive "P") (funcall consult-mu-default-command initial noaction)) diff --git a/consult-mu.org b/consult-mu.org index df840e7..88a426f 100644 --- a/consult-mu.org +++ b/consult-mu.org @@ -1,10 +1,10 @@ * consult-mu.el :PROPERTIES: -:header-args:emacs-lisp: :results none :lexical t :mkdirp yes :link yes :tangle ./consult-mu.el +:header-args:emacs-lisp: :results none :lexical t :mkdirp yes :comments none :tangle ./consult-mu.el :END: ** Header #+begin_src emacs-lisp -;;; consult-mu.el --- Consult Mu4e asynchronously in GNU Emacs -*- lexical-binding: t -*- +;;; consult-mu.el --- Consult Mu4e asynchronously -*- lexical-binding: t -*- ;; Copyright (C) 2023 Armin Darvish @@ -51,6 +51,7 @@ ;;; Requirements (require 'consult) (require 'mu4e) + #+end_src ** Define Group, Customs, Vars, etc. @@ -65,6 +66,7 @@ :group 'consult :group 'mu4e :prefix "consult-mu-") + #+end_src *** Custom Variables @@ -73,21 +75,25 @@ (defcustom consult-mu-args '("mu") "Command line arguments to call `mu` asynchronously. + The dynamically computed arguments are appended. Can be either a string, or a list of strings or expressions." :group 'consult-mu :type '(choice string (repeat (choice string sexp)))) (defcustom consult-mu-maxnum mu4e-search-results-limit - "Maximum number of results + "Maximum number of results. -This is normally passed to \"--maxnum\" in the command line or is defined by `mu4e-search-results-limit'. By default inherits from `mu4e-search-results-limit'. " +This is normally passed to “--maxnum” in the command line or is defined by +`mu4e-search-results-limit'. By default inherits from +`mu4e-search-results-limit'." :group 'consult-mu :type '(choice (const :tag "Unlimited" -1) (integer :tag "Limit"))) (defcustom consult-mu-search-sort-field mu4e-search-sort-field "What field to sort results by? + By defualt inherits from `mu4e-search-sort-field'." :group 'consult-mu :type '(radio (const :tag "Date" :date) @@ -109,9 +115,9 @@ Each element has the form (HEADER . WIDTH), where HEADER is one of the available headers (see `mu4e-header-info') and WIDTH is the respective width in characters. -A width of nil means \"unrestricted\", and this is best reserved -for the rightmost (last) field. Note that emacs may become very -slow with excessively long lines (1000s of characters), so if you +A width of nil means “unrestricted”, and this is best reserved +for the rightmost \(last\) field. Note that Emacs may become very +slow with excessively long lines \(1000s of characters\), so if you regularly get such messages, you want to avoid fields with nil altogether." :group 'consult-mu @@ -126,31 +132,36 @@ altogether." (defcustom consult-mu-headers-template nil "A template string to make custom header formats. -If non-nil, consult-mu uses this string to format the headers instead of `consult-mu-headers-field'. +If non-nil, `consult-mu' uses this string to format the headers instead of +`consult-mu-headers-field'. -The string should be of the format “%[char][integer]%[char][integer]...”, and allow dynamic insertion of the content. Each “%[char][integer]“ chunk represents a different field and the integer defines the length of the field. for exmaple \"%d15%s50\" means 15 characters for date and 50 charcters for subject. +The string should be of the format “%[char][integer]%[char][integer]...”, +and allow dynamic insertion of the content. Each “%[char][integer]“ chunk +represents a different field and the integer defines the length of the +field. The list of available fields are: - %f sender(s) (e.g. from: field of email) - %t receivers(s) (i.e. to: field of email) - %s subject (i.e. title of email) - %d date (i.e. the date email was sent/received) + %f sender(s) \(e.g. from: field of email\) + %t receivers(s) \(i.e. to: field of email\) + %s subject \(i.e. title of email\) + %d date \(i.e. the date email was sent/received\) %p priority %z size - %i message-id (as defined by mu) - %g flags (as defined by mu) - %G pretty flags (this uses `mu4e~headers-flags-str' to pretify flags) - %x tags (as defined by mu) - %c cc (i.e. cc: field of the email) - %h bcc (i.e. bcc: field of the email) - %r date chaged (as defined by :changed in mu4e) - -For example, the string \"%d13%s50%f17\" would make a header containing 13 characters for Date, 50 characters for Subject, and 20 characters for From field, making a header that looks like this: - -Thu 09 Nov 23 Title of the Email Limited to 50 Characters Onl... example@domain... - -" + %i message-id \(as defined by mu\) + %g flags \(as defined by mu\) + %G pretty flags \(this uses `mu4e~headers-flags-str' to pretify flags\) + %x tags \(as defined by mu\) + %c cc \(i.e. cc: field of the email\) + %h bcc \(i.e. bcc: field of the email\) + %r date chaged \(as defined by :changed in mu4e\) + +For exmaple, “%d15%s50” means 15 characters for date and 50 charcters for +subject, and “%d13%s37%f17” would make a header containing 13 characters +for Date, 37 characters for Subject, and 20 characters for From field, +making a header that looks like this: + +Thu 09 Nov 23 Title of the Email Limited to 50 Char... example@domain..." :group 'consult-mu :type '(choice (const :tag "Fromatted String" :format "%{%%d13%%s50%%f17%}") (function :tag "Custom Function"))) @@ -158,10 +169,8 @@ Thu 09 Nov 23 Title of the Email Limited to 50 Characters Onl... example@domai (defcustom consult-mu-search-sort-direction mu4e-search-sort-direction "Direction to sort by a symbol. -By defualt inherits from 'mu4e-search-sort-direction'. and can either be -`descending' (sorting Z->A) -or -`ascending' (sorting A->Z)." +By defualt inherits from `mu4e-search-sort-direction', and can either be +\='descending (sorting Z->A) or \='ascending (sorting A->Z)." :group 'consult-mu :type '(radio (const ascending) @@ -170,36 +179,37 @@ or (defcustom consult-mu-search-threads mu4e-search-threads "Whether to calculate threads for search results. -By defualt inherits from 'mu4e-search-threads'. + +By defualt inherits from `mu4e-search-threads'. Note that per mu4e docs: When threading is enabled, the headers are exclusively sorted -chronologically (:date) by the newest message in the thread. -" +chronologically (:date) by the newest message in the thread." :group 'consult-mu :type 'boolean) (defcustom consult-mu-group-by nil "What field to use to group the results in the minibuffer. -By default it is set to :date. But can be any of: +By default it is set to :date, but can be any of: :subject group by subject :from group by the name/email the sender(s) :to group by name/email of the reciver(s) :date group by date - :time group by the time of email (i.e. hour, minute, seconds) + :time group by the time of email \(i.e. hour, minute, seconds\) :datetime group by date and time of the email - :year group by the year of the email (i.e. 2023, 2022, ...) - :month group by the month of the email (i.e. Jan, Feb, ..., Dec) - :week group by the week number of the email (.i.e. 1st week, 2nd week, ... 52nd week) + :year group by the year of the email \(i.e. 2023, 2022, ...\) + :month group by the month of the email \(i.e. Jan, Feb, ..., Dec\) + :week group by the week number of the email + \(i.e. 1st week, 2nd week, ... 52nd week\) :day-of-week group by the day email was sent (i.e. Mondays, Tuesdays, ...) :day group by the day email was sent (similar to :day-of-week) :size group by the file size of the email :flags group by flags (as defined by mu) :tags group by tags (as defined by mu) - :changed group by the date changed (as defined by :changed field in mu4e) -" + :changed group by the date changed + \(as defined by :changed field in mu4e\)" :group 'consult-mu :type '(radio (const :date) (const :subject) @@ -229,14 +239,14 @@ By default it is set to :date. But can be any of: :type 'boolean) (defcustom consult-mu-headers-buffer-name "*consult-mu-headers*" - "Default name for HEADERS buffer explicitly for consult-mu. + "Default name for HEADERS buffer explicitly for `consult-mu'. For more info see `mu4e-headers-buffer-name'." :group 'consult-mu :type 'string) (defcustom consult-mu-view-buffer-name "*consult-mu-view*" - "Default name for VIEW buffer explicitly for consult-mu. + "Default name for VIEW buffer explicitly for `consult-mu'. For more info see `mu4e-view-buffer-name'." :group 'consult-mu @@ -245,8 +255,9 @@ For more info see `mu4e-view-buffer-name'." (defcustom consult-mu-preview-key consult-preview-key "Preview key for `consult-mu'. -This is similar to `consult-preview-key' but explicitly for consult-mu." - :type '(choice (const :tag "Any key" any) +This is similar to `consult-preview-key' but explicitly for `consult-mu'." + :group 'consult-mu + :type '(choice (symbol :tag "Any key" 'any) (list :tag "Debounced" (const :debounce) (float :tag "Seconds" 0.1) @@ -266,7 +277,7 @@ This is similar to `consult-preview-key' but explicitly for consult-mu." This defines whether `consult-mu--reply-action' should reply to all or not." :group 'consult-mu - :type '(choice (const :tag "Ask for confirmation" 'ask) + :type '(choice (symbol :tag "Ask for confirmation" 'ask) (const :tag "Do not reply to all" nil) (const :tag "Always reply to all" t))) @@ -274,9 +285,9 @@ This defines whether `consult-mu--reply-action' should reply to all or not." "The function that is used when selecting a message. By default it is bound to `consult-mu--view-action'." :group 'consult-mu - :type '(choice (function :tag "(Default) View Message in Mu4e Buffers" #'consult-mu--view-action) - (function :tag "Reply to Message" #'consult-mu--reply-action) - (function :tag "Forward Message" #'consult-mu--forward-action) + :type '(choice (function :tag "(Default) View Message in Mu4e Buffers" consult-mu--view-action) + (function :tag "Reply to Message" consult-mu--reply-action) + (function :tag "Forward Message" consult-mu--forward-action) (function :tag "Custom Function"))) (defcustom consult-mu-default-command #'consult-mu-dynamic @@ -315,7 +326,10 @@ The idea is Taken from https://github.com/seanfarley/counsel-mu.") "List of Favorite searches for `consult-mu-async'.") (defvar consult-mu--override-group nil -"Override grouping in `consult-mu' based on user input.") + "Override grouping in `consult-mu' based on user input.") + +(defvar consult-mu--mail-headers '("Subject" "From" "To" "From/To" "Cc" "Bcc" "Reply-To" "Date" "Attachments" "Tags" "Flags" "Maildir" "Summary" "List" "Path" "Size" "Message-Id" "List-Id" "Changed") + "List of possible headers in a message.") #+end_src @@ -325,75 +339,77 @@ The idea is Taken from https://github.com/seanfarley/counsel-mu.") (defface consult-mu-highlight-match-face `((t :inherit 'consult-highlight-match)) - "Highlight match face in `consult-mu''s view buffer. + "Highlight match face in `consult-mu' view buffer. By default inherits from `consult-highlight-match'. -This is used to highlight matches of search queries in the minibufffer completion list.") +This is used to highlight matches of search queries in the minibufffer +completion list.") (defface consult-mu-preview-match-face `((t :inherit 'consult-preview-match)) - "Preview match face in `consult-mu''s preview buffers. + "Preview match face in `consult-mu' preview buffers. By default inherits from `consult-preview-match'. -This is used to highlight matches of search query terms in preview buffers \(i.e. `consult-mu-view-buffer-name'\).") +This is used to highlight matches of search query terms in preview buffers +\(i.e. `consult-mu-view-buffer-name'\).") (defface consult-mu-default-face `((t :inherit 'default)) - "Default face in `consult-mu''s minibuffer annotations. + "Default face in `consult-mu' minibuffer annotations. By default inherits from `default' face.") (defface consult-mu-subject-face `((t :inherit 'font-lock-keyword-face)) - "Subject face in `consult-mu''s minibuffer annotations. + "Subject face in `consult-mu' minibuffer annotations. By default inherits from `font-lock-keyword-face'.") (defface consult-mu-sender-face `((t :inherit 'font-lock-variable-name-face)) - "Contact face in `consult-mu''s minibuffer annotations. + "Contact face in `consult-mu' minibuffer annotations. By default inherits from `font-lock-variable-name-face'.") (defface consult-mu-receiver-face `((t :inherit 'font-lock-variable-name-face)) - "Contact face in `consult-mu''s minibuffer annotations. + "Contact face in `consult-mu' minibuffer annotations. By default inherits from `font-lock-variable-name-face'.") (defface consult-mu-date-face `((t :inherit 'font-lock-preprocessor-face)) - "Date face in `consult-mu''s minibuffer annotations. + "Date face in `consult-mu' minibuffer annotations. By default inherits from `font-lock-preprocessor-face'.") (defface consult-mu-count-face `((t :inherit 'font-lock-string-face)) - "Count face in `consult-mu''s minibuffer annotations. + "Count face in `consult-mu' minibuffer annotations. By default inherits from `font-lock-string-face'.") (defface consult-mu-size-face `((t :inherit 'font-lock-string-face)) - "Size face in `consult-mu''s minibuffer annotations. + "Size face in `consult-mu' minibuffer annotations. By default inherits from `font-lock-string-face'.") (defface consult-mu-tags-face `((t :inherit 'font-lock-comment-face)) - "Tags/Comments face in `consult-mu''s minibuffer annotations. + "Tags/Comments face in `consult-mu' minibuffer annotations. By default inherits from `font-lock-comment-face'.") (defface consult-mu-flags-face `((t :inherit 'font-lock-function-call-face)) - "Flags face in `consult-mu''s minibuffer annotations. + "Flags face in `consult-mu' minibuffer annotations. By default inherits from `font-lock-function-call-face'.") (defface consult-mu-url-face `((t :inherit 'link)) - "URL face in `consult-mu''s minibuffer annotations; + "URL face in `consult-mu' minibuffer annotations; By default inherits from `link'.") @@ -406,43 +422,47 @@ This section includes functions (utilities, mu4e hacks, ...). ***** pulse-regexp #+begin_src emacs-lisp (defun consult-mu--pulse-regexp (regexp) - "Finds and pulses REGEXP" + "Find and pulse REGEXP." (goto-char (point-min)) (while (re-search-forward regexp nil t) (when-let* ((m (match-data)) - (beg (car m)) - (end (cadr m)) - (ov (make-overlay beg end)) - (pulse-delay 0.075)) + (beg (car m)) + (end (cadr m)) + (ov (make-overlay beg end)) + (pulse-delay 0.075)) (pulse-momentary-highlight-overlay ov 'highlight)))) + #+end_src ***** pulse-region #+begin_src emacs-lisp (defun consult-mu--pulse-region (beg end) - "Finds and pulses region from BEG to END" + "Find and pulse region from BEG to END." (let ((ov (make-overlay beg end)) (pulse-delay 0.075)) - (pulse-momentary-highlight-overlay ov 'highlight))) + (pulse-momentary-highlight-overlay ov 'highlight))) #+end_src ***** pulse-line #+begin_src emacs-lisp (defun consult-mu--pulse-line () - "Pulses line at point momentarily" + "Pulse line at point momentarily." (let* ((pulse-delay 0.055) (ov (make-overlay (car (bounds-of-thing-at-point 'line)) (cdr (bounds-of-thing-at-point 'line))))) (pulse-momentary-highlight-overlay ov 'highlight))) + #+end_src **** formatting strings ***** fix string length #+begin_src emacs-lisp (defun consult-mu--set-string-width (string width &optional prepend) - "Sets the STRING width to a fixed value, WIDTH. + "Set the STRING width to a fixed value, WIDTH. -If the STRING is longer than WIDTH, it truncates the string and adds ellipsis, \"...\". If the string is shorter it adds whitespace to the string. -If PREPEND is non-nil, it truncates or adds whitespace from the beginning of string, instead of the end." +If the STRING is longer than WIDTH, it truncates the string and adds +ellipsis, “...”. If the string is shorter, it adds whitespace to the +string. If PREPEND is non-nil, it truncates or adds whitespace from the +beginning of string, instead of the end." (let* ((string (format "%s" string)) (w (string-width string))) (when (< w width) @@ -456,10 +476,12 @@ If PREPEND is non-nil, it truncates or adds whitespace from the beginning of str string)) (defun consult-mu--justify-left (string prefix maxwidth) - "Sets the width of STRING+PREFIX justified from left. -It uses `consult-mu--set-string-width' and sets the width of the concatenate of STRING+PREFIX (e.g. `(concat prefix string)`) within MAXWIDTH. This is used for aligning marginalia info in minibuffer when using `consult-mu'." - (let ((s (string-width string)) - (w (string-width prefix))) + "Set the width of STRING+PREFIX justified from left. + +Use `consult-mu--set-string-width' to the width of the concatenate of +STRING+PREFIX \(e.g. “(concat prefix string)”\) within MAXWIDTH. This is +used for aligning marginalia info in the minibuffer." + (let ((w (string-width prefix))) (if (> maxwidth w) (consult-mu--set-string-width string (- maxwidth w) t) string))) @@ -468,12 +490,12 @@ It uses `consult-mu--set-string-width' and sets the width of the concatenate of ***** highlight match with text-properties #+begin_src emacs-lisp (defun consult-mu--highlight-match (regexp str ignore-case) - "Highlights REGEXP in STR. + "Highlight REGEXP in STR. -If a regular expression contains capturing groups, only these are highlighted. -If no capturing groups are used highlight the whole match. Case is ignored -if IGNORE-CASE is non-nil. -(This is adapted from `consult--highlight-regexps'.)" +If a REGEXP contains a capturing group, only the captured group is +highlighted, otherwise, the whole match is highlighted. +Case is ignored if IGNORE-CASE is non-nil. +\(This is adapted from `consult--highlight-regexps'.\)" (let ((i 0)) (while (and (let ((case-fold-search ignore-case)) (string-match regexp str i)) @@ -487,59 +509,69 @@ if IGNORE-CASE is non-nil. 'consult-mu-highlight-match-face nil str)) (setq m (cddr m)))))) str) + #+end_src ***** highlight match with overlay #+begin_src emacs-lisp (defun consult-mu--overlay-match (match-str buffer ignore-case) - "Highlights MATCH-STR in BUFFER using an overlay. + "Highlight MATCH-STR in BUFFER using an overlay. + If IGNORE-CASE is non-nil, it uses case-insensitive match. -This is used to highlight matches to use rqueries when viewing emails in consult-mu. See `consult-mu-overlays-toggle' for toggling highligths on/off." -(with-current-buffer (or (get-buffer buffer) (current-buffer)) - (remove-overlays (point-min) (point-max) 'consult-mu-overlay t) - (goto-char (point-min)) - (let ((case-fold-search ignore-case) - (consult-mu-overlays (list))) - (while (search-forward match-str nil t) - (when-let* ((m (match-data)) - (beg (car m)) - (end (cadr m)) - (overlay (make-overlay beg end))) - (overlay-put overlay 'consult-mu-overlay t) - (overlay-put overlay 'face 'consult-mu-highlight-match-face)))))) +This is used to highlight matches to use queries when viewing emails. See +`consult-mu-overlays-toggle' for toggling highligths on/off." + (with-current-buffer (or (get-buffer buffer) (current-buffer)) + (remove-overlays (point-min) (point-max) 'consult-mu-overlay t) + (goto-char (point-min)) + (let ((case-fold-search ignore-case)) + (while (search-forward match-str nil t) + (when-let* ((m (match-data)) + (beg (car m)) + (end (cadr m)) + (overlay (make-overlay beg end))) + (overlay-put overlay 'consult-mu-overlay t) + (overlay-put overlay 'face 'consult-mu-highlight-match-face)))))) (defun consult-mu-overlays-toggle (&optional buffer) - "Toggles overlay highlights in consult-mu view/preview buffers." -(interactive) -(let ((buffer (or buffer (current-buffer)))) -(with-current-buffer buffer - (dolist (o (overlays-in (point-min) (point-max))) - (when (overlay-get o 'consult-mu-overlay) - (if (and (overlay-get o 'face) (eq (overlay-get o 'face) 'consult-mu-highlight-match-face)) - (overlay-put o 'face nil) - (overlay-put o 'face 'consult-mu-highlight-match-face))))))) + "Toggle overlay highlight in BUFFER. + +BUFFER defaults to `current-buffer'." + (interactive) + (let ((buffer (or buffer (current-buffer)))) + (with-current-buffer buffer + (dolist (o (overlays-in (point-min) (point-max))) + (when (overlay-get o 'consult-mu-overlay) + (if (and (overlay-get o 'face) (eq (overlay-get o 'face) 'consult-mu-highlight-match-face)) + (overlay-put o 'face nil) + (overlay-put o 'face 'consult-mu-highlight-match-face))))))) + #+end_src **** format date #+begin_src emacs-lisp (defun consult-mu--format-date (string) -"Format the date STRING from mu output. + "Format the date STRING from mu output. -STRING is the output form mu command. for example from `mu find query --fields d` -Returns the date in the format Day-of-Week Month Day Year Time (e.g. Sat Nov 04 2023 09:46:54)" +STRING is the output form a mu command, for example: +`mu find query --fields d` +Returns the date in the format Day-of-Week Month Day Year Time +\(e.g. Sat Nov 04 2023 09:46:54\)" (let ((string (replace-regexp-in-string " " "0" string))) (format "%s %s %s" (substring string 0 10) (substring string -4 nil) (substring string 11 -4)))) + #+end_src **** flags to string #+begin_src emacs-lisp (defun consult-mu-flags-to-string (FLAG) - "Coverts FLAGS, from mu output to strings. + "Covert FLAGS, from mu output to strings. -FLAG is the output form mu command in the terminal. For example `mu find query --fields g`. -This function converts each character in FLAG to an expanded string of the flag and returns the list of these strings." +FLAG is the output form mu command in the terminal, for example: + `mu find query --fields g`. +This function converts each character in FLAG to an expanded string of the +flag and returns the list of these strings." (cl-loop for c across FLAG collect (pcase (string c) @@ -558,40 +590,48 @@ This function converts each character in FLAG to an expanded string of the flag ("q" 'personal) ("c" 'calendar) (_ nil)))) + #+end_src **** extract email from string #+begin_src emacs-lisp (defun consult-mu--message-extract-email-from-string (string) - "Finds and returns the first email address in the STRING" + "Find and return the first email address in the STRING." (when (stringp string) (string-match "[a-zA-Z0-9\_\.\+\-]+@[a-zA-Z0-9\-]+\.[a-zA-Z0-9\-\.]+" string) (match-string 0 string))) + #+end_src **** split string of emails to list of emails #+begin_src emacs-lisp (defun consult-mu--message-emails-string-to-list (string) - "Converts comma-separated STRING containing email addresses to list of emails" - (when (stringp string) - (remove '(" " "\s" "\t") (mapcar #'consult-mu--message-extract-email-from-string - (split-string string ",\\|;\\|\t" t))))) + "Convert comma-separated STRING of email addresses to a list." + (when (stringp string) + (remove '(" " "\s" "\t") + (mapcar #'consult-mu--message-extract-email-from-string + (split-string string ",\\|;\\|\t" t))))) + #+end_src **** get header field from message #+begin_src emacs-lisp (defun consult-mu--message-get-header-field (&optional field) - "Retrive FIELD header from the message/mail in the current buffer" + "Retrive FIELD header from the message/mail in the current buffer." (save-match-data (save-excursion (when (or (derived-mode-p 'message-mode) (derived-mode-p 'mu4e-view-mode) (derived-mode-p 'org-msg-edit-mode) (derived-mode-p 'mu4e-compose-mode)) - (let ((field (or field - (s-lower-camel-case (consult--read '("Subject" "From" "To" "Cc" "Bcc" "Reply-To" "Date" "Attachments" "Tags" "Flags" "Maildir" "Summary") - :prompt "Header Field: "))))) - (if (equal field "attachments") (setq field "\\(attachment\\|attachments\\)")) + (let* ((case-fold-search t) + (header-regexp (mapconcat (lambda (str) (concat "\n" str ": ")) + consult-mu--mail-headers "\\|")) + (field (or (downcase field) + (downcase (consult--read consult-mu--mail-headers + :prompt "Header Field: "))))) + (if (string-prefix-p "attachment" field) (setq field "\\(attachment\\|attachments\\)")) (goto-char (point-min)) - (let* ((match (re-search-forward (concat "^" field ": \\(?1:[[:ascii:][:nonascii:]]+?\\)\n.*?: ") nil t)) - (str (if match (string-trim (match-string 1))))) + (message-goto-body) + (let* ((match (re-search-backward (concat "^" field ": \\(?1:[[:ascii:][:nonascii:]]*?\\)\n\\(.*?:\\|\n\\)") nil t)) + (str (if (and match (match-string 1)) (string-trim (match-string 1))))) (if (string-empty-p str) nil str))))))) #+end_src @@ -599,38 +639,32 @@ This function converts each character in FLAG to an expanded string of the flag **** append-handler #+begin_src emacs-lisp (defun consult-mu--headers-append-handler (msglst) - "Overrides `mu4e~headers-append-handler' for `consult-mu'. -This is to ensure that buffer handling is done right for consult-mu. + "Append one-line descriptions of messages in MSGLST. -From mu4e docs: +This is used to override `mu4e~headers-append-handler' to ensure that +buffer handling is done right for `consult-mu'." + (with-current-buffer "*consult-mu-headers*" + (let ((inhibit-read-only t)) + (seq-do + ;; I use mu4e-column-faces and it overrides the default append-handler. To get the same effect I check if mu4e-column-faces is active and enabled. + (if (and (featurep 'mu4e-column-faces) mu4e-column-faces-mode) + (lambda (msg) + (mu4e-column-faces--insert-header msg (point-max))) + (lambda (msg) + (mu4e~headers-insert-header msg (point-max)))) + msglst)))) -Append one-line descriptions of messages in MSGLIST. -Do this at the end of the headers-buffer. -" - (with-current-buffer "*consult-mu-headers*" - (let ((inhibit-read-only t)) - (seq-do - ;; I use mu4e-column-faces and it overrides the default append-handler. To get the same effect I check if mu4e-column-faces is active and enabled. - (if (and (featurep 'mu4e-column-faces) mu4e-column-faces-mode) - (lambda (msg) - (mu4e-column-faces--insert-header msg (point-max))) - (lambda (msg) - (mu4e~headers-insert-header msg (point-max)))) - msglst)))) #+end_src **** view-msg #+begin_src emacs-lisp (defun consult-mu--view-msg (msg &optional buffername) - "Overrides `mu4e-view' for `consult-mu'. -This is to ensure that buffer handling is done right for consult-mu. + "Display the message MSG in a buffer with BUFFERNAME. -From mu4e docs: +BUFFERNAME defaults to `consult-mu-view-buffer-name'. -Display the message MSG in a new buffer, and keep in sync with `consult-mu-headers-buffer-name' buffer. -\"In sync\" here means that moving to the next/previous message -in the the message view affects `consult-mu-headers-buffer-name', as does marking etc. -" +This s used to overrides `mu4e-view' to ensure that buffer handling is done +right for `consult-mu'." (let* ((linked-headers-buffer (mu4e-get-headers-buffer "*consult-mu-headers*" t)) (mu4e-view-buffer-name (or buffername consult-mu-view-buffer-name))) (setq gnus-article-buffer (mu4e-get-view-buffer linked-headers-buffer t)) @@ -652,13 +686,12 @@ in the the message view affects `consult-mu-headers-buffer-name', as does markin **** headers-clear #+begin_src emacs-lisp (defun consult-mu--headers-clear (&optional text) - "Overrides `mu4e~headers-clear' for `consult-mu'. -This is to ensure that buffer handling is done right for consult-mu. + "Clear the headers buffer and related data structures. -From mu4e docs: +Optionally, show TEXT. -Clear the headers buffer and related data structures. -Optionally, show TEXT. " +This is used to override `mu4e~headers-clear' to ensure that buffer +handling is done right for `consult-mu'." (setq mu4e~headers-render-start (float-time) mu4e~headers-hidden 0) (with-current-buffer "*consult-mu-headers*" @@ -668,26 +701,30 @@ Optionally, show TEXT. " (when text (goto-char (point-min)) (insert (propertize text 'face 'mu4e-system-face 'intangible t)))))) + #+end_src **** set mu4e search properties from opts #+begin_src emacs-lisp (defun consult-mu--set-mu4e-search-sortfield (opts) - "Dynamically sets the `mu4e-search-sort-field' based on user input. + "Dynamically set the `mu4e-search-sort-field' based on user input. + Uses user input (i.e. from `consult-mu' command) to define the sort field. -OPTS is the command line options for mu and can be set by entering options in the minibuffer input. For more details refer to `cpnsult-grep' and consult async documentation. +OPTS is the command line options for mu and can be set by entering options +in the minibuffer input. For more details, refer to `consult-grep' and +consult async documentation. For example if the user enters the following in the minibuffer: - `#query -- --maxnum 400 --sortfield from --reverse --include-related --skip-dups --threads' -mu4e-search-sort-field is set to :from +“#query -- --maxnum 400 --sortfield from” + +`mu4e-search-sort-field' is set to :from Note that per mu4e docs: When threading is enabled, the headers are exclusively sorted -chronologically (:date) by the newest message in the thread. -" +chronologically (:date) by the newest message in the thread." (let* ((sortfield (cond ((member "-s" opts) (nth (+ (cl-position "-s" opts :test 'equal) 1) opts)) ((member "--sortfield" opts) (nth (+ (cl-position "--sortfield" opts :test 'equal) 1) opts)) @@ -715,16 +752,20 @@ chronologically (:date) by the newest message in the thread. consult-mu-search-sort-field)))) (defun consult-mu--set-mu4e-search-sort-direction (opts) -"Dynamically sets the `mu4e-search-sort-direction' based on user input. -Uses user input (i.e. from `consult-mu' command) to define the sort field. + "Dynamically set the `mu4e-search-sort-direction' based on user input. -OPTS is the command line options for mu and can be set by entering options in the minibuffer input. For more details refer to `cpnsult-grep' and consult async documentation. +Uses user input \(i.e. from `consult-mu' command\) to define the sort field. -For example if the user enters the following in the minibuffer: - `#query -- --maxnum 400 --sortfield from --reverse --include-related --skip-dups --threads' +OPTS is the command line options for mu and can be set by entering options +in the minibuffer input. For more details, refer to `consult-grep' and +consult async documentation. + +For example, if the user enters the following in the minibuffer: + +“#query -- --maxnum 400 --sortfield from --reverse” -the `mu4e-search-sort-direction' is reversed; if it is set to 'ascending, it is toggled to 'descending and vise versa. -" +The `mu4e-search-sort-direction' is reversed; If it is set to +\='ascending, it is toggled to \='descending and vise versa." (if (or (member "-z" opts) (member "--reverse" opts)) (pcase consult-mu-search-sort-direction ('descending @@ -734,79 +775,104 @@ the `mu4e-search-sort-direction' is reversed; if it is set to 'ascending, it is consult-mu-search-sort-direction)) (defun consult-mu--set-mu4e-skip-duplicates (opts) - "Dynamically sets the `mu4e-search-skip-duplicates' based on user input. -Uses user input (i.e. from `consult-mu' command) to define the sort field. + "Dynamically set the `mu4e-search-skip-duplicates' based on user input. -OPTS is the command line options for mu and can be set by entering options in the minibuffer input. For more details refer to `cpnsult-grep' and consult async documentation. +Uses user input \(i.e. from `consult-mu' command\) to define whether to +skip duplicates. -For example if the user enters the following in the minibuffer: - `#query -- --maxnum 400 --sortfield from --reverse --include-related --skip-dups --threads' +OPTS is the command line options for mu and can be set by entering options +in the minibuffer input. For more details, refer to `consult-grep' and +consult async documentation. -the `mu4e-search-skip-duplicates' is set to t. -" +For example, if the user enters the following in the minibuffer: + +“#query -- --maxnum 400 --skip-dups” + +The `mu4e-search-skip-duplicates' is set to t." (if (or (member "--skip-dups" opts) mu4e-search-skip-duplicates) t nil)) (defun consult-mu--set-mu4e-results-limit (opts) - "Dynamically sets the `mu4e-search-results-limit' based on user input. -Uses user input (i.e. from `consult-mu' command) to define the maximum number of results. + "Dynamically set the `mu4e-search-results-limit' based on user input. -OPTS is the command line options for mu and can be set by entering options in the minibuffer input. For more details refer to `consult-mu' or `consult-mu-async' documentation. -For example if the user enters the following in the minibuffer: - `#query -- --maxnum 400 --sortfield from --reverse --include-related --skip-dups --threads' +Uses user input \(i.e. from `consult-mu' command\) to define the number of +results shown. -the `mu4e-search-results-limit' is set to 400. -" - (cond - ((member "-n" opts) (string-to-number (nth (+ (cl-position "-n" opts :test 'equal) 1) opts))) - ((member "--maxnum" opts) (string-to-number (nth (+ (cl-position "--maxnum" opts :test 'equal) 1) opts))) - (t consult-mu-maxnum))) +OPTS is the command line options for mu and can be set by entering options +in the minibuffer input. For more details, refer to `consult-grep' and +consult async documentation. +For example, if the user enters the following in the minibuffer: -(defun consult-mu--set-mu4e-include-related (opts) - "Dynamically sets the `mu4e-search-include-related' based on user input. -Uses user input (i.e. from `consult-mu' command) to define the include-related property. +“#query -- --maxnum 400” -OPTS is the command line options for mu and can be set by entering options in the minibuffer input. For more details refer to `consult-mu' or `consult-mu-async' documentation. +The `mu4e-search-results-limit' is set to 400." + (cond + ((member "-n" opts) (string-to-number (nth (+ (cl-position "-n" opts :test 'equal) 1) opts))) + ((member "--maxnum" opts) (string-to-number (nth (+ (cl-position "--maxnum" opts :test 'equal) 1) opts))) + (t consult-mu-maxnum))) -For example if the user enters the following in the minibuffer: - `#query -- --maxnum 400 --sortfield from --reverse --include-related --skip-dups --threads' -the `mu4e-search-include-related' is set to t. -" - (if (or (member "-r" opts) (member "--include-related" opts) mu4e-search-include-related) t nil)) +(defun consult-mu--set-mu4e-include-related (opts) + "Dynamically set the `mu4e-search-include-related' based on user input. +Uses user input \(i.e. from `consult-mu' command\) to define whether to +include related messages. +OPTS is the command line options for mu and can be set by entering options +in the minibuffer input. For more details, refer to `consult-grep' and +consult async documentation. -(defun consult-mu--set-mu4e-threads (opts) -"Sets the `mu4e-search-threads' based on `mu4e-search-sort-field'. +For example if the user enters the following in the minibuffer: -Note that per mu4e docs, when threading is enabled, the headers are exclusively sorted by date. -Here the logic is reversed in order to allow dynamically sorting by fields other than date (even when threads are enabled). +“#query -- --include-related” -In other words if the sort-field is not the :date threading is disabled (because otherwise sort field will be ignored anyway).This allows the user to use command line arguments to sort messages by fields other than the date. For example the user can enter the following in the minibuffer input to sort by subject +The `mu4e-search-include-related' is set to t." + (if (or (member "-r" opts) (member "--include-related" opts) mu4e-search-include-related) t nil)) -`#query -- --sortfield from' -When the sort-field is :date, then `consult-mu-search-threads' is used. If `consult-mu-search-threads' is set to nil, the user can use command line arguments (a.k.a. -t or --thread) to enable it dynamically. -" -(cond - ((not (equal mu4e-search-sort-field :date)) - nil) - ((or (member "-t" opts) (member "--threads" opts) consult-mu-search-threads) - t))) -#+end_src +(defun consult-mu--set-mu4e-threads (opts) + "Set the `mu4e-search-threads' based on `mu4e-search-sort-field'. + +Uses user input \(i.e. from `consult-mu' command\) to define whether to +show threads. + +OPTS is the command line options for mu and can be set by entering options +in the minibuffer input. For more details, refer to `consult-grep' and +consult async documentation. + +Note that per mu4e docs, when threading is enabled, the headers are +exclusively sorted by date. Here the logic is reversed in order to allow +dynamically sorting by fields other than date \(even when threads are +enabled\). In other words, if the sort-field is not the :date, threading +is disabled because otherwise sort field will be ignored. This allows the +user to use command line arguments to sort messages by fields other than +the date. For example, the user can enter the following in the minibuffer +input to sort by subject + +“#query -- --sortfield subject” + +When the sort-field is :date, the default setting, +`consult-mu-search-threads' is used, and if that is set to nil, the user +can use command line arguments \(a.k.a. -t or --thread\) to enable it +dynamically." + (cond + ((not (equal mu4e-search-sort-field :date)) + nil) + ((or (member "-t" opts) (member "--threads" opts) consult-mu-search-threads) + t))) +#+end_src **** update headers #+begin_src emacs-lisp (defun consult-mu--update-headers (query ignore-history msg type) - "Search for QUERY, and updates `consult-mu-headers-buffer-name' buffer. + "Search for QUERY, and update `consult-mu-headers-buffer-name' buffer. -If IGNORE-HISTORY is true, does *not* update the query history stack, `mu4e--search-query-past'. - -If MSGID is non-nil, put the cursor on message with MSGID. -" +If IGNORE-HISTORY is true, does *not* update the query history stack, +`mu4e--search-query-past'. +If MSG is non-nil, put the cursor on MSG. +TYPE can be either \=':dynamic or \=':async" (consult-mu--execute-all-marks) (cl-letf* (((symbol-function #'mu4e~headers-append-handler) #'consult-mu--headers-append-handler)) (unless (mu4e-running-p) (mu4e--server-start)) @@ -814,7 +880,6 @@ If MSGID is non-nil, put the cursor on message with MSGID. (view-buffer (get-buffer consult-mu-view-buffer-name)) (expr (car (consult--command-split (substring-no-properties query)))) (rewritten-expr (funcall mu4e-query-rewrite-function expr)) - (maxnum (unless mu4e-search-full mu4e-search-results-limit)) (mu4e-headers-fields consult-mu-headers-fields)) (pcase type (:dynamic) @@ -841,7 +906,7 @@ If MSGID is non-nil, put the cursor on message with MSGID. (consult-mu--headers-clear mu4e~search-message) (setq mu4e~headers-search-start (float-time)) - (pcase-let* ((`(,arg . ,opts) (consult--command-split query)) + (pcase-let* ((`(,_arg . ,opts) (consult--command-split query)) (mu4e-search-sort-field (consult-mu--set-mu4e-search-sortfield opts)) (mu4e-search-sort-direction (consult-mu--set-mu4e-search-sort-direction opts)) (mu4e-search-skip-duplicates (consult-mu--set-mu4e-skip-duplicates opts)) @@ -860,17 +925,21 @@ If MSGID is non-nil, put the cursor on message with MSGID. (equal (buffer-substring (point-min) (+ (point-min) (length mu4e~search-message))) mu4e~search-message) (not (or (equal (buffer-substring (- (point-max) (length mu4e~no-matches)) (point-max)) mu4e~no-matches) (equal (buffer-substring (- (point-max) (length mu4e~end-of-results)) (point-max)) mu4e~end-of-results)))) (sleep-for 0.005)))))))) + #+end_src **** execute-marks #+begin_src emacs-lisp (defun consult-mu--execute-all-marks (&optional no-confirmation) - "Execute the actions for all marked messages in `consult-mu-headers-buffer-name' buffer. + "Execute the actions for all marked messages. + +Executes all actions for marked messages in the buffer +`consult-mu-headers-buffer-name'. If NO-CONFIRMATION is non-nil, don't ask user for confirmation. -This is similar to `mu4e-mark-execute-all' but, with buffer/window handling set accordingly for consult-mu. -" +This is similar to `mu4e-mark-execute-all' but, with buffer/window +handling set accordingly for `consult-mu'." (interactive "P") (when-let* ((buf (get-buffer consult-mu-headers-buffer-name))) (with-current-buffer buf @@ -882,97 +951,103 @@ This is similar to `mu4e-mark-execute-all' but, with buffer/window handling set (unless (one-window-p) (delete-other-windows)) (mu4e-mark-execute-all no-confirmation) (quit-window)))))))) + #+end_src **** goto-message by message-id #+begin_src emacs-lisp (defun consult-mu--headers-goto-message-id (msgid) - "Jumps to message with MSGID + "Jump to message with MSGID. -in `consult-mu-headers-buffer-name' buffer." +This is done in `consult-mu-headers-buffer-name' buffer." (when-let ((buffer consult-mu-headers-buffer-name)) (with-current-buffer buffer (setq mu4e-view-buffer-name consult-mu-view-buffer-name) (mu4e-headers-goto-message-id msgid)))) + #+end_src **** get message form message-id #+begin_src emacs-lisp (defun consult-mu--get-message-by-id (msgid) - "Finds the message with MSGID and returns the mu4e MSG plist for it." + "Find the message with MSGID and return the mu4e MSG plist for it." (cl-letf* (((symbol-function #'mu4e-view) #'consult-mu--view-msg)) - (when-let ((buffer consult-mu-headers-buffer-name)) - (with-current-buffer buffer - (setq mu4e-view-buffer-name consult-mu-view-buffer-name) - (mu4e-headers-goto-message-id msgid) - (mu4e-message-at-point))))) + (when-let ((buffer consult-mu-headers-buffer-name)) + (with-current-buffer buffer + (setq mu4e-view-buffer-name consult-mu-view-buffer-name) + (mu4e-headers-goto-message-id msgid) + (mu4e-message-at-point))))) + #+end_src **** make or retrive from/to/cc/bcc plist #+begin_src emacs-lisp (defun consult-mu--contact-string-to-plist (string) "Convert STRING for contacts to plist. -STRING is the output form mu command. for example from `mu find query --fields f` -Returns plist with :email and :name keys. +STRING is the output form mu command, for example from: +`mu find query --fields f` + +Returns a plist with \=':email and \':name keys. For example -\"John Doe \" +“John Doe ” will be converted to -(:name \"John Doe\" :email \"john.doe@example.com\") - -" -(let* ((string (replace-regexp-in-string ">,\s\\|>;\s" ">\n" string)) - (list (string-split string "\n" t))) +\(:name “John Doe” :email “john.doe@example.com”\)" + (let* ((string (replace-regexp-in-string ">,\s\\|>;\s" ">\n" string)) + (list (split-string string "\n" t))) (mapcar (lambda (item) (cond ((string-match "\\(?2:.*\\)\s+<\\(?1:.+\\)>" item) (list :email (or (match-string 1 item) nil) :name (or (match-string 2 item) nil))) ((string-match "^\\(?1:[a-zA-Z0-9\_\.\+\-]+@[a-zA-Z0-9\-]+\.[a-zA-Z0-9\-\.]+\\)" item) - (list :email (or (match-string 1 item) nil) :name nil)) + (list :email (or (match-string 1 item) nil) :name nil)) (t (list :email (format "%s" item) :name nil)))) list))) + #+end_src #+begin_src emacs-lisp (defun consult-mu--contact-name-or-email (contact) -"Retrieve name or email of CONTACT. + "Retrieve name or email of CONTACT. -Looks at the contact plist (e.g. (:name \"John Doe\" :email \"john.doe@example.com\") ) and returns the name. If the name is missing returns the email address. -" +Looks at the contact plist \(e.g. (:name “John Doe” :email +“john.doe@example.com”)\) and returns the name. If the name is missing, +returns the email address." (cond ((stringp contact) contact) ((listp contact) - (mapconcat (lambda (item) (or (plist-get item :name) (plist-get item :email) "")) contact ",")))) + (mapconcat (lambda (item) (or (plist-get item :name) (plist-get item :email) "")) contact ",")))) + #+end_src **** make custom headers info ***** make headers template #+begin_src emacs-lisp (defun consult-mu--headers-template () "Make headers template using `consult-mu-headers-template'." -(if (and consult-mu-headers-template (functionp consult-mu-headers-template)) - (funcall consult-mu-headers-template) - consult-mu-headers-template)) + (if (and consult-mu-headers-template (functionp consult-mu-headers-template)) + (funcall consult-mu-headers-template) + consult-mu-headers-template)) + #+end_src ***** expand headers template #+begin_src emacs-lisp (defun consult-mu--expand-headers-template (msg string) - "Expands STRING to create a custom header format for MSG. + "Expand STRING to create a custom header format for MSG. -See `consult-mu-headers-template' for explanation of the format of STRING. -" +See `consult-mu-headers-template' for explanation of the format of +STRING." - (cl-loop with str = nil - for c in (string-split string "%" t) + (cl-loop for c in (split-string string "%" t) concat (concat (pcase (substring c 0 1) ("f" (let ((sender (consult-mu--contact-name-or-email (plist-get msg :from))) - (length (string-to-number (substring c 1 nil)))) + (length (string-to-number (substring c 1 nil)))) (if sender (propertize (if (> length 0) (consult-mu--set-string-width sender length) sender) 'face 'consult-mu-sender-face)))) ("t" (let ((receiver (consult-mu--contact-name-or-email (plist-get msg :to))) - (length (string-to-number (substring c 1 nil)))) + (length (string-to-number (substring c 1 nil)))) (if receiver (propertize (if (> length 0) (consult-mu--set-string-width receiver length) receiver) 'face 'consult-mu-sender-face)))) ("s" (let ((subject (plist-get msg :subject)) @@ -1013,12 +1088,12 @@ See `consult-mu-headers-template' for explanation of the format of STRING. (propertize (if (> length 0) (consult-mu--set-string-width tags length) tags) 'face 'consult-mu-tags-face) nil))) ("c" (let ((cc (consult-mu--contact-name-or-email (plist-get msg :cc))) - (length (string-to-number (substring c 1 nil)))) + (length (string-to-number (substring c 1 nil)))) (if cc (propertize (if (> length 0) (consult-mu--set-string-width cc length) cc) 'face 'consult-mu-tags-face)))) ("h" (let ((bcc (consult-mu--contact-name-or-email (plist-get msg :bcc))) - (length (string-to-number (substring c 1 nil)))) + (length (string-to-number (substring c 1 nil)))) (if bcc (propertize (if (> length 0) (consult-mu--set-string-width bcc length) bcc) 'face 'consult-mu-tags-face)))) @@ -1028,6 +1103,7 @@ See `consult-mu-headers-template' for explanation of the format of STRING. (propertize (if (> length 0) (consult-mu--set-string-width changed length) changed) 'face 'consult-mu-tags-face)))) (_ nil)) " "))) + #+end_src *** consult-mu backend **** buffer handling @@ -1036,13 +1112,14 @@ See `consult-mu-headers-template' for explanation of the format of STRING. (defun consult-mu--quit-header-buffer () "Quits `consult-mu-headers-buffer-name' buffer." (save-mark-and-excursion - (when-let* ((buf (get-buffer consult-mu-headers-buffer-name))) - (with-current-buffer buf - (if (eq major-mode 'mu4e-headers-mode) - (mu4e-mark-handle-when-leaving) - (quit-window t) - ;; clear the decks before going to the main-view - (mu4e--query-items-refresh 'reset-baseline)))))) + (when-let* ((buf (get-buffer consult-mu-headers-buffer-name))) + (with-current-buffer buf + (if (eq major-mode 'mu4e-headers-mode) + (mu4e-mark-handle-when-leaving) + (quit-window t) + ;; clear the decks before going to the main-view + (mu4e--query-items-refresh 'reset-baseline)))))) + #+end_src ***** quit view buffer #+begin_src emacs-lisp @@ -1052,85 +1129,87 @@ See `consult-mu-headers-template' for explanation of the format of STRING. (with-current-buffer buf (if (eq major-mode 'mu4e-view-mode) (mu4e-view-quit))))) + #+end_src ***** quit main buffer #+begin_src emacs-lisp (defun consult-mu--quit-main-buffer () - "Quits 'mu4e-main-buffer-name' buffer." + "Quits `mu4e-main-buffer-name' buffer." (when-let* ((buf (get-buffer mu4e-main-buffer-name))) (with-current-buffer buf (if (eq major-mode 'mu4e-main-mode) (mu4e-quit))))) + #+end_src **** minibuffer completion utilities ***** lookup #+begin_src emacs-lisp (defun consult-mu--lookup () -"Lookup function for `consult-mu' or `consult-mu-async' minibuffer candidates. + "Lookup function for `consult-mu' or `consult-mu-async' candidates. -This is passed as LOOKUP to `consult--read' on candidates and is used to format the output when a candidate is selected." - (lambda (sel cands &rest args) +This is passed as LOOKUP to `consult--read' on candidates and is used to +format the output when a candidate is selected." + (lambda (sel cands &rest _args) (let* ((info (cdr (assoc sel cands))) (msg (plist-get info :msg)) (subject (plist-get msg :subject))) (cons subject info)))) + #+end_src ***** group #+begin_src emacs-lisp (defun consult-mu--group-name (cand) - "Gets the group name of CAND using `consult-mu-group-by' -See `consult-mu-group-by' for details of grouping options. -" -(let* ((msg (get-text-property 0 :msg cand)) - (group (or consult-mu--override-group consult-mu-group-by)) - (field (if (not (keywordp group)) (intern (concat ":" (format "%s" group))) group))) - (pcase field - (:date (format-time-string "%a %d %b %y" (plist-get msg field))) - (:from (cond - ((listp (plist-get msg field)) - (mapconcat (lambda (item) (or (plist-get item :name) (plist-get item :email))) (plist-get msg field) ";")) - (stringp (plist-get msg field) (plist-get msg field)))) - (:to (cond - ((listp (plist-get msg field)) - (mapconcat (lambda (item) (or (plist-get item :name) (plist-get item :email))) (plist-get msg field) ";")) - (stringp (plist-get msg field) (plist-get msg field)))) - (:changed (format-time-string "%a %d %b %y" (plist-get msg field))) - (:datetime (format-time-string "%F %r" (plist-get msg :date))) - (:time (format-time-string "%X" (plist-get msg :date))) - (:year (format-time-string "%Y" (plist-get msg :date))) - (:month (format-time-string "%B" (plist-get msg :date))) - (:day-of-week (format-time-string "%A" (plist-get msg :date))) - (:day (format-time-string "%A" (plist-get msg :date))) - (:week (format-time-string "%V" (plist-get msg :date))) - (:size (file-size-human-readable (plist-get msg field))) - (:flags (format "%s" (plist-get msg field))) - (:tags (format "%s" (plist-get msg field))) - (_ (if (plist-get msg field) (format "%s" (plist-get msg field)) nil))))) + "Get the group name of CAND using `consult-mu-group-by'. + +See `consult-mu-group-by' for details of grouping options." + (let* ((msg (get-text-property 0 :msg cand)) + (group (or consult-mu--override-group consult-mu-group-by)) + (field (if (not (keywordp group)) (intern (concat ":" (format "%s" group))) group))) + (pcase field + (:date (format-time-string "%a %d %b %y" (plist-get msg field))) + (:from (cond + ((listp (plist-get msg field)) + (mapconcat (lambda (item) (or (plist-get item :name) (plist-get item :email))) (plist-get msg field) ";")) + ((stringp (plist-get msg field)) (plist-get msg field)))) + (:to (cond + ((listp (plist-get msg field)) + (mapconcat (lambda (item) (or (plist-get item :name) (plist-get item :email))) (plist-get msg field) ";")) + ((stringp (plist-get msg field)) (plist-get msg field)))) + (:changed (format-time-string "%a %d %b %y" (plist-get msg field))) + (:datetime (format-time-string "%F %r" (plist-get msg :date))) + (:time (format-time-string "%X" (plist-get msg :date))) + (:year (format-time-string "%Y" (plist-get msg :date))) + (:month (format-time-string "%B" (plist-get msg :date))) + (:day-of-week (format-time-string "%A" (plist-get msg :date))) + (:day (format-time-string "%A" (plist-get msg :date))) + (:week (format-time-string "%V" (plist-get msg :date))) + (:size (file-size-human-readable (plist-get msg field))) + (:flags (format "%s" (plist-get msg field))) + (:tags (format "%s" (plist-get msg field))) + (_ (if (plist-get msg field) (format "%s" (plist-get msg field)) nil))))) (defun consult-mu--group (cand transform) -"Group function for `consult-mu' or `consult-mu-async' minibuffer candidates. + "Group function for `consult-mu' or `consult-mu-async'. -This is passed as GROUP to `consult--read' on candidates and is used to group emails using `consult-mu--group-name'." +CAND is passed to `consult-mu--group-name' to get the group for CAND. +When TRANSFORM is non-nil, the name of CAND is used for group." (when-let ((name (consult-mu--group-name cand))) (if transform (substring cand) name))) + #+end_src ***** actions In this section we define action functions that can be run on a candidate for example view, reply, forward, etc. ****** view messages #+begin_src emacs-lisp - (defun consult-mu--view (msg noselect mark-as-read match-str) "Opens MSG in `consult-mu-headers' and `consult-mu-view'. If NOSELECT is non-nil, does not select the view buffer/window. - If MARK-AS-READ is non-nil, marks the MSG as read. - -If MATCH-STR is non-nil, highlights the MATCH-STR in the view buffer. -" +If MATCH-STR is non-nil, highlights the MATCH-STR in the view buffer." (let ((msgid (plist-get msg :message-id))) (when-let ((buf (mu4e-get-headers-buffer consult-mu-headers-buffer-name t))) (with-current-buffer buf @@ -1145,9 +1224,9 @@ If MATCH-STR is non-nil, highlights the MATCH-STR in the view buffer. (with-current-buffer consult-mu-headers-buffer-name (if msgid (progn - (mu4e-headers-goto-message-id msgid) - (if mark-as-read - (mu4e--server-move (mu4e-message-field-at-point :docid) nil "+S-u-N"))))) + (mu4e-headers-goto-message-id msgid) + (if mark-as-read + (mu4e--server-move (mu4e-message-field-at-point :docid) nil "+S-u-N"))))) (when match-str (add-to-history 'search-ring match-str) @@ -1163,11 +1242,14 @@ If MATCH-STR is non-nil, highlights the MATCH-STR in the view buffer. (defun consult-mu--view-action (cand) - "Opens the candidate, CAND, from consult-mu. + "Open the candidate, CAND. -This is a wrapper function around `consult-mu--view'. It parses CAND to extract relevant MSG plist and other information and passes them to `consult-mu--view'. +This is a wrapper function around `consult-mu--view'. It parses CAND to +extract relevant MSG plist and other information and passes them to +`consult-mu--view'. -To use this as the default action for consult-mu, set `consult-mu-default-action' to #'consult-mu--view-action." +To use this as the default action for `consult-mu', set +`consult-mu-default-action' to \=#'consult-mu--view-action." (let* ((info (cdr cand)) (msg (plist-get info :msg)) @@ -1175,16 +1257,16 @@ To use this as the default action for consult-mu, set `consult-mu-default-action (match-str (car (consult--command-split query)))) (consult-mu--view msg nil consult-mu-mark-viewed-as-read match-str) (consult-mu-overlays-toggle consult-mu-view-buffer-name))) + #+end_src ****** reply to message #+begin_src emacs-lisp - (defun consult-mu--reply (msg &optional wide-reply) "Reply to MSG using `mu4e-compose-reply'. -If WIDE-REPLY is non-nil use wide-reply (a.k.a. reply all) with `mu4e-compose-wide-reply'. -" +If WIDE-REPLY is non-nil use wide-reply \(a.k.a. reply all\) with +`mu4e-compose-wide-reply'." (let ((msgid (plist-get msg :message-id))) (when-let ((buf (mu4e-get-headers-buffer consult-mu-headers-buffer-name t))) (with-current-buffer buf @@ -1199,14 +1281,23 @@ If WIDE-REPLY is non-nil use wide-reply (a.k.a. reply all) with `mu4e-compose-wi (mu4e-compose-wide-reply))))) (defun consult-mu--reply-action (cand &optional wide-reply) + "Reply to CAND. + +This is a wrapper function around `consult-mu--reply'. It passes +relevant message plist, from CAND, as well as WIDE-REPLY to +`consult-mu--reply'. + +To use this as the default action for `consult-mu', set +`consult-mu-default-action' to \=#'consult-mu--reply-action." (let* ((info (cdr cand)) (msg (plist-get info :msg)) (wide-reply (or wide-reply (pcase consult-mu-use-wide-reply - ('ask (y-or-n-p "reply all?")) + ('ask (y-or-n-p "Reply All?")) ('nil nil) ('t t))))) - (consult-mu--reply msg wide-reply))) + (consult-mu--reply msg wide-reply))) + #+end_src ****** forward a message @@ -1223,32 +1314,41 @@ If WIDE-REPLY is non-nil use wide-reply (a.k.a. reply all) with `mu4e-compose-wi (mu4e-compose-forward)))) (defun consult-mu--forward-action (cand) + "Forward CAND. + +This is a wrapper function around `consult-mu--forward'. It passes +the relevant message plist, from CAND to `consult-mu--forward'. + +To use this as the default action for `consult-mu', set +`consult-mu-default-action' to \=#'consult-mu--forward-action." (let* ((info (cdr cand)) (msg (plist-get info :msg))) - (consult-mu--forward msg))) + (consult-mu--forward msg))) + #+end_src **** get consult split style character #+begin_src emacs-lisp (defun consult-mu--get-split-style-character (&optional style) -"Get the character for consult async split STYLE. + "Get the character for consult async split STYLE. STYLE defaults to `consult-async-split-style'." -(let ((style (or style consult-async-split-style 'none))) - (or (char-to-string (plist-get (alist-get style consult-async-split-styles-alist) :initial)) - (char-to-string (plist-get (alist-get style consult-async-split-styles-alist) :separator)) - ""))) + (let ((style (or style consult-async-split-style 'none))) + (or (char-to-string (plist-get (alist-get style consult-async-split-styles-alist) :initial)) + (char-to-string (plist-get (alist-get style consult-async-split-styles-alist) :separator)) + ""))) + #+end_src ** Frontend Interactive Commands **** consult-mu-dynamic (dynamic collection) ***** format candidate #+begin_src emacs-lisp (defun consult-mu--dynamic-format-candidate (cand highlight) - "Formats minibuffer candidates for `consult-mu'. - -CAND is the minibuffer completion candidate (a mu4e message collected by `consult-mu--dynamic-collection'). + "Format minibuffer candidate, CAND. -if HIGHLIGHT is non-nil, it is highlighted with `consult-mu-highlight-match-face' in the minibuffer completion list." +CAND is the minibuffer completion candidate \(a mu4e message collected by +`consult-mu--dynamic-collection'\). If HIGHLIGHT is non-nil, it is +highlighted with `consult-mu-highlight-match-face'." (let* ((string (car cand)) (info (cadr cand)) @@ -1263,7 +1363,7 @@ if HIGHLIGHT is non-nil, it is highlighted with `consult-mu-highlight-match-face (if (and consult-mu-highlight-matches highlight) (cond ((listp match-str) - (mapcar (lambda (match) (setq str (consult-mu--highlight-match match str t))) match-str)) + (mapc (lambda (match) (setq str (consult-mu--highlight-match match str t))) match-str)) ((stringp match-str) (setq str (consult-mu--highlight-match match-str str t)))) str) @@ -1274,13 +1374,15 @@ if HIGHLIGHT is non-nil, it is highlighted with `consult-mu-highlight-match-face ***** dynamic collection #+begin_src emacs-lisp (defun consult-mu--dynamic-collection (input) - "Dynamically collects mu4e search results. + "Dynamically collect mu4e search results. -INPUT is the user input. It is passed as QUERY to `consult-mu--update-headers', appends the result to `consult-mu-headers-buffer-name' and returns the collects list of found messages and returns it as minibuffer completion table. -" +INPUT is the user input. It is passed as QUERY to +`consult-mu--update-headers', appends the result to +`consult-mu-headers-buffer-name' and returns a list of found +messages." (save-excursion - (pcase-let* ((`(,arg . ,opts) (consult--command-split input))) + (pcase-let* ((`(,_arg . ,opts) (consult--command-split input))) (consult-mu--update-headers (substring-no-properties input) nil nil :dynamic) (if (or (member "-g" opts) (member "--group" opts)) (cond @@ -1294,14 +1396,15 @@ INPUT is the user input. It is passed as QUERY to `consult-mu--update-headers', (goto-char (point-min)) (remove nil (cl-loop until (eobp) - collect (consult-mu--dynamic-format-candidate (list (buffer-substring (point) (point-at-eol)) (list :msg (ignore-errors (mu4e-message-at-point)) :query input)) t) + collect (consult-mu--dynamic-format-candidate (list (buffer-substring (point) (line-end-position)) (list :msg (ignore-errors (mu4e-message-at-point)) :query input)) t) do (forward-line 1)))))) #+end_src ***** state/preview #+begin_src emacs-lisp (defun consult-mu--dynamic-state () - "State function for consult-mu candidates. -This is passed as STATE to `consult--read' and is used to preview or do other actions on the candidate." + "State function for `consult-mu' candidates. +This is passed as STATE to `consult--read' and is used to preview or do +other actions on the candidate." (lambda (action cand) (let ((preview (consult--buffer-preview))) (pcase action @@ -1334,34 +1437,51 @@ This is passed as STATE to `consult--read' and is used to preview or do other ac (defun consult-mu--dynamic (prompt collection &optional initial) "Query mu4e messages dyunamically. -This is a non-interactive internal function. For the interactive version see `consult-mu'. +This is a non-interactive internal function. For the interactive version +see `consult-mu'. -It runs the `consult-mu--dynamic-collection' to do a `mu4e-search' with user input (e.g. INITIAL) and returns the results (list of messages found) as a completion table in minibuffer. +It runs the `consult-mu--dynamic-collection' to do a `mu4e-search' with +user input \(e.g. INITIAL\) and returns the results \(list of messages +found\) as a completion table in minibuffer. -The completion table gets dynamically updated as the user types in the minibuffer. Each candidate in the minibuffer is formatted by `consult-mu--dynamic-format-candidate' to add annotation and other info to the candidate. +The completion table gets dynamically updated as the user types in the +minibuffer. Each candidate in the minibuffer is formatted by +`consult-mu--dynamic-format-candidate' to add annotation and other info to +the candidate. -PROMPT is the prompt in the minibuffer (passed as PROMPT to `consult--read'.) -COLLECTION is a colection function passed to `consult--dynamic-collection'. -INITIAL is an optional arg for the initial input in the minibuffer. (passed as INITITAL to `consult--read'.) +Description of Arguments: + PROMPT the prompt in the minibuffer + \(passed as PROMPT to `consult--read'\) + COLLECTION a colection function passed to `consult--dynamic-collection'. + INITIAL an optional arg for the initial input in the minibuffer. + \(passed as INITITAL to `consult--read'\) -commandline arguments/options (see `mu find --help` in the command line for details) can be passed to the minibuffer input similar to `consult-grep'. For example the user can enter: +commandline arguments/options \(see `mu find --help` in the command line +for details\) can be passed to the minibuffer input similar to +`consult-grep'. For example the user can enter: -`#paper -- --maxnum 200 --sortfield from --reverse' +“#paper -- --maxnum 200 --sortfield from --reverse” -this will search for mu4e messages with the query \"paper\", retrives a maximum of 200 messagesn sorts them by the \"from:\" field and reverses the sort direction (opposite of `consult-mu-search-sort-field'). +this will search for mu4e messages with the query “paper”, retrives a +maximum of 200 messages and sorts them by the “from:” field and reverses +the sort direction (opposite of `consult-mu-search-sort-field'). -Note that some command line arguments are not supported by mu4e (for example sorting base on cc: field or bcc: field is not supported in `mu4e-search-sort-field') +Note that some command line arguments are not supported by mu4e (for +example sorting based on cc: or bcc: fields are not supported in +`mu4e-search-sort-field') -Also, the results can further be narrowed by entering \"#\" similar to `consult-grep'. +Also, the results can further be narrowed by +`consult-async-split-style' \(e.g. by entering “#” when +`consult-async-split-style' is set to \='perl\). For example: -`#paper -- --maxnum 200 --sortfield from --reverse#accepted' +“#paper -- --maxnum 200 --sortfield from --reverse#accepted” -will retrieve the message as the example above, then narrows down the completion table to candidates that match \"accepted\". -" +will retrieve the message as the example above, then narrows down the +candidates to those that that match “accepted”." (consult--read - (consult--dynamic-collection #'consult-mu--dynamic-collection) + (consult--dynamic-collection (or collection #'consult-mu--dynamic-collection)) :prompt (or prompt "Select: ") :lookup (consult-mu--lookup) :state (funcall #'consult-mu--dynamic-state) @@ -1380,41 +1500,52 @@ will retrieve the message as the example above, then narrows down the completion ***** interactive command #+begin_src emacs-lisp (defun consult-mu-dynamic (&optional initial noaction) - "Lists results of `mu4e-search' dynamically. + "Lists results of `mu4e-search' dynamically. -This is an interactive wrapper function around `consult-mu--dynamic'. It queries the user for a search term in the minibuffer, then fetches a list of messages for the entered search term as a minibuffer completion table for selection. The list of candidates in the completion table are dynamically updated as the user changes the entry. +This is an interactive wrapper function around `consult-mu--dynamic'. It +queries the user for a search term in the minibuffer, then fetches a list +of messages for the entered search term as a minibuffer completion table +for selection. The list of candidates in the completion table are +dynamically updated as the user changes the entry. Upon selection of a candidate either - the candidate is returned if NOACTION is non-nil or - the candidate is passed to `consult-mu-action' if NOACTION is nil. -Additional commandline arguments can be passed in the minibuffer entry by typing `--` followed by command line arguments. +Additional commandline arguments can be passed in the minibuffer entry by +typing “--” followed by command line arguments. -For example the user can enter: +For example, the user can enter: -`#consult-mu -- -n 10' +“#consult-mu -- -n 10” -this will run a `mu4e-search' with the query \"consult-my\" and changes the search limit (i.e. `mu4e-search-results-limit' to 10. +this will run a `mu4e-search' with the query “consult-mu” and changes the +search limit \(i.e. `mu4e-search-results-limit' to 10\). -Also, the results can further be narrowed by entering \"#\" similar to `consult-grep'. +Also, the results can further be narrowed by +`consult-async-split-style' \(e.g. by entering “#” when +`consult-async-split-style' is set to \='perl\). For example: -`#consult-mu -- -n 10#github' +“#consult-mu -- -n 10#github” -will retrieve the message as the example above, then narrows down the completion table to candidates that match \"github\". +will retrieve the messages as the example above, then narrows down the +completion table to candidates that match “github”. -INITIAL is an optional arg for the initial input in the minibuffer. (passed as INITITAL to `consult-mu--dynamic') +INITIAL is an optional arg for the initial input in the minibuffer. +\(passed as INITITAL to `consult-mu--dynamic'\) -For more details on consult--async functionalities, see `consult-grep' and the official manual of consult, here: https://github.com/minad/consult. -" +For more details on consult--async functionalities, see `consult-grep' and +the official manual of consult, here: +URL `https://github.com/minad/consult'" (interactive) (save-mark-and-excursion - (consult-mu--execute-all-marks)) + (consult-mu--execute-all-marks)) (let* ((sel - (consult-mu--dynamic (concat "[" (propertize "consult-mu-dynamic" 'face 'consult-mu-sender-face) "]" " Search For: ") #'consult-mu--dynamic-collection initial))) + (consult-mu--dynamic (concat "[" (propertize "consult-mu-dynamic" 'face 'consult-mu-sender-face) "]" " Search For: ") #'consult-mu--dynamic-collection initial))) (save-mark-and-excursion (consult-mu--execute-all-marks)) (if noaction @@ -1432,10 +1563,12 @@ For more details on consult--async functionalities, see `consult-grep' and the o STRING is the output retrieved from `mu find INPUT ...` in the command line. INPUT is the query from the user. -if HIGHLIGHT is t, input is highlighted with `consult-mu-highlight-match-face' in the minibuffer." + +If HIGHLIGHT is t, input is highlighted with +`consult-mu-highlight-match-face' in the minibuffer." (let* ((query input) - (parts (string-split (replace-regexp-in-string "^\\\\->\s\\|^\\\/->\s" "" string) consult-mu-delimiter)) + (parts (split-string (replace-regexp-in-string "^\\\\->\s\\|^\\\/->\s" "" string) consult-mu-delimiter)) (msgid (car parts)) (date (date-to-time (cadr parts))) (sender (cadr (cdr parts))) @@ -1457,9 +1590,10 @@ if HIGHLIGHT is t, input is highlighted with `consult-mu-highlight-match-face' i (headers-template (consult-mu--headers-template)) (str (if headers-template (consult-mu--expand-headers-template msg headers-template) - (format "%s\s\s%s\s\s%s\s\s%s\s\s%s" + (format "%s\s\s%s\s\s%s\s\s%s\s\s%s\s\s%s" (propertize (consult-mu--set-string-width - (format-time-string "%x" date) 10) 'face 'consult-mu-date-face) + (format-time-string "%x" date) 10) + 'face 'consult-mu-date-face) (propertize (consult-mu--set-string-width (consult-mu--contact-name-or-email sender) (floor (* (frame-width) 0.2))) 'face 'consult-mu-sender-face) (propertize (consult-mu--set-string-width subject (floor (* (frame-width) 0.55))) 'face 'consult-mu-subject-face) (propertize (file-size-human-readable size) 'face 'consult-mu-size-face) @@ -1469,11 +1603,12 @@ if HIGHLIGHT is t, input is highlighted with `consult-mu-highlight-match-face' i (if (and consult-mu-highlight-matches highlight) (cond ((listp match-str) - (mapcar (lambda (match) (setq str (consult-mu--highlight-match match str t))) match-str)) + (mapc (lambda (match) (setq str (consult-mu--highlight-match match str t))) match-str)) ((stringp match-str) (setq str (consult-mu--highlight-match match-str str t)))) str) (cons str (list :msg msg :query query :type :async)))) + #+end_src @@ -1482,7 +1617,8 @@ if HIGHLIGHT is t, input is highlighted with `consult-mu-highlight-match-face' i (defun consult-mu--async-state () "State function for `consult-mu-async' candidates. -This is passed as STATE to `consult--read' and is used to preview or do other actions on the candidate." +This is passed as STATE to `consult--read' and is used to preview or do +other actions on the candidate." (lambda (action cand) (let ((preview (consult--buffer-preview))) (pcase action @@ -1510,7 +1646,6 @@ This is passed as STATE to `consult--read' and is used to preview or do other ac ***** transform #+begin_src emacs-lisp - (defun consult-mu--async-transform (input) "Add annotation to minibuffer candiates for `consult-mu'. @@ -1550,15 +1685,15 @@ Format each candidates with `consult-gh--repo-format' and INPUT." (setq opts (append opts (list "--fields" (format "i%sd%sf%st%ss%sz%sg%sx%sp%sc%sh%sl" consult-mu-delimiter consult-mu-delimiter consult-mu-delimiter consult-mu-delimiter consult-mu-delimiter consult-mu-delimiter consult-mu-delimiter consult-mu-delimiter consult-mu-delimiter consult-mu-delimiter consult-mu-delimiter)))) (unless (or (member "-s" flags) (member "--sortfiled" flags)) - (setq opts (append opts (list "--sortfield" (substring (symbol-name consult-mu-search-sort-field) 1))))) + (setq opts (append opts (list "--sortfield" (substring (symbol-name consult-mu-search-sort-field) 1))))) (if threads (setq opts (append opts (list "--thread")))) (if skip-dups (setq opts (append opts (list "--skip-dups")))) (if include-related (setq opts (append opts (list "--include-related")))) (cond ((and (member "-n" flags) (< (string-to-number (nth (+ (cl-position "-n" opts :test 'equal) 1) opts)) 0)) - (setq opts (remove "-n" (remove (nth (+ (cl-position "-n" opts :test 'equal) 1) opts) opts)))) + (setq opts (remove "-n" (remove (nth (+ (cl-position "-n" opts :test 'equal) 1) opts) opts)))) ((and (member "--maxnum" flags) (< (string-to-number (nth (+ (cl-position "--maxnum" opts :test 'equal) 1) opts)) 0)) - (setq opts (remove "--maxnum" (remove (nth (+ (cl-position "--maxnum" opts :test 'equal) 1) opts) opts))))) + (setq opts (remove "--maxnum" (remove (nth (+ (cl-position "--maxnum" opts :test 'equal) 1) opts) opts))))) (unless (or (member "-n" flags) (member "--maxnum" flags)) (if (and consult-mu-maxnum (> consult-mu-maxnum 0)) (setq opts (append opts (list "--maxnum" (format "%s" consult-mu-maxnum)))))) @@ -1576,36 +1711,53 @@ Format each candidates with `consult-gh--repo-format' and INPUT." (list (string-join re " ")) opts) hl))))) + #+end_src ***** internal async command #+begin_src emacs-lisp (defun consult-mu--async (prompt builder &optional initial) -"Query mu4e messages asynchronously. + "Query mu4e messages asynchronously. + +This is a non-interactive internal function. For the interactive +version, see `consult-mu-async'. -This is a non-interactive internal function. For the interactive version see `consult-mu-async'. +It runs the command line from `consult-mu--async-builder' in an async +process and returns the results (list of messages) as a completion table +in minibuffer that will be passed to `consult--read'. The completion +table gets dynamically updated as the user types in the minibuffer. Each +candidate in the minibuffer is formatted by `consult-mu--async-transform' +to add annotation and other info to the candidate. -It runs the command line from `consult-mu--async-builder' in an async process and returns the results (list of messages) as a completion table in minibuffer that will be passed to `consult--read'. The completion table gets dynamically updated as the user types in the minibuffer. Each candidate in the minibuffer is formatted by `consult-mu--async-transform' to add annotation and other info to the candidate. +Description of Arguments: -PROMPT is the prompt in the minibuffer (passed as PROMPT to `consult--red'.) -BUILDER is an async builder function passed to `consult--async-command'. -INITIAL is an optional arg for the initial input in the minibuffer. (passed as INITITAL to `consult--read'.) +PROMPT the prompt in the minibuffer + \(passed as PROMPT to `consult--red'\) +BUILDER an async builder function passed to `consult--async-command' +INITIAL an optional arg for the initial input in the minibuffer + \(passed as INITITAL to `consult--read'\) -commandline arguments/options (see `mu find --help` in the command line for details) can be passed to the minibuffer input similar to `consult-grep'. For example the user can enter: +commandline arguments/options \(see `mu find --help` in the command line +for details\) can be passed to the minibuffer input similar to +`consult-grep'. For example the user can enter: -`#paper -- --maxnum 200 --sortfield from --reverse' +“#paper -- --maxnum 200 --sortfield from --reverse” -this will search for mu4e messages with the query \"paper\", retrives a maximum of 200 messages sorts them by the \"from:\" field and reverses the sort direction (opposite of `consult-mu-search-sort-field'). +this will search for mu4e messages with the query “paper”, retrives a +maximum of 200 messages sorts them by the “from:” field and reverses the +sort direction (opposite of `consult-mu-search-sort-field'). -Also, the results can further be narrowed by entering \"#\" similar to `consult-grep'. +Also, the results can further be narrowed by +`consult-async-split-style' \(e.g. by entering “#” when +`consult-async-split-style' is set to \='perl\). For example: `#paper -- --maxnum 200 --sortfield from --reverse#accepted' -will retrieve the message as the example above, then narrows down the completion table to candidates that match \"accepted\". -" +will retrieve the message as the example above, then narrows down the +completion table to candidates that match “accepted”." (consult--read (consult--process-collection builder :transform (consult--async-transform-by-input #'consult-mu--async-transform)) @@ -1629,36 +1781,57 @@ will retrieve the message as the example above, then narrows down the completion (defun consult-mu-async (&optional initial noaction) "Lists results of `mu find` Asynchronously. -This is an interactive wrapper function around `consult-mu--async'. It queries the user for a search term in the minibuffer, then fetches a list of messages for the entered search term as a minibuffer completion table for selection. The list of candidates in the completion table are dynamically updated as the user changes the entry. +This is an interactive wrapper function around `consult-mu--async'. It +queries the user for a search term in the minibuffer, then fetches a list +of messages for the entered search term as a minibuffer completion table +for selection. The list of candidates in the completion table are +dynamically updated as the user changes the entry. Upon selection of a candidate either - the candidate is returned if NOACTION is non-nil or - the candidate is passed to `consult-mu-action' if NOACTION is nil. -Additional commandline arguments can be passed in the minibuffer entry by typing `--` followed by command line arguments. +Additional commandline arguments can be passed in the minibuffer entry by +typing `--` followed by command line arguments. For example the user can enter: `#consult-mu -- -n 10' -this will run a `mu4e-search' with the query \"consult-my\" and changes the search limit (i.e. `mu4e-search-results-limit' to 10. +this will run a `mu4e-search' with the query \"consult-my\" and changes the +search limit (i.e. `mu4e-search-results-limit' to 10. -Also, the results can further be narrowed by entering \"#\" similar to `consult-grep'. +Also, the results can further be narrowed by `consult-async-split-style' +\(e.g. by entering “#” when `consult-async-split-style' is set to \='perl\). For example: -`#consult-mu -- -n 10#github' - -will retrieve the message as the example above, then narrows down the completion table to candidates that match \"github\". - -INITIAL is an optional arg for the initial input in the minibuffer. (passed as INITITAL to `consult-mu--async'). - -For more details on consult--async functionalities, see `consult-grep' and the official manual of consult, here: https://github.com/minad/consult. - -Note that this is the async search directly using the commandline `mu` command and not mu4e-search. As a result, mu4e-headers buffers are not created until a single message is selected (or interacted with using embark, etc.) Previews are shown in a mu4e-view buffer (see `consult-mu-view-buffer-name') attached to an empty mu4e-headers buffer (i.e. `consult-mu-headers-buffer-name'). This allows quick retrieval of many messages (tens of thousands) and previews, but not opening the results in a mu4e-headers buffer. If you want ot open the results in a mu4e-headers buffer for other work flow, then you should use the dynamically collected function `consult-mu' which is slower if searching for many emails but allows follow up interactions in a mu4e-headers buffer. -" +“#consult-mu -- -n 10#github” + +will retrieve the message as the example above, then narrows down the +completion table to candidates that match “github”. + +INITIAL is an optional arg for the initial input in the minibuffer. +\(passed as INITITAL to `consult-mu--async'\). + +For more details on consult--async functionalities, see `consult-grep' and +the official manual of consult, here: +URL `https://github.com/minad/consult' + +Note that this is the async search directly using the commandline `mu` +command and not mu4e-search. As a result, mu4e-headers buffers are not +created until a single message is selected \(or interacted with using +embark, etc.\) Previews are shown in a mu4e-view buffer \(see +`consult-mu-view-buffer-name'\) attached to an empty mu4e-headers buffer +\(i.e. `consult-mu-headers-buffer-name'\). This allows quick retrieval of +many messages \(tens of thousands\) and previews, but not opening the +results in a mu4e-headers buffer. If you want ot open the results in a +mu4e-headers buffer for other work flow, then you should use the +dynamically collected function `consult-mu' which is slower if searching +for many emails but allows follow up interactions in a mu4e-headers +buffer." (interactive) (save-mark-and-excursion (consult-mu--execute-all-marks)) @@ -1675,6 +1848,7 @@ Note that this is the async search directly using the commandline `mu` command a (consult-mu--update-headers query t msg :async)) (funcall consult-mu-action sel) sel))) + #+end_src @@ -1683,17 +1857,18 @@ Note that this is the async search directly using the commandline `mu` command a ***** interactive command #+begin_src emacs-lisp (defun consult-mu (&optional initial noaction) -"Default consult-mu command. + "Default interactive command. -This is a wrapper function that calls `consult-mu-default-command'. +This is a wrapper function that calls `consult-mu-default-command' with +INITIAL and NOACTION. For example, the `consult-mu-default-command can be set to -`#'consult-mu-dynamic' sets the default behavior to dynamic collection -`#'consult-mu-async' sets the default behavior to async collection -" + `#'consult-mu-dynamic' sets the default behavior to dynamic collection + `#'consult-mu-async' sets the default behavior to async collection" (interactive "P") (funcall consult-mu-default-command initial noaction)) + #+end_src @@ -1701,6 +1876,7 @@ For example, the `consult-mu-default-command can be set to #+begin_src emacs-lisp ;;; provide `consult-mu' module (provide 'consult-mu) + #+end_src ** Footer #+begin_src emacs-lisp @@ -1709,7 +1885,7 @@ For example, the `consult-mu-default-command can be set to * consult-mu-embark.el :PROPERTIES: -:header-args:emacs-lisp: :results none :mkdirp yes :link yes :tangle ./consult-mu-embark.el +:header-args:emacs-lisp: :results none :mkdirp yes :comments none :tangle ./consult-mu-embark.el :END: *** Header #+begin_src emacs-lisp @@ -1753,6 +1929,7 @@ For example, the `consult-mu-default-command can be set to ;; This package requires mu4e version "1.10.8" or later. ;;; Code: + #+end_src *** Main @@ -1771,7 +1948,7 @@ This section includes additional useful embark actions as well as possible keyma ;;; Define Embark Action Functions (defun consult-mu-embark-default-action (cand) - "Run `consult-mu-action' on the candidate." + "Run `consult-mu-action' on the candidate, CAND." (let* ((msg (get-text-property 0 :msg cand)) (query (get-text-property 0 :query cand)) (type (get-text-property 0 :type cand)) @@ -1814,7 +1991,6 @@ This section includes additional useful embark actions as well as possible keyma (let* ((msg (get-text-property 0 :msg cand)) (query (get-text-property 0 :query cand)) (type (get-text-property 0 :type cand)) - (newcand (cons cand `(:msg ,msg :query ,query :type ,type))) (msg-id (plist-get msg :message-id))) (if (equal type :async) (consult-mu--update-headers query t msg :async)) @@ -1827,14 +2003,13 @@ This section includes additional useful embark actions as well as possible keyma (with-current-buffer consult-mu-view-buffer-name (kill-new (consult-mu--message-get-header-field)) - (consult-mu--pulse-region (point) (point-at-eol))))) + (consult-mu--pulse-region (point) (line-end-position))))) (defun consult-mu-embark-save-attachmnts (cand) "Save attachments of CAND." (let* ((msg (get-text-property 0 :msg cand)) (query (get-text-property 0 :query cand)) (type (get-text-property 0 :type cand)) - (newcand (cons cand `(:msg ,msg :query ,query :type ,type))) (msg-id (plist-get msg :message-id))) (if (equal type :async) @@ -1850,7 +2025,7 @@ This section includes additional useful embark actions as well as possible keyma (with-current-buffer consult-mu-view-buffer-name (goto-char (point-min)) (re-search-forward "^\\(Attachment\\|Attachments\\): " nil t) - (consult-mu--pulse-region (point) (point-at-eol)) + (consult-mu--pulse-region (point) (line-end-position)) (mu4e-view-save-attachments t)))) (defun consult-mu-embark-search-messages-from-contact (cand) @@ -1904,9 +2079,12 @@ This section includes additional useful embark actions as well as possible keyma ;; add embark functions for marks (defun consult-mu-embark--defun-func-for-marks (marks) - "Runs the macro `consult-mu-embark--defun-mark-for' on a list of marks. + "Run the macro `consult-mu-embark--defun-mark-for' on MARKS. -This is useful for creating embark functions for all the `mu4e-marks' elements." +MARKS is a list of marks. + +This is useful for creating embark functions for all the `mu4e-marks' +elements." (mapcar (lambda (mark) (eval `(consult-mu-embark--defun-mark-for ,mark))) marks)) ;; use consult-mu-embark--defun-func-for-marks to make a function for each `mu4e-marks' element. @@ -1936,11 +2114,16 @@ This is useful for creating embark functions for all the `mu4e-marks' elements." ;; add mark keys to `consult-mu-embark-messages-actions-map' keymap (defun consult-mu-embark--add-keys-for-marks (marks) - "Adds a key for each mark in MARKS to `consult-mu-embark-messages-actions-map'. + "Add a key for each mark in MARKS to embark map. -Binds the combination “m key”, where key is the :char in mark plist in the `consult-mu-embark-messages-actions-map' to the function defined by the prefix “consult-mu-embark-mark-for-” and mark. +Adds the keys in `consult-mu-embark-messages-actions-map', and binds the +combination “m key”, where key is the :char in mark plist in the +`consult-mu-embark-messages-actions-map' to the function defined by the +prefix “consult-mu-embark-mark-for-” and mark. -This is useful for adding all `mu4e-marks' to embark key bindings under a submenu (called by “m”) ,for example the default mark-for-archive mark that is bound to r in mu4e buffers can be called in embark by “m r”." +This is useful for adding all `mu4e-marks' to embark key bindings under a +submenu (called by “m”), for example, the default mark-for-archive mark +that is bound to r in mu4e buffers can be called in embark by “m r”." (mapcar (lambda (mark) (let* ((key (plist-get (cdr mark) :char)) (key (cond ((consp key) (car key)) ((stringp key) key))) @@ -1960,17 +2143,17 @@ This is useful for adding all `mu4e-marks' to embark key bindings under a submen (provide 'consult-mu-embark) -;;; consult-mu-embark.el ends here +;;; consult-mu-embark.el ends here #+end_src * consult-mu-compose.el :PROPERTIES: -:header-args:emacs-lisp: :results none :mkdirp yes :link yes :tangle ./extras/consult-mu-compose.el +:header-args:emacs-lisp: :results none :mkdirp yes :comments none :tangle ./extras/consult-mu-compose.el :END: ** Header #+begin_src emacs-lisp -;;; consult-mu-compose.el --- Consult Mu4e asynchronously in GNU Emacs -*- lexical-binding: t -*- +;;; consult-mu-compose.el --- Consult Mu4e asynchronously -*- lexical-binding: t -*- ;; Copyright (C) 2023 Armin Darvish @@ -2016,6 +2199,7 @@ This is useful for adding all `mu4e-marks' to embark key bindings under a submen ** Requirements #+begin_src emacs-lisp (require 'consult-mu) + #+end_src ** Define Group, Customs, Vars, etc. @@ -2023,21 +2207,25 @@ This is useful for adding all `mu4e-marks' to embark key bindings under a submen #+begin_src emacs-lisp ;;; Customization Variables (defcustom consult-mu-compose-use-dired-attachment 'in-dired - "Use a dired buffer for multiple file attachment? -If set to 'in-dired uses dired buffer and dired marks only when inside dired buffer -If 't, consult-mu will always use dired buffer for selecting attachment files similar to what Doom Emacs does (see https://github.com/doomemacs/doomemacs/blob/bea81278fd2ecb65db6a63dbcd6db2f52921ee41/modules/email/mu4e/autoload/email.el#L272). + "Use a Dired buffer for multiple file attachment? + +If set to \='in-dired uses `dired' buffer and `dired' marks only when inside +a `dired' buffer. If \='t, a `dired' buffer will be used for selecting attachment files similar to what Doom Emacs does: +URL `https://github.com/doomemacs/doomemacs/blob/bea81278fd2ecb65db6a63dbcd6db2f52921ee41/modules/email/mu4e/autoload/email.el#L272'. -If 'nil, consult-mu uses minibuffer completion for selection files to attach even if inside a dired buffer. +If \='nil, consult-mu uses minibuffer completion for selection files to +attach, even if inside a `dired' buffer. -By default this is set to 'in-dired." +By default this is set to \='in-dired." :group 'consult-mu :type '(choice (const :tag "Only use Dired if inside Dired Buffer" 'in-dired) (const :tag "Always use Dired" t) (const :tag "Never use Dired" nil))) (defcustom consult-mu-large-file-warning-threshold large-file-warning-threshold - "Threshold for size of file to require confirmation for preview when selecting files to attach to emails. -Files larger than this value in size will require user confirmation before previewing the file. Default value is set by `large-file-warning-threshold'. If nil, no cofnirmation is required." + "Threshold for size of file to require confirmation for preview. + +This is used when selecting files to attach to emails. Files larger than this value in size will require user confirmation before previewing the file. Default value is set by `large-file-warning-threshold'. If nil, no cofnirmation is required." :group 'consult-mu :type '(choice integer (const :tag "Never request confirmation" nil))) @@ -2045,8 +2233,9 @@ Files larger than this value in size will require user confirmation before previ (defcustom consult-mu-compose-preview-key consult-mu-preview-key "Preview key for `consult-mu-compose'. -This is similar to `consult-mu-preview-key' but explicitly for consult-mu-compose. -It is recommended to set this to something other than 'any to avoid loading preview buffers for each file." +This is similar to `consult-mu-preview-key' but explicitly for +consult-mu-compose. It is recommended to set this to something other than +\='any to avoid loading preview buffers for each file." :group 'consult-mu :type '(choice (const :tag "Any key" any) (list :tag "Debounced" @@ -2062,14 +2251,18 @@ It is recommended to set this to something other than 'any to avoid loading prev :group 'consult-mu :type '(choice (key :tag "Key") (const :tag "no key binding" nil))) + #+end_src *** Others #+begin_src emacs-lisp (defvar consult-mu-compose-attach-history nil - "History variable for file attachment used in `consult-mu-compose--read-file-attach'.") + "History variable for file attachment. + +It is used in `consult-mu-compose--read-file-attach'.") (defvar consult-mu-compose-current-draft-buffer nil - "Stores the buffer that is being edited.") + "Store the buffer that is being edited.") + #+end_src ** Backend Functions @@ -2101,7 +2294,7 @@ INITIAL is the initial input in the minibuffer." (file-attributes filename)))) (confirm (if (and filename (>= filesize consult-mu-large-file-warning-threshold)) - (yes-or-no-p (format "File is %s Bytes. Do you really want to preview it?" filesize)) + (yes-or-no-p (format "File is %s Bytes. Do you really want to preview it?" filesize)) t))) (if confirm (funcall preview action @@ -2146,7 +2339,7 @@ INITIAL is the initial input in the minibuffer." (file-attributes filename)))) (confirm (if (and filename (>= filesize consult-mu-large-file-warning-threshold)) - (yes-or-no-p (format "File is %s Bytes. Do you really want to preview it?" filesize)) + (yes-or-no-p (format "File is %s Bytes. Do you really want to preview it?" filesize)) t))) (if confirm (funcall preview action @@ -2164,7 +2357,7 @@ INITIAL is the initial input in the minibuffer." **** get draft buffer #+begin_src emacs-lisp (defun consult-mu-compose-get-draft-buffer () - "Queries user to select a mu4e compose draft buffer" + "Query user to select a mu4e compose draft buffer." (save-excursion (if (and (consult-mu-compose-get-current-buffers) (y-or-n-p "Attach the files to an existing compose buffer? ")) @@ -2205,8 +2398,8 @@ INITIAL is the initial input in the minibuffer." **** add attachment ***** attach a file #+begin_src emacs-lisp -(defun consult-mu-compose--attach-files (files &optional mail-buffer &rest args) - "Attach FILE to email in MAIL-BUFFER compose buffer." +(defun consult-mu-compose--attach-files (files &optional mail-buffer &rest _args) + "Attach FILES to email in MAIL-BUFFER compose buffer." (let ((files (if (stringp files) (list files) files)) (mail-buffer (or mail-buffer (if (version<= mu4e-mu-version "1.12") (mu4e-compose 'new) (mu4e-compose-new))))) @@ -2236,8 +2429,8 @@ INITIAL is the initial input in the minibuffer." **** remove attachment #+begin_src emacs-lisp -(defun consult-mu-compose--remove-files (files &optional mail-buffer &rest args) - "Removes FILES from current attachments in MAIL-BUFFER." +(defun consult-mu-compose--remove-files (files &optional mail-buffer &rest _args) + "Remove FILES from current attachments in MAIL-BUFFER." (let ((files (if (stringp files) (list files) files)) (mail-buffer (or mail-buffer (current-buffer)))) (with-current-buffer mail-buffer @@ -2273,7 +2466,9 @@ INITIAL is the initial input in the minibuffer." ** Frontend Interactive Commands #+begin_src emacs-lisp (defun consult-mu-compose-attach (&optional files mail-buffer) - "Attach FILES to email interactively." + "Attach FILES to email in MAIL-BUFFER interactively. + +MAIL-BUFFER defaults to `consult-mu-compose-current-draft-buffer'." (interactive) (let* ((consult-mu-compose-current-draft-buffer (cond ((or (derived-mode-p 'mu4e-compose-mode) (derived-mode-p 'org-msg-edit-mode) (derived-mode-p 'message-mode)) (current-buffer)) @@ -2399,11 +2594,11 @@ INITIAL is the initial input in the minibuffer." #+end_src ** Footer #+begin_src emacs-lisp -;;; consult-mu-compose.el ends here +;;; consult-mu-compose.el ends here #+end_src * consult-mu-compose-embark.el :PROPERTIES: -:header-args:emacs-lisp: :results none :mkdirp yes :link yes :tangle ./extras/consult-mu-compose-embark.el +:header-args:emacs-lisp: :results none :mkdirp yes :comments none :tangle ./extras/consult-mu-compose-embark.el :END: *** Header #+begin_src emacs-lisp @@ -2457,16 +2652,18 @@ INITIAL is the initial input in the minibuffer." (require 'consult-mu-embark) (defun consult-mu-compose-embark-attach-file (cand) - "Run `consult-mu-attach-files' on the candidate." - (funcall (apply-partially #'consult-mu-compose-attach cand))) + "Run `consult-mu-attach-files' on CAND." + (funcall (apply-partially #'consult-mu-compose-attach cand))) ;;; add consult-mu-attach to embark-file-map (defun consult-mu-compose-embark-bind-attach-file-key (&optional key) -"Binds `consult-mu-embark-attach-file-key' to `consult-mu-compose-embark-attach-file' in `embark-file-map'. + "Binds `consult-mu-embark-attach-file-key'. -If KEY is non-nil binds KEY instead of `consult-mu-embark-attach-file-key'." -(if-let ((keyb (or key (kbd consult-mu-embark-attach-file-key)))) -(define-key embark-file-map keyb #'consult-mu-compose-embark-attach-file))) +Bind `consult-mu-embark-attach-file-key' to +`consult-mu-compose-embark-attach-file' in `embark-file-map'. If KEY is +non-nil binds KEY instead of `consult-mu-embark-attach-file-key'." + (if-let ((keyb (or key (kbd consult-mu-embark-attach-file-key)))) + (define-key embark-file-map keyb #'consult-mu-compose-embark-attach-file))) (consult-mu-compose-embark-bind-attach-file-key) @@ -2478,16 +2675,16 @@ If KEY is non-nil binds KEY instead of `consult-mu-embark-attach-file-key'." (provide 'consult-mu-compose-embark) -;;; consult-mu-compose-embark.el ends here +;;; consult-mu-compose-embark.el ends here #+end_src * consult-mu-contacts.el :PROPERTIES: -:header-args:emacs-lisp: :results none :mkdirp yes :link yes :tangle ./extras/consult-mu-contacts.el +:header-args:emacs-lisp: :results none :mkdirp yes :comments none :tangle ./extras/consult-mu-contacts.el :END: ** Header #+begin_src emacs-lisp -;;; consult-mu-contacts.el --- Consult Mu4e asynchronously in GNU Emacs -*- lexical-binding: t -*- +;;; consult-mu-contacts.el --- Consult Mu4e asynchronously -*- lexical-binding: t -*- ;; Copyright (C) 2023 Armin Darvish @@ -2534,6 +2731,7 @@ If KEY is non-nil binds KEY instead of `consult-mu-embark-attach-file-key'." ** Requirements #+begin_src emacs-lisp (require 'consult-mu) + #+end_src ** Define Group, Customs, Vars, etc. *** Custom Variables @@ -2541,15 +2739,16 @@ If KEY is non-nil binds KEY instead of `consult-mu-embark-attach-file-key'." ;;; Customization Variables (defcustom consult-mu-contacts-group-by :name - "What field to use to group the results in the minibuffer. + "What field to use to group the results in the minibuffer? -By default it is set to :name. But can be any of: +By default it is set to :name, but can be any of: - :name group by contact name - :email group by email of the contact - :domain group by the domain of the contact's email (e.g. domain.com in user@domain.com) - :user group by the ncontact's user name (e.g. user in user@domain.com) -" + :name group by contact name + :email group by email of the contact + :domain group by the domain of the contact's email + \(e.g. domain.com in user@domain.com\) + :user group by the ncontact's user name + \(e.g. user in user@domain.com\)" :group 'consult-mu :type '(radio (const :name) (const :email) @@ -2557,10 +2756,10 @@ By default it is set to :name. But can be any of: (const :user))) (defcustom consult-mu-contacts-action #'consult-mu-contacts--list-messages-action - "Which function to use when selecting a contact. + "Which function to use when selecting a contact? -By default it is bound to `consult-mu-contacts--list-messages-action'. -" +By default it is bound to +`consult-mu-contacts--list-messages-action'." :group 'consult-mu :type '(choice (function :tag "(Default) Show Messages from Contact" #'consult-mu-contacts--list-messages-action) (function :tag "Insert Email" #'consult-mu-contacts--insert-email-action) @@ -2570,21 +2769,22 @@ By default it is bound to `consult-mu-contacts--list-messages-action'. (defcustom consult-mu-contacts-ignore-list (list) "List of Regexps to ignore when searching contacts. -This is useful to filter certain addreses from contacts. For example you can remove no-reply adresses by setting this variable to '((“no-reply@example.com”)). -" +This is useful to filter certain addreses from contacts. For example, you +can remove no-reply adresses by setting this variable to +\='((“no-reply@example.com”))." :group 'consult-mu :type '(repeat :tag "Regexp List" regexp)) (defcustom consult-mu-contacts-ignore-case-fold-search case-fold-search - "Whether to ignore case when matching against `consult-mu-contacts-ignore-list'. -When non-nil, `consult-mu-contacts' performs case *insensitive* match with `consult-mu-contacts-ignore-list' and removes matches from candidates. + "Whether to ignore case when matching against ignore-list? -By default it is inherited from `case-fold-search'. -" +When non-nil, `consult-mu-contacts' performs case *insensitive* match with +`consult-mu-contacts-ignore-list' and removes matches from candidates. + +By default it is inherited from `case-fold-search'." :group 'consult-mu :type 'boolean) - #+end_src *** Other Variables @@ -2600,7 +2800,6 @@ By default it is inherited from `case-fold-search'. (defvar consult-mu-contacts--history nil "History variable for `consult-mu-contacts'.") - #+end_src ** Backend Commands @@ -2609,53 +2808,67 @@ By default it is inherited from `case-fold-search'. (defun consult-mu-contacts--list-messages (contact) "List messages from CONTACT using `consult-mu'." (let* ((consult-mu-maxnum nil) - (email (plist-get contact :email))) - (consult-mu (format "contact:%s" email)))) + (email (plist-get contact :email))) + (consult-mu (format "contact:%s" email)))) (defun consult-mu-contacts--list-messages-action (cand) - "Searches the messages from contact candidate, CAND. + "Search the messages from contact candidate, CAND. -This is a wrapper function around `consult-mu-contacts--list-messages'. It parses CAND to extract relevant CONTACT plist and other information and passes them to `consult-mu-contacts--list-messages'. +This is a wrapper function around `consult-mu-contacts--list-messages'. It +parses CAND to extract relevant CONTACT plist and other information and +passes them to `consult-mu-contacts--list-messages'. -To use this as the default action for consult-mu-contacts, set `consult-mu-contacts-default-action' to #'consult-mu-contacts--list-messages-action." +To use this as the default action for consult-mu-contacts, set +`consult-mu-contacts-default-action' to +\=#'consult-mu-contacts--list-messages-action." (let* ((info (cdr cand)) (contact (plist-get info :contact))) (consult-mu-contacts--list-messages contact))) + #+end_src *** insert contact #+begin_src emacs-lisp (defun consult-mu-contacts--insert-email (contact) - "insert email of CONTACT at point. + "Insert email of CONTACT at point. This is useful for inserting email when composing an email to contact." (let* ((email (plist-get contact :email))) - (insert (concat email "; ")))) + (insert (concat email "; ")))) (defun consult-mu-contacts--insert-email-action (cand) - "inserts the email from contact candidate, CAND. + "Insert the email from contact candidate, CAND. -This is a wrapper function around `consult-mu-contacts--insert-email'. It parses CAND to extract relevant CONTACT plist and other information and passes them to `consult-mu-contacts--insert-email'. +This is a wrapper function around `consult-mu-contacts--insert-email'. It +parses CAND to extract relevant CONTACT plist and other information and +passes them to `consult-mu-contacts--insert-email'. -To use this as the default action for consult-mu-contacts, set `consult-mu-contacts-default-action' to #'consult-mu-contacts--insert-email-action." +To use this as the default action for consult-mu-contacts, set +`consult-mu-contacts-default-action' to +\=#'consult-mu-contacts--insert-email-action." (let* ((info (cdr cand)) (contact (plist-get info :contact))) (consult-mu-contacts--insert-email contact))) + #+end_src *** copy contact #+begin_src emacs-lisp (defun consult-mu-contacts--copy-email (contact) - "copy email of CONTACT to kill ring." + "Copy email of CONTACT to kill ring." (let* ((email (plist-get contact :email))) (kill-new email))) (defun consult-mu-contacts--copy-email-action (cand) - "Copies the email from contact candidate, CAND, to kill ring. + "Copy the email from contact candidate, CAND, to kill ring. -This is a wrapper function around `consult-mu-contacts--copy-email'. It parses CAND to extract relevant CONTACT plist and other information and passes them to `consult-mu-contacts--copy-email'. +This is a wrapper function around `consult-mu-contacts--copy-email'. It +parses CAND to extract relevant CONTACT plist and other information and +passes them to `consult-mu-contacts--copy-email'. -To use this as the default action for consult-mu-contacts, set `consult-mu-contacts-default-action' to #'consult-mu-contacts--copy-email-action." +To use this as the default action for consult-mu-contacts, set +`consult-mu-contacts-default-action' to +\=#'consult-mu-contacts--copy-email-action." (let* ((info (cdr cand)) (contact (plist-get info :contact))) (consult-mu-contacts--copy-email contact))) @@ -2664,30 +2877,39 @@ To use this as the default action for consult-mu-contacts, set `consult-mu-conta *** compose email #+begin_src emacs-lisp (defun consult-mu-contacts--compose-to (contact) - "compose an email to CONTACT using `mu4e-compose-new'." + "Compose an email to CONTACT using `mu4e-compose-new'." (let* ((email (plist-get contact :email))) (mu4e-compose-new email))) (defun consult-mu-contacts--compose-to-action (cand) "Open a new buffer to compose a message to contact candidate, CAND. -This is a wrapper function around `consult-mu-contacts--compose-to'. It parses CAND to extract relevant CONTACT plist and other information and passes them to `consult-mu-contacts--compose-to'. +This is a wrapper function around `consult-mu-contacts--compose-to'. It +parses CAND to extract relevant CONTACT plist and other information and +passes them to `consult-mu-contacts--compose-to'. -To use this as the default action for consult-mu-contacts, set `consult-mu-contacts-default-action' to #'consult-mu-contacts--compose-to-action." +To use this as the default action for consult-mu-contacts, set +`consult-mu-contacts-default-action' to \=#'consult-mu-contacts--compose-to-action." (let* ((info (cdr cand)) (contact (plist-get info :contact))) (consult-mu-contacts--compose-to contact))) + #+end_src ** Fontend Interactive Commands **** consult-mu-contacts ***** format candidate #+begin_src emacs-lisp (defun consult-mu-contacts--format-candidate (string input highlight) - "Formats minibuffer candidates for `consult-mu-contacts'. -STRING is the output retrieved from `mu cfind INPUT ...` in the command line. + "Format minibuffer candidates for `consult-mu-contacts'. + +STRING is the output retrieved from “mu cfind INPUT ...” in the command +line. + INPUT is the query from the user. -if HIGHLIGHT is t, input is highlighted with `consult-mu-highlight-match-face' in the minibuffer." + +If HIGHLIGHT is non-nil, input is highlighted with +`consult-mu-highlight-match-face' in the minibuffer." (let* ((query input) (email (consult-mu--message-extract-email-from-string string)) (name (string-trim (replace-regexp-in-string email "" string nil t nil nil))) @@ -2700,7 +2922,7 @@ if HIGHLIGHT is t, input is highlighted with `consult-mu-highlight-match-face' i (if (and consult-mu-highlight-matches highlight) (cond ((listp match-str) - (mapcar (lambda (match) (setq str (consult-mu--highlight-match match str t))) match-str)) + (mapc (lambda (match) (setq str (consult-mu--highlight-match match str t))) match-str)) ((stringp match-str) (setq str (consult-mu--highlight-match match-str str t)))) str) @@ -2710,7 +2932,9 @@ if HIGHLIGHT is t, input is highlighted with `consult-mu-highlight-match-face' i ***** add history #+begin_src emacs-lisp (defun consult-mu-contacts--add-history () - "Make a list of emails from current buffer to add to `consult-mu-contacts''s history." + "Get list of emails in the current buffer. + +This is used to add the emails in the current buffer to history." (let ((add (list))) (pcase major-mode ((or mu4e-view-mode mu4e-compose-mode org-msg-edit-mode message-mode) @@ -2723,13 +2947,14 @@ if HIGHLIGHT is t, input is highlighted with `consult-mu-highlight-match-face' i (consult-mu--message-emails-string-to-list (consult-mu--message-get-header-field "bcc")) (consult-mu--message-emails-string-to-list (consult-mu--message-get-header-field "reply-to"))))) (_ (list))))) + #+end_src ***** group #+begin_src emacs-lisp (defun consult-mu-contacts--group-name (cand) - "Gets the group name of CAND using `consult-mu-contacts-group-by' -See `consult-mu-contacts-group-by' for details of grouping options. -" + "Get the group name of CAND using `consult-mu-contacts-group-by'. + +See `consult-mu-contacts-group-by' for details of grouping options." (let* ((contact (get-text-property 0 :contact cand)) (email (plist-get contact :email)) (name (plist-get contact :name)) @@ -2746,51 +2971,60 @@ See `consult-mu-contacts-group-by' for details of grouping options. (_ nil)))) (defun consult-mu-contacts--group (cand transform) -"Group function for `consult-mu-contacts''s minibuffer candidates. +"Group function for `consult-mu-contacts' candidates. -This is passed as GROUP to `consult--read' on candidates and is used to group contacts using `consult-mu-contacts--group-name'." +CAND `consult-mu-contacts--group-name' to get the group name for contact. +When TRANSFORM is non-nil, the name of the candiate is used as group title." (when-let ((name (consult-mu-contacts--group-name cand))) (if transform (substring cand) name))) + #+end_src ***** lookup #+begin_src emacs-lisp (defun consult-mu-contacs--lookup () -"Lookup function for `consult-mu-contacs' minibuffer candidates. + "Lookup function for `consult-mu-contacs' minibuffer candidates. -This is passed as LOOKUP to `consult--read' on candidates and is used to format the output when a candidate is selected." +This is passed as LOOKUP to `consult--read' on candidates and is used to +format the output when a candidate is selected." (lambda (sel cands &rest args) (let* ((info (cdr (assoc sel cands))) (contact (plist-get info :contact)) (name (plist-get contact :name)) (email (plist-get contact :email))) (cons (or name email) info)))) + #+end_src ***** predicate #+begin_src emacs-lisp (defun consult-mu-contatcs--predicate (cand) -"Predicate function for `consult-mu-contacs' minibuffer candidates. + "Predicate function for `consult-mu-contacs' candidate, CAND. -This is passed as Predicate to `consult--read' on candidates and is used to remove contacts matching `consult-mu-contacts-ignore-list' from the list of candidtaes. +This is passed as Predicate to `consult--read' on candidates and is used to +remove contacts matching `consult-mu-contacts-ignore-list' from the list of +candidtaes. -note that `consult-mu-contacts-ignore-case-fold-search' is used to define case (in)sensitivity as well." +Note that `consult-mu-contacts-ignore-case-fold-search' is used to define +case (in)sensitivity as well." -(let* ((contact (plist-get (cdr cand) :contact)) - (email (plist-get contact :email)) - (name (plist-get contact :name)) - (case-fold-search consult-mu-contacts-ignore-case-fold-search)) - (if (seq-empty-p (seq-filter (lambda (reg) (or (string-match-p reg email) - (string-match-p reg name))) - consult-mu-contacts-ignore-list)) - t - nil))) + (let* ((contact (plist-get (cdr cand) :contact)) + (email (plist-get contact :email)) + (name (plist-get contact :name)) + (case-fold-search consult-mu-contacts-ignore-case-fold-search)) + (if (seq-empty-p (seq-filter (lambda (reg) (or (string-match-p reg email) + (string-match-p reg name))) + consult-mu-contacts-ignore-list)) + t + nil))) #+end_src ***** state/preview #+begin_src emacs-lisp (defun consult-mu-contacts--state () "State function for `consult-mu-contacts' candidates. -This is passed as STATE to `consult--read' and is used to preview or do other actions on the candidate." + +This is passed as STATE to `consult--read' and is used to preview or do +other actions on the candidate." (lambda (action cand) (let ((preview (consult--buffer-preview))) (pcase action @@ -2807,19 +3041,20 @@ This is passed as STATE to `consult--read' and is used to preview or do other ac ***** transform #+begin_src emacs-lisp :lexical t (defun consult-mu-contacts--transform (input) - "Adds annotation to minibuffer candiates for `consult-mu-contacts'. + "Add annotation to minibuffer candiates for `consult-mu-contacts'. Format each candidates with `consult-gh--repo-format' and INPUT." (lambda (cands) (cl-loop for cand in cands collect (consult-mu-contacts--format-candidate cand input t)))) + #+end_src ***** builder #+begin_src emacs-lisp (defun consult-mu-contacts--builder (input) - "Build mu command line for searching contacts by INPUT (e.g. `mu cfind INPUT)`." + "Build mu command line for searching contacts by INPUT." (pcase-let* ((consult-mu-args (append consult-mu-args '("cfind"))) (cmd (consult--build-args consult-mu-args)) (`(,arg . ,opts) (consult--command-split input)) @@ -2842,36 +3077,52 @@ Format each candidates with `consult-gh--repo-format' and INPUT." (list (string-join re " ")) opts) hl))))) + #+end_src ***** internal async command #+begin_src emacs-lisp :lexical t (defun consult-mu-contacts--async (prompt builder &optional initial) -"Query mu4e contacts asynchronously. + "Query mu4e contacts asynchronously. -This is a non-interactive internal function. For the interactive version see `consult-mu-contacts'. +This is a non-interactive internal function. For the interactive version +see `consult-mu-contacts'. -It runs the command line from `consult-mu-contacts--builder' in an async process and returns the results (list of contacts) as a completion table in minibuffer that will be passed to `consult--read'. The completion table gets dynamically updated as the user types in the minibuffer. Each candidate in the minibuffer is formatted by `consult-mu-contacts--transform' to add annotation and other info to the candidate. +It runs the command line from `consult-mu-contacts--builder' in an async +process and returns the results \(list of contacts\) as a completion table +in minibuffer that will be passed to `consult--read'. The completion table +gets dynamically updated as the user types in the minibuffer. Each +candidate in the minibuffer is formatted by +`consult-mu-contacts--transform' to add annotation and other info to the +candidate. -PROMPT is the prompt in the minibuffer (passed as PROMPT to `consult--red'.) -BUILDER is an async builder function passed to `consult--async-command'. -INITIAL is an optional arg for the initial input in the minibuffer. (passed as INITITAL to `consult--read'.) +Description of Arguments: + PROMPT the prompt in the minibuffer. + \(passed as PROMPT to `consult--red'\) + BUILDER an async builder function passed to `consult--async-command'. + INITIAL an optional arg for the initial input in the minibuffer. + \(passed as INITITAL to `consult--read'\) -commandline arguments/options (see `mu cfind --help` in the command line for details) can be passed to the minibuffer input similar to `consult-grep'. For example the user can enter: +commandline arguments/options \(run “mu cfind --help” in the command line +for details\) can be passed to the minibuffer input similar to +`consult-grep'. For example the user can enter: -`#john -- --maxnum 10' +“#john -- --maxnum 10” -this will search for contacts with the query \"john\", and retrives a maximum of 10 contacts. +This will search for contacts with the query “john”, and retrives a maximum +of 10 contacts. -Also, the results can further be narrowed by entering \"#\" similar to `consult-grep'. +Also, the results can further be narrowed by +`consult-async-split-style' \(e.g. by entering “#” when +`consult-async-split-style' is set to \='perl\). For example: -`#john -- --maxnum 10#@gmail' +“#john -- --maxnum 10#@gmail” -will retrieve the message as the example above, then narrows down the completion table to candidates that match \"@gmail\". -" +Will retrieve the message as the example above, then narrows down the +completion table to candidates that match “@gmail”." (consult--read (consult--process-collection builder :transform (consult--async-transform-by-input #'consult-mu-contacts--transform)) @@ -2893,36 +3144,46 @@ will retrieve the message as the example above, then narrows down the completion ***** interactive command #+begin_src emacs-lisp (defun consult-mu-contacts (&optional initial noaction) - "Lists results of `mu cfind` Asynchronously. + "List results of “mu cfind” asynchronously. + +This is an interactive wrapper function around +`consult-mu-contacts--async'. It queries the user for a search term in the +minibuffer, then fetches a list of contacts for the entered search term as +a minibuffer completion table for selection. The list of candidates in the +completion table are dynamically updated as the user changes the entry. -This is an interactive wrapper function around `consult-mu-contacts--async'. It queries the user for a search term in the minibuffer, then fetches a list of contacts for the entered search term as a minibuffer completion table for selection. The list of candidates in the completion table are dynamically updated as the user changes the entry. +INITIAL is an optional arg for the initial input in the minibuffer \(passed +as INITITAL to `consult-mu-contacts--async'\). Upon selection of a candidate either - the candidate is returned if NOACTION is non-nil or - - the candidate is passed to `consult-mu-contacts-action' if NOACTION is nil. + - the candidate is passed to `consult-mu-contacts-action' if NOACTION is + nil. -Additional commandline arguments can be passed in the minibuffer entry by typing `--` followed by command line arguments. +Additional commandline arguments can be passed in the minibuffer entry by +typing “--” followed by command line arguments. For example the user can enter: -`#john doe -- -n 10' +“#john doe -- -n 10” -this will run a contact search with the query \"john doe\" and changes the search limit to 10. +This will run a contact search with the query “john doe” and changes the +search limit to 10. +Also, the results can further be narrowed by `consult-async-split-style' +\(e.g. by entering “#” when `consult-async-split-style' is set to \='perl\). -Also, the results can further be narrowed by entering \"#\" similar to `consult-grep'. For example: -`#john doe -- -n 10#@gmail' +“#john doe -- -n 10#@gmail” -will retrieve the message as the example above, then narrows down the completion table to candidates that match \"@gmail\". +will retrieve the message as the example above, then narrows down to the +candidates that match “@gmail”. -INITIAL is an optional arg for the initial input in the minibuffer. (passed as INITITAL to `consult-mu-contacts--async'). - -For more details on consult--async functionalities, see `consult-grep' and the official manual of consult, here: https://github.com/minad/consult. -" +For more details on consult--async functionalities, see `consult-grep' and +the official manual of consult, here: https://github.com/minad/consult." (interactive) (save-mark-and-excursion (consult-mu--execute-all-marks)) @@ -2935,20 +3196,22 @@ For more details on consult--async functionalities, see `consult-grep' and the o (progn (funcall consult-mu-contacts-action sel) sel)))) + #+end_src ** Provide #+begin_src emacs-lisp ;;; provide `consult-mu-contacts' module (provide 'consult-mu-contacts) + #+end_src ** Footer #+begin_src emacs-lisp -;;; consult-mu-contacts.el ends here +;;; consult-mu-contacts.el ends here #+end_src * consult-mu-contacts-embark.el :PROPERTIES: -:header-args:emacs-lisp: :results none :mkdirp yes :link yes :tangle ./extras/consult-mu-contacts-embark.el +:header-args:emacs-lisp: :results none :mkdirp yes :comments none :tangle ./extras/consult-mu-contacts-embark.el :END: *** Header #+begin_src emacs-lisp @@ -2993,6 +3256,7 @@ For more details on consult--async functionalities, see `consult-grep' and the o ;;; Code: + #+end_src *** Main @@ -3000,48 +3264,49 @@ This section includes additional useful embark actions as well as possible keyma #+begin_src emacs-lisp ;;; Requirements + (require 'embark) (require 'consult-mu) (require 'consult-mu-embark) (defun consult-mu-contacts-embark-insert-email (cand) - "Embark function for inserting contact's email." + "Embark function for inserting CAND's email." (let* ((contact (get-text-property 0 :contact cand)) (email (plist-get contact :email))) - (insert (concat email "; ")))) + (insert (concat email "; ")))) (defun consult-mu-contacts-embark-kill-email (cand) - "Embark function for copying contact's email." + "Embark function for copying CAND's email." (let* ((contact (get-text-property 0 :contact cand)) (email (plist-get contact :email))) - (kill-new email))) + (kill-new email))) (defun consult-mu-contacts-embark-get-alternative (cand) - "Embark function for copying contact's email." + "Embark function for copying CAND's email." (let* ((contact (get-text-property 0 :contact cand)) (name (string-trim (plist-get contact :name))) (email (plist-get contact :email)) (user (string-trim (replace-regexp-in-string "@.*" "" email)))) - (consult-mu-contacts (cond - ((not (string-empty-p name)) - name) - ((not (string-empty-p user)) - user) - ((t "")))))) + (consult-mu-contacts (cond + ((not (string-empty-p name)) + name) + ((not (string-empty-p user)) + user) + ((t "")))))) (defun consult-mu-contacts-embark-compose (cand) - "Embark function for `consult-mu-contacts--compose-to'." + "Embark function for composing an email to CAND." (let* ((contact (get-text-property 0 :contact cand))) - (consult-mu-contacts--compose-to contact))) + (consult-mu-contacts--compose-to contact))) (defun consult-mu-contacts-embark-search-messages (cand) "Embark function for searching messages from CAND using `consult-mu'." (let* ((contact (get-text-property 0 :contact cand)) (email (plist-get contact :email))) - (consult-mu (concat "from:" email)))) + (consult-mu (concat "from:" email)))) (defun consult-mu-contacts-embark-default-action (cand) - "Run `consult-mu-contacts-action' on the candidate." + "Run `consult-mu-contacts-action' on CAND." (let* ((contact (get-text-property 0 :contact cand)) (query (get-text-property 0 :query cand)) (newcand (cons cand `(:contact ,contact :query ,query)))) @@ -3067,5 +3332,5 @@ This section includes additional useful embark actions as well as possible keyma (provide 'consult-mu-contacts-embark) -;;; consult-mu-contacts-embark.el ends here +;;; consult-mu-contacts-embark.el ends here #+end_src diff --git a/extras/consult-mu-compose-embark.el b/extras/consult-mu-compose-embark.el index 964c333..8447b28 100644 --- a/extras/consult-mu-compose-embark.el +++ b/extras/consult-mu-compose-embark.el @@ -46,16 +46,18 @@ (require 'consult-mu-embark) (defun consult-mu-compose-embark-attach-file (cand) - "Run `consult-mu-attach-files' on the candidate." - (funcall (apply-partially #'consult-mu-compose-attach cand))) + "Run `consult-mu-attach-files' on CAND." + (funcall (apply-partially #'consult-mu-compose-attach cand))) ;;; add consult-mu-attach to embark-file-map (defun consult-mu-compose-embark-bind-attach-file-key (&optional key) -"Binds `consult-mu-embark-attach-file-key' to `consult-mu-compose-embark-attach-file' in `embark-file-map'. + "Binds `consult-mu-embark-attach-file-key'. -If KEY is non-nil binds KEY instead of `consult-mu-embark-attach-file-key'." -(if-let ((keyb (or key (kbd consult-mu-embark-attach-file-key)))) -(define-key embark-file-map keyb #'consult-mu-compose-embark-attach-file))) +Bind `consult-mu-embark-attach-file-key' to +`consult-mu-compose-embark-attach-file' in `embark-file-map'. If KEY is +non-nil binds KEY instead of `consult-mu-embark-attach-file-key'." + (if-let ((keyb (or key (kbd consult-mu-embark-attach-file-key)))) + (define-key embark-file-map keyb #'consult-mu-compose-embark-attach-file))) (consult-mu-compose-embark-bind-attach-file-key) @@ -67,4 +69,4 @@ If KEY is non-nil binds KEY instead of `consult-mu-embark-attach-file-key'." (provide 'consult-mu-compose-embark) -;;; consult-mu-compose-embark.el ends here +;;; consult-mu-compose-embark.el ends here diff --git a/extras/consult-mu-compose.el b/extras/consult-mu-compose.el index 49ab9eb..8703995 100644 --- a/extras/consult-mu-compose.el +++ b/extras/consult-mu-compose.el @@ -1,4 +1,4 @@ -;;; consult-mu-compose.el --- Consult Mu4e asynchronously in GNU Emacs -*- lexical-binding: t -*- +;;; consult-mu-compose.el --- Consult Mu4e asynchronously -*- lexical-binding: t -*- ;; Copyright (C) 2023 Armin Darvish @@ -43,21 +43,25 @@ ;;; Customization Variables (defcustom consult-mu-compose-use-dired-attachment 'in-dired - "Use a dired buffer for multiple file attachment? -If set to 'in-dired uses dired buffer and dired marks only when inside dired buffer -If 't, consult-mu will always use dired buffer for selecting attachment files similar to what Doom Emacs does (see https://github.com/doomemacs/doomemacs/blob/bea81278fd2ecb65db6a63dbcd6db2f52921ee41/modules/email/mu4e/autoload/email.el#L272). + "Use a Dired buffer for multiple file attachment? -If 'nil, consult-mu uses minibuffer completion for selection files to attach even if inside a dired buffer. +If set to \='in-dired uses `dired' buffer and `dired' marks only when inside +a `dired' buffer. If \='t, a `dired' buffer will be used for selecting attachment files similar to what Doom Emacs does: +URL `https://github.com/doomemacs/doomemacs/blob/bea81278fd2ecb65db6a63dbcd6db2f52921ee41/modules/email/mu4e/autoload/email.el#L272'. -By default this is set to 'in-dired." +If \='nil, consult-mu uses minibuffer completion for selection files to +attach, even if inside a `dired' buffer. + +By default this is set to \='in-dired." :group 'consult-mu :type '(choice (const :tag "Only use Dired if inside Dired Buffer" 'in-dired) (const :tag "Always use Dired" t) (const :tag "Never use Dired" nil))) (defcustom consult-mu-large-file-warning-threshold large-file-warning-threshold - "Threshold for size of file to require confirmation for preview when selecting files to attach to emails. -Files larger than this value in size will require user confirmation before previewing the file. Default value is set by `large-file-warning-threshold'. If nil, no cofnirmation is required." + "Threshold for size of file to require confirmation for preview. + +This is used when selecting files to attach to emails. Files larger than this value in size will require user confirmation before previewing the file. Default value is set by `large-file-warning-threshold'. If nil, no cofnirmation is required." :group 'consult-mu :type '(choice integer (const :tag "Never request confirmation" nil))) @@ -65,8 +69,9 @@ Files larger than this value in size will require user confirmation before previ (defcustom consult-mu-compose-preview-key consult-mu-preview-key "Preview key for `consult-mu-compose'. -This is similar to `consult-mu-preview-key' but explicitly for consult-mu-compose. -It is recommended to set this to something other than 'any to avoid loading preview buffers for each file." +This is similar to `consult-mu-preview-key' but explicitly for +consult-mu-compose. It is recommended to set this to something other than +\='any to avoid loading preview buffers for each file." :group 'consult-mu :type '(choice (const :tag "Any key" any) (list :tag "Debounced" @@ -84,10 +89,12 @@ It is recommended to set this to something other than 'any to avoid loading prev (const :tag "no key binding" nil))) (defvar consult-mu-compose-attach-history nil - "History variable for file attachment used in `consult-mu-compose--read-file-attach'.") + "History variable for file attachment. + +It is used in `consult-mu-compose--read-file-attach'.") (defvar consult-mu-compose-current-draft-buffer nil - "Stores the buffer that is being edited.") + "Store the buffer that is being edited.") (defun consult-mu-compose--read-file-attach (&optional initial) "Read files in the minibuffer to attach to an email. @@ -113,7 +120,7 @@ INITIAL is the initial input in the minibuffer." (file-attributes filename)))) (confirm (if (and filename (>= filesize consult-mu-large-file-warning-threshold)) - (yes-or-no-p (format "File is %s Bytes. Do you really want to preview it?" filesize)) + (yes-or-no-p (format "File is %s Bytes. Do you really want to preview it?" filesize)) t))) (if confirm (funcall preview action @@ -155,7 +162,7 @@ INITIAL is the initial input in the minibuffer." (file-attributes filename)))) (confirm (if (and filename (>= filesize consult-mu-large-file-warning-threshold)) - (yes-or-no-p (format "File is %s Bytes. Do you really want to preview it?" filesize)) + (yes-or-no-p (format "File is %s Bytes. Do you really want to preview it?" filesize)) t))) (if confirm (funcall preview action @@ -169,7 +176,7 @@ INITIAL is the initial input in the minibuffer." nil))) (defun consult-mu-compose-get-draft-buffer () - "Queries user to select a mu4e compose draft buffer" + "Query user to select a mu4e compose draft buffer." (save-excursion (if (and (consult-mu-compose-get-current-buffers) (y-or-n-p "Attach the files to an existing compose buffer? ")) @@ -203,8 +210,8 @@ INITIAL is the initial input in the minibuffer." (push (buffer-name buffer) buffers)))) (nreverse buffers))) -(defun consult-mu-compose--attach-files (files &optional mail-buffer &rest args) - "Attach FILE to email in MAIL-BUFFER compose buffer." +(defun consult-mu-compose--attach-files (files &optional mail-buffer &rest _args) + "Attach FILES to email in MAIL-BUFFER compose buffer." (let ((files (if (stringp files) (list files) files)) (mail-buffer (or mail-buffer (if (version<= mu4e-mu-version "1.12") (mu4e-compose 'new) (mu4e-compose-new))))) @@ -231,8 +238,8 @@ INITIAL is the initial input in the minibuffer." (_ (error "%s is not a compose buffer" (current-buffer))))))) -(defun consult-mu-compose--remove-files (files &optional mail-buffer &rest args) - "Removes FILES from current attachments in MAIL-BUFFER." +(defun consult-mu-compose--remove-files (files &optional mail-buffer &rest _args) + "Remove FILES from current attachments in MAIL-BUFFER." (let ((files (if (stringp files) (list files) files)) (mail-buffer (or mail-buffer (current-buffer)))) (with-current-buffer mail-buffer @@ -265,7 +272,9 @@ INITIAL is the initial input in the minibuffer." (message "file(s) %s detached" (mapconcat 'identity removed-files ", "))))))))) (defun consult-mu-compose-attach (&optional files mail-buffer) - "Attach FILES to email interactively." + "Attach FILES to email in MAIL-BUFFER interactively. + +MAIL-BUFFER defaults to `consult-mu-compose-current-draft-buffer'." (interactive) (let* ((consult-mu-compose-current-draft-buffer (cond ((or (derived-mode-p 'mu4e-compose-mode) (derived-mode-p 'org-msg-edit-mode) (derived-mode-p 'message-mode)) (current-buffer)) @@ -386,4 +395,4 @@ INITIAL is the initial input in the minibuffer." ;;; provide `consult-mu-compose' module (provide 'consult-mu-compose) -;;; consult-mu-compose.el ends here +;;; consult-mu-compose.el ends here diff --git a/extras/consult-mu-contacts-embark.el b/extras/consult-mu-contacts-embark.el index a7a0c95..dd49427 100644 --- a/extras/consult-mu-contacts-embark.el +++ b/extras/consult-mu-contacts-embark.el @@ -41,48 +41,49 @@ ;;; Code: ;;; Requirements + (require 'embark) (require 'consult-mu) (require 'consult-mu-embark) (defun consult-mu-contacts-embark-insert-email (cand) - "Embark function for inserting contact's email." + "Embark function for inserting CAND's email." (let* ((contact (get-text-property 0 :contact cand)) (email (plist-get contact :email))) - (insert (concat email "; ")))) + (insert (concat email "; ")))) (defun consult-mu-contacts-embark-kill-email (cand) - "Embark function for copying contact's email." + "Embark function for copying CAND's email." (let* ((contact (get-text-property 0 :contact cand)) (email (plist-get contact :email))) - (kill-new email))) + (kill-new email))) (defun consult-mu-contacts-embark-get-alternative (cand) - "Embark function for copying contact's email." + "Embark function for copying CAND's email." (let* ((contact (get-text-property 0 :contact cand)) (name (string-trim (plist-get contact :name))) (email (plist-get contact :email)) (user (string-trim (replace-regexp-in-string "@.*" "" email)))) - (consult-mu-contacts (cond - ((not (string-empty-p name)) - name) - ((not (string-empty-p user)) - user) - ((t "")))))) + (consult-mu-contacts (cond + ((not (string-empty-p name)) + name) + ((not (string-empty-p user)) + user) + ((t "")))))) (defun consult-mu-contacts-embark-compose (cand) - "Embark function for `consult-mu-contacts--compose-to'." + "Embark function for composing an email to CAND." (let* ((contact (get-text-property 0 :contact cand))) - (consult-mu-contacts--compose-to contact))) + (consult-mu-contacts--compose-to contact))) (defun consult-mu-contacts-embark-search-messages (cand) "Embark function for searching messages from CAND using `consult-mu'." (let* ((contact (get-text-property 0 :contact cand)) (email (plist-get contact :email))) - (consult-mu (concat "from:" email)))) + (consult-mu (concat "from:" email)))) (defun consult-mu-contacts-embark-default-action (cand) - "Run `consult-mu-contacts-action' on the candidate." + "Run `consult-mu-contacts-action' on CAND." (let* ((contact (get-text-property 0 :contact cand)) (query (get-text-property 0 :query cand)) (newcand (cons cand `(:contact ,contact :query ,query)))) @@ -108,4 +109,4 @@ (provide 'consult-mu-contacts-embark) -;;; consult-mu-contacts-embark.el ends here +;;; consult-mu-contacts-embark.el ends here diff --git a/extras/consult-mu-contacts.el b/extras/consult-mu-contacts.el index 1bd305a..710b1a6 100644 --- a/extras/consult-mu-contacts.el +++ b/extras/consult-mu-contacts.el @@ -1,4 +1,4 @@ -;;; consult-mu-contacts.el --- Consult Mu4e asynchronously in GNU Emacs -*- lexical-binding: t -*- +;;; consult-mu-contacts.el --- Consult Mu4e asynchronously -*- lexical-binding: t -*- ;; Copyright (C) 2023 Armin Darvish @@ -45,15 +45,16 @@ ;;; Customization Variables (defcustom consult-mu-contacts-group-by :name - "What field to use to group the results in the minibuffer. + "What field to use to group the results in the minibuffer? -By default it is set to :name. But can be any of: +By default it is set to :name, but can be any of: - :name group by contact name - :email group by email of the contact - :domain group by the domain of the contact's email (e.g. domain.com in user@domain.com) - :user group by the ncontact's user name (e.g. user in user@domain.com) -" + :name group by contact name + :email group by email of the contact + :domain group by the domain of the contact's email + \(e.g. domain.com in user@domain.com\) + :user group by the ncontact's user name + \(e.g. user in user@domain.com\)" :group 'consult-mu :type '(radio (const :name) (const :email) @@ -61,10 +62,10 @@ By default it is set to :name. But can be any of: (const :user))) (defcustom consult-mu-contacts-action #'consult-mu-contacts--list-messages-action - "Which function to use when selecting a contact. + "Which function to use when selecting a contact? -By default it is bound to `consult-mu-contacts--list-messages-action'. -" +By default it is bound to +`consult-mu-contacts--list-messages-action'." :group 'consult-mu :type '(choice (function :tag "(Default) Show Messages from Contact" #'consult-mu-contacts--list-messages-action) (function :tag "Insert Email" #'consult-mu-contacts--insert-email-action) @@ -74,17 +75,19 @@ By default it is bound to `consult-mu-contacts--list-messages-action'. (defcustom consult-mu-contacts-ignore-list (list) "List of Regexps to ignore when searching contacts. -This is useful to filter certain addreses from contacts. For example you can remove no-reply adresses by setting this variable to '((“no-reply@example.com”)). -" +This is useful to filter certain addreses from contacts. For example, you +can remove no-reply adresses by setting this variable to +\='((“no-reply@example.com”))." :group 'consult-mu :type '(repeat :tag "Regexp List" regexp)) (defcustom consult-mu-contacts-ignore-case-fold-search case-fold-search - "Whether to ignore case when matching against `consult-mu-contacts-ignore-list'. -When non-nil, `consult-mu-contacts' performs case *insensitive* match with `consult-mu-contacts-ignore-list' and removes matches from candidates. + "Whether to ignore case when matching against ignore-list? -By default it is inherited from `case-fold-search'. -" +When non-nil, `consult-mu-contacts' performs case *insensitive* match with +`consult-mu-contacts-ignore-list' and removes matches from candidates. + +By default it is inherited from `case-fold-search'." :group 'consult-mu :type 'boolean) @@ -102,15 +105,19 @@ By default it is inherited from `case-fold-search'. (defun consult-mu-contacts--list-messages (contact) "List messages from CONTACT using `consult-mu'." (let* ((consult-mu-maxnum nil) - (email (plist-get contact :email))) - (consult-mu (format "contact:%s" email)))) + (email (plist-get contact :email))) + (consult-mu (format "contact:%s" email)))) (defun consult-mu-contacts--list-messages-action (cand) - "Searches the messages from contact candidate, CAND. + "Search the messages from contact candidate, CAND. -This is a wrapper function around `consult-mu-contacts--list-messages'. It parses CAND to extract relevant CONTACT plist and other information and passes them to `consult-mu-contacts--list-messages'. +This is a wrapper function around `consult-mu-contacts--list-messages'. It +parses CAND to extract relevant CONTACT plist and other information and +passes them to `consult-mu-contacts--list-messages'. -To use this as the default action for consult-mu-contacts, set `consult-mu-contacts-default-action' to #'consult-mu-contacts--list-messages-action." +To use this as the default action for consult-mu-contacts, set +`consult-mu-contacts-default-action' to +\=#'consult-mu-contacts--list-messages-action." (let* ((info (cdr cand)) @@ -118,58 +125,74 @@ To use this as the default action for consult-mu-contacts, set `consult-mu-conta (consult-mu-contacts--list-messages contact))) (defun consult-mu-contacts--insert-email (contact) - "insert email of CONTACT at point. + "Insert email of CONTACT at point. This is useful for inserting email when composing an email to contact." (let* ((email (plist-get contact :email))) - (insert (concat email "; ")))) + (insert (concat email "; ")))) (defun consult-mu-contacts--insert-email-action (cand) - "inserts the email from contact candidate, CAND. + "Insert the email from contact candidate, CAND. -This is a wrapper function around `consult-mu-contacts--insert-email'. It parses CAND to extract relevant CONTACT plist and other information and passes them to `consult-mu-contacts--insert-email'. +This is a wrapper function around `consult-mu-contacts--insert-email'. It +parses CAND to extract relevant CONTACT plist and other information and +passes them to `consult-mu-contacts--insert-email'. -To use this as the default action for consult-mu-contacts, set `consult-mu-contacts-default-action' to #'consult-mu-contacts--insert-email-action." +To use this as the default action for consult-mu-contacts, set +`consult-mu-contacts-default-action' to +\=#'consult-mu-contacts--insert-email-action." (let* ((info (cdr cand)) (contact (plist-get info :contact))) (consult-mu-contacts--insert-email contact))) (defun consult-mu-contacts--copy-email (contact) - "copy email of CONTACT to kill ring." + "Copy email of CONTACT to kill ring." (let* ((email (plist-get contact :email))) (kill-new email))) (defun consult-mu-contacts--copy-email-action (cand) - "Copies the email from contact candidate, CAND, to kill ring. + "Copy the email from contact candidate, CAND, to kill ring. -This is a wrapper function around `consult-mu-contacts--copy-email'. It parses CAND to extract relevant CONTACT plist and other information and passes them to `consult-mu-contacts--copy-email'. +This is a wrapper function around `consult-mu-contacts--copy-email'. It +parses CAND to extract relevant CONTACT plist and other information and +passes them to `consult-mu-contacts--copy-email'. -To use this as the default action for consult-mu-contacts, set `consult-mu-contacts-default-action' to #'consult-mu-contacts--copy-email-action." +To use this as the default action for consult-mu-contacts, set +`consult-mu-contacts-default-action' to +\=#'consult-mu-contacts--copy-email-action." (let* ((info (cdr cand)) (contact (plist-get info :contact))) (consult-mu-contacts--copy-email contact))) (defun consult-mu-contacts--compose-to (contact) - "compose an email to CONTACT using `mu4e-compose-new'." + "Compose an email to CONTACT using `mu4e-compose-new'." (let* ((email (plist-get contact :email))) (mu4e-compose-new email))) (defun consult-mu-contacts--compose-to-action (cand) "Open a new buffer to compose a message to contact candidate, CAND. -This is a wrapper function around `consult-mu-contacts--compose-to'. It parses CAND to extract relevant CONTACT plist and other information and passes them to `consult-mu-contacts--compose-to'. +This is a wrapper function around `consult-mu-contacts--compose-to'. It +parses CAND to extract relevant CONTACT plist and other information and +passes them to `consult-mu-contacts--compose-to'. -To use this as the default action for consult-mu-contacts, set `consult-mu-contacts-default-action' to #'consult-mu-contacts--compose-to-action." +To use this as the default action for consult-mu-contacts, set +`consult-mu-contacts-default-action' to \=#'consult-mu-contacts--compose-to-action." (let* ((info (cdr cand)) (contact (plist-get info :contact))) (consult-mu-contacts--compose-to contact))) (defun consult-mu-contacts--format-candidate (string input highlight) - "Formats minibuffer candidates for `consult-mu-contacts'. -STRING is the output retrieved from `mu cfind INPUT ...` in the command line. + "Format minibuffer candidates for `consult-mu-contacts'. + +STRING is the output retrieved from “mu cfind INPUT ...” in the command +line. + INPUT is the query from the user. -if HIGHLIGHT is t, input is highlighted with `consult-mu-highlight-match-face' in the minibuffer." + +If HIGHLIGHT is non-nil, input is highlighted with +`consult-mu-highlight-match-face' in the minibuffer." (let* ((query input) (email (consult-mu--message-extract-email-from-string string)) (name (string-trim (replace-regexp-in-string email "" string nil t nil nil))) @@ -182,14 +205,16 @@ if HIGHLIGHT is t, input is highlighted with `consult-mu-highlight-match-face' i (if (and consult-mu-highlight-matches highlight) (cond ((listp match-str) - (mapcar (lambda (match) (setq str (consult-mu--highlight-match match str t))) match-str)) + (mapc (lambda (match) (setq str (consult-mu--highlight-match match str t))) match-str)) ((stringp match-str) (setq str (consult-mu--highlight-match match-str str t)))) str) (cons str (list :contact contact :query query)))) (defun consult-mu-contacts--add-history () - "Make a list of emails from current buffer to add to `consult-mu-contacts''s history." + "Get list of emails in the current buffer. + +This is used to add the emails in the current buffer to history." (let ((add (list))) (pcase major-mode ((or mu4e-view-mode mu4e-compose-mode org-msg-edit-mode message-mode) @@ -204,9 +229,9 @@ if HIGHLIGHT is t, input is highlighted with `consult-mu-highlight-match-face' i (_ (list))))) (defun consult-mu-contacts--group-name (cand) - "Gets the group name of CAND using `consult-mu-contacts-group-by' -See `consult-mu-contacts-group-by' for details of grouping options. -" + "Get the group name of CAND using `consult-mu-contacts-group-by'. + +See `consult-mu-contacts-group-by' for details of grouping options." (let* ((contact (get-text-property 0 :contact cand)) (email (plist-get contact :email)) (name (plist-get contact :name)) @@ -223,16 +248,18 @@ See `consult-mu-contacts-group-by' for details of grouping options. (_ nil)))) (defun consult-mu-contacts--group (cand transform) -"Group function for `consult-mu-contacts''s minibuffer candidates. +"Group function for `consult-mu-contacts' candidates. -This is passed as GROUP to `consult--read' on candidates and is used to group contacts using `consult-mu-contacts--group-name'." +CAND `consult-mu-contacts--group-name' to get the group name for contact. +When TRANSFORM is non-nil, the name of the candiate is used as group title." (when-let ((name (consult-mu-contacts--group-name cand))) (if transform (substring cand) name))) (defun consult-mu-contacs--lookup () -"Lookup function for `consult-mu-contacs' minibuffer candidates. + "Lookup function for `consult-mu-contacs' minibuffer candidates. -This is passed as LOOKUP to `consult--read' on candidates and is used to format the output when a candidate is selected." +This is passed as LOOKUP to `consult--read' on candidates and is used to +format the output when a candidate is selected." (lambda (sel cands &rest args) (let* ((info (cdr (assoc sel cands))) (contact (plist-get info :contact)) @@ -241,25 +268,30 @@ This is passed as LOOKUP to `consult--read' on candidates and is used to format (cons (or name email) info)))) (defun consult-mu-contatcs--predicate (cand) -"Predicate function for `consult-mu-contacs' minibuffer candidates. + "Predicate function for `consult-mu-contacs' candidate, CAND. -This is passed as Predicate to `consult--read' on candidates and is used to remove contacts matching `consult-mu-contacts-ignore-list' from the list of candidtaes. +This is passed as Predicate to `consult--read' on candidates and is used to +remove contacts matching `consult-mu-contacts-ignore-list' from the list of +candidtaes. -note that `consult-mu-contacts-ignore-case-fold-search' is used to define case (in)sensitivity as well." +Note that `consult-mu-contacts-ignore-case-fold-search' is used to define +case (in)sensitivity as well." -(let* ((contact (plist-get (cdr cand) :contact)) - (email (plist-get contact :email)) - (name (plist-get contact :name)) - (case-fold-search consult-mu-contacts-ignore-case-fold-search)) - (if (seq-empty-p (seq-filter (lambda (reg) (or (string-match-p reg email) - (string-match-p reg name))) - consult-mu-contacts-ignore-list)) - t - nil))) + (let* ((contact (plist-get (cdr cand) :contact)) + (email (plist-get contact :email)) + (name (plist-get contact :name)) + (case-fold-search consult-mu-contacts-ignore-case-fold-search)) + (if (seq-empty-p (seq-filter (lambda (reg) (or (string-match-p reg email) + (string-match-p reg name))) + consult-mu-contacts-ignore-list)) + t + nil))) (defun consult-mu-contacts--state () "State function for `consult-mu-contacts' candidates. -This is passed as STATE to `consult--read' and is used to preview or do other actions on the candidate." + +This is passed as STATE to `consult--read' and is used to preview or do +other actions on the candidate." (lambda (action cand) (let ((preview (consult--buffer-preview))) (pcase action @@ -271,7 +303,7 @@ This is passed as STATE to `consult--read' and is used to preview or do other ac cand))))) (defun consult-mu-contacts--transform (input) - "Adds annotation to minibuffer candiates for `consult-mu-contacts'. + "Add annotation to minibuffer candiates for `consult-mu-contacts'. Format each candidates with `consult-gh--repo-format' and INPUT." (lambda (cands) @@ -280,7 +312,7 @@ Format each candidates with `consult-gh--repo-format' and INPUT." (consult-mu-contacts--format-candidate cand input t)))) (defun consult-mu-contacts--builder (input) - "Build mu command line for searching contacts by INPUT (e.g. `mu cfind INPUT)`." + "Build mu command line for searching contacts by INPUT." (pcase-let* ((consult-mu-args (append consult-mu-args '("cfind"))) (cmd (consult--build-args consult-mu-args)) (`(,arg . ,opts) (consult--command-split input)) @@ -305,30 +337,45 @@ Format each candidates with `consult-gh--repo-format' and INPUT." hl))))) (defun consult-mu-contacts--async (prompt builder &optional initial) -"Query mu4e contacts asynchronously. + "Query mu4e contacts asynchronously. -This is a non-interactive internal function. For the interactive version see `consult-mu-contacts'. +This is a non-interactive internal function. For the interactive version +see `consult-mu-contacts'. -It runs the command line from `consult-mu-contacts--builder' in an async process and returns the results (list of contacts) as a completion table in minibuffer that will be passed to `consult--read'. The completion table gets dynamically updated as the user types in the minibuffer. Each candidate in the minibuffer is formatted by `consult-mu-contacts--transform' to add annotation and other info to the candidate. +It runs the command line from `consult-mu-contacts--builder' in an async +process and returns the results \(list of contacts\) as a completion table +in minibuffer that will be passed to `consult--read'. The completion table +gets dynamically updated as the user types in the minibuffer. Each +candidate in the minibuffer is formatted by +`consult-mu-contacts--transform' to add annotation and other info to the +candidate. -PROMPT is the prompt in the minibuffer (passed as PROMPT to `consult--red'.) -BUILDER is an async builder function passed to `consult--async-command'. -INITIAL is an optional arg for the initial input in the minibuffer. (passed as INITITAL to `consult--read'.) +Description of Arguments: + PROMPT the prompt in the minibuffer. + \(passed as PROMPT to `consult--red'\) + BUILDER an async builder function passed to `consult--async-command'. + INITIAL an optional arg for the initial input in the minibuffer. + \(passed as INITITAL to `consult--read'\) -commandline arguments/options (see `mu cfind --help` in the command line for details) can be passed to the minibuffer input similar to `consult-grep'. For example the user can enter: +commandline arguments/options \(run “mu cfind --help” in the command line +for details\) can be passed to the minibuffer input similar to +`consult-grep'. For example the user can enter: -`#john -- --maxnum 10' +“#john -- --maxnum 10” -this will search for contacts with the query \"john\", and retrives a maximum of 10 contacts. +This will search for contacts with the query “john”, and retrives a maximum +of 10 contacts. -Also, the results can further be narrowed by entering \"#\" similar to `consult-grep'. +Also, the results can further be narrowed by +`consult-async-split-style' \(e.g. by entering “#” when +`consult-async-split-style' is set to \='perl\). For example: -`#john -- --maxnum 10#@gmail' +“#john -- --maxnum 10#@gmail” -will retrieve the message as the example above, then narrows down the completion table to candidates that match \"@gmail\". -" +Will retrieve the message as the example above, then narrows down the +completion table to candidates that match “@gmail”." (consult--read (consult--process-collection builder :transform (consult--async-transform-by-input #'consult-mu-contacts--transform)) @@ -345,36 +392,46 @@ will retrieve the message as the example above, then narrows down the completion :sort t)) (defun consult-mu-contacts (&optional initial noaction) - "Lists results of `mu cfind` Asynchronously. + "List results of “mu cfind” asynchronously. -This is an interactive wrapper function around `consult-mu-contacts--async'. It queries the user for a search term in the minibuffer, then fetches a list of contacts for the entered search term as a minibuffer completion table for selection. The list of candidates in the completion table are dynamically updated as the user changes the entry. +This is an interactive wrapper function around +`consult-mu-contacts--async'. It queries the user for a search term in the +minibuffer, then fetches a list of contacts for the entered search term as +a minibuffer completion table for selection. The list of candidates in the +completion table are dynamically updated as the user changes the entry. + +INITIAL is an optional arg for the initial input in the minibuffer \(passed +as INITITAL to `consult-mu-contacts--async'\). Upon selection of a candidate either - the candidate is returned if NOACTION is non-nil or - - the candidate is passed to `consult-mu-contacts-action' if NOACTION is nil. + - the candidate is passed to `consult-mu-contacts-action' if NOACTION is + nil. -Additional commandline arguments can be passed in the minibuffer entry by typing `--` followed by command line arguments. +Additional commandline arguments can be passed in the minibuffer entry by +typing “--” followed by command line arguments. For example the user can enter: -`#john doe -- -n 10' +“#john doe -- -n 10” -this will run a contact search with the query \"john doe\" and changes the search limit to 10. +This will run a contact search with the query “john doe” and changes the +search limit to 10. +Also, the results can further be narrowed by `consult-async-split-style' +\(e.g. by entering “#” when `consult-async-split-style' is set to \='perl\). -Also, the results can further be narrowed by entering \"#\" similar to `consult-grep'. For example: -`#john doe -- -n 10#@gmail' - -will retrieve the message as the example above, then narrows down the completion table to candidates that match \"@gmail\". +“#john doe -- -n 10#@gmail” -INITIAL is an optional arg for the initial input in the minibuffer. (passed as INITITAL to `consult-mu-contacts--async'). +will retrieve the message as the example above, then narrows down to the +candidates that match “@gmail”. -For more details on consult--async functionalities, see `consult-grep' and the official manual of consult, here: https://github.com/minad/consult. -" +For more details on consult--async functionalities, see `consult-grep' and +the official manual of consult, here: https://github.com/minad/consult." (interactive) (save-mark-and-excursion (consult-mu--execute-all-marks)) @@ -391,4 +448,4 @@ For more details on consult--async functionalities, see `consult-grep' and the o ;;; provide `consult-mu-contacts' module (provide 'consult-mu-contacts) -;;; consult-mu-contacts.el ends here +;;; consult-mu-contacts.el ends here