From 0d913c1a1f06b55ced82bc8f9a74b84302d5aca4 Mon Sep 17 00:00:00 2001 From: djcb Date: Thu, 14 Jun 2012 19:10:02 +0300 Subject: [PATCH] * new feature: deferred marking, i.e. mark now, decide what for later (WIP) --- TODO | 8 ++-- emacs/mu4e-headers.el | 71 ++++++++++++++++++------------------ emacs/mu4e-mark.el | 73 +++++++++++++++++++++++++++++-------- emacs/mu4e-view.el | 13 ++++++- emacs/mu4e.texi | 85 ++++++++++++++++++++++++++++--------------- 5 files changed, 167 insertions(+), 83 deletions(-) diff --git a/TODO b/TODO index d8f103e0..0d32796c 100644 --- a/TODO +++ b/TODO @@ -18,13 +18,14 @@ - contact completion (see Jacek's 'mu4e: using' mail) - actions for /all/ headers, actions for /all/ attachment - custom header fields in headers-view, message-view + - custom predicate functions for marking - guile integration - check if we can speed up mu4e-proc parsing by using search rather than regexp search - show maildirs as a tree, not a list in speed bar - - mark message, decide what to do with them later - make killing all windows (i.e.. 'fullscreen mode' optional) - - improve fringe marks (see https://github.com/djcb/mu/issues/21) + - better naming for draft buffers + - review emacs menus ** Done @@ -50,7 +51,8 @@ https://github.com/djcb/mu/issues/26) - *FIX* don't remove unknown message flags when moving - make guile/gtk/webkit dependency optional - + - improve fringe marks (see https://github.com/djcb/mu/issues/21) + - mark message, decide what to do with them later (i.e.. 'deferred marking') # Local Variables: diff --git a/emacs/mu4e-headers.el b/emacs/mu4e-headers.el index a13a36ba..4df93c23 100644 --- a/emacs/mu4e-headers.el +++ b/emacs/mu4e-headers.el @@ -30,7 +30,9 @@ (eval-when-compile (byte-compile-disable-warning 'cl-functions)) (require 'cl) +(require 'fringe) (require 'hl-line) + (require 'mu4e-utils) ;; utility functions (require 'mu4e-proc) (require 'mu4e-vars) @@ -310,24 +312,34 @@ after the end of the search results." ;; highlight the first message (mu4e~headers-highlight (mu4e~headers-docid-at-point (point-min))))))))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - - - + +(defmacro mu4e~headers-defun-mark-func (mark) + "Define a function mu4e~headers-mark-MARK." + (let ((funcname (intern (concat "mu4e~headers-mark-" (symbol-name mark)))) + (docstring (concat "Mark header at point with " (symbol-name mark) "."))) + `(defun ,funcname () ,docstring + (interactive) + (mu4e-headers-mark-and-next (quote ,mark))))) + +;; define our mark functions; there must be some way to do this in a loop but +;; since `mu4e~headers-defun-mark-func' is a macro, the argument must be a +;; literal value. +(mu4e~headers-defun-mark-func trash) +(mu4e~headers-defun-mark-func delete) +(mu4e~headers-defun-mark-func read) +(mu4e~headers-defun-mark-func unread) +(mu4e~headers-defun-mark-func flag) +(mu4e~headers-defun-mark-func unflag) +(mu4e~headers-defun-mark-func deferred) +(mu4e~headers-defun-mark-func unmark) + + ;;; headers-mode and mode-map ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (defvar mu4e-headers-mode-map nil "Keymap for *mu4e-headers* buffers.") (unless mu4e-headers-mode-map - ;; add some quick funcs so our key descriptions below are shorter - ;; TODO: defmacro this - (defun mu4e~headers-mark-trash()(interactive)(mu4e-headers-mark-and-next 'trash)) - (defun mu4e~headers-mark-delete()(interactive)(mu4e-headers-mark-and-next 'delete)) - (defun mu4e~headers-mark-unmark()(interactive)(mu4e-headers-mark-and-next 'unmark)) - (defun mu4e~headers-mark-read()(interactive)(mu4e-headers-mark-and-next 'read)) - (defun mu4e~headers-mark-unread()(interactive)(mu4e-headers-mark-and-next 'unread)) - (defun mu4e~headers-mark-flag()(interactive)(mu4e-headers-mark-and-next 'flag)) - (defun mu4e~headers-mark-unflag()(interactive)(mu4e-headers-mark-and-next 'unflag)) - + (setq mu4e-headers-mode-map (let ((map (make-sparse-keymap))) @@ -391,7 +403,11 @@ after the end of the search results." (define-key map (kbd "-") 'mu4e~headers-mark-unflag) (define-key map "m" 'mu4e-headers-mark-for-move-and-next) - + + (define-key map (kbd "*") 'mu4e~headers-mark-deferred) + (define-key map (kbd "") 'mu4e~headers-mark-deferred) + (define-key map (kbd "#") 'mu4e-mark-resolve-deferred-marks) + (define-key map "U" 'mu4e-mark-unmark-all) (define-key map "x" 'mu4e-mark-execute-all) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; @@ -718,25 +734,7 @@ header." (when msg (funcall func msg)))))) - -(defun mu4e~headers-get-markpair () - "Ask user for a mark; return (MARK . TARGET)." - (let* ((mark - (mu4e-read-option "Mark to set: " - '( ("move" . move) - ("dtrash" . trash) - ("Delete" . delete) - ("ounread" . unread) - ("read" . read) - ("+flag" . flag) - ("-unflag" . unflag) - ("unmark" . unmark)))) - (target - (when (eq mark 'move) - (mu4e-ask-maildir-check-exists "Move message to: ")))) - (cons mark target))) - -(defvar mu4e~headers-regexp-hist nil + (defvar mu4e~headers-regexp-hist nil "History list of regexps used.") (defun mu4e-headers-mark-pattern () @@ -744,7 +742,7 @@ header." match and a regular expression to match with. Then, mark all matching messages with that mark." (interactive) - (let ((markpair (mu4e~headers-get-markpair)) + (let ((markpair (mu4e~mark-get-markpair "Mark matched messages with: " t)) (field (mu4e-read-option "Field to match: " '( ("subject" . :subject) ("from" . :from) @@ -789,7 +787,10 @@ limited to the message at point and its descendants." (mu4e-message-at-point t) 'thread-id)) (path (mu4e~headers-get-thread-info (mu4e-message-at-point t) 'path)) - (markpair (mu4e~headers-get-markpair)) + (markpair + (mu4e~mark-get-markpair + (if subthread "Mark subthread with: " "Mark whole thread with: ") + t)) (last-marked-point)) (mu4e-headers-for-each (lambda (msg) diff --git a/emacs/mu4e-mark.el b/emacs/mu4e-mark.el index b3449c93..2329e7cb 100644 --- a/emacs/mu4e-mark.el +++ b/emacs/mu4e-mark.el @@ -52,8 +52,8 @@ particularly fast).") "Map (hash) of docid->markinfo; when a message is marked, the information is added here. -markinfo is a list consisting of the following: -\(mark target) +markinfo is a cons cell consisting of the following: +\(mark . target) where MARK is the type of mark (move, trash, delete) TARGET (optional) is the target directory (for 'move')") @@ -99,6 +99,7 @@ The following marks are available, and the corresponding props: `unread' n mark the message as unread `flag' n mark this message for flagging `unflag' n mark this message for unflagging + `deferred' n mark this message for *something* (decided later) `unmark' n unmark this message" (interactive) (let* ((docid (mu4e~headers-docid-at-point)) @@ -106,16 +107,17 @@ The following marks are available, and the corresponding props: ;; target (the target folder) the other ones get a pseudo "target", as ;; info for the user. (markcell - (case mark ;; the visual mark - ('move `("m" . ,target)) - ('trash '("d" . "trash")) - ('delete '("D" . "delete")) - ('unread '("o" . "unread")) - ('read '("r" . "read")) - ('flag '("+" . "flag")) - ('unflag '("-" . "unflag")) - ('unmark '(" " . nil)) - (t (error "Invalid mark %S" mark)))) + (case mark + (move `("m" . ,target)) + (trash '("d" . "trash")) + (delete '("D" . "delete")) + (unread '("o" . "unread")) + (read '("r" . "read")) + (flag '("+" . "flag")) + (unflag '("-" . "unflag")) + (deferred '("*" . "deferred")) + (unmark '(" " . nil)) + (otherwise (error "Invalid mark %S" mark)))) (markkar (car markcell)) (target (cdr markcell))) (unless docid (error "No message on this line")) @@ -128,7 +130,7 @@ The following marks are available, and the corresponding props: (remove-overlays (line-beginning-position) (line-end-position)) ;; now, let's set a mark (unless we were unmarking) (unless (eql mark 'unmark) - (puthash docid (list mark target) mu4e~mark-map) + (puthash docid (cons mark target) mu4e~mark-map) ;; when we have a target (ie., when moving), show the target folder in ;; an overlay (when (and target mu4e-headers-show-target) @@ -178,6 +180,45 @@ provided, function asks for it." (mu4e-mark-set 'move target)))) +(defun mu4e~mark-get-markpair (prompt &optional allow-deferred) + "Ask user for a mark; return (MARK . TARGET). If ALLOW-DEFERRED +is non-nil, allow the 'deferred' pseudo mark as well." + (let* ((marks '(("move" . move) + ("dtrash" . trash) + ("Delete" . delete) + ("ounread" . unread) + ("read" . read) + ("+flag" . flag) + ("-unflag" . unflag) + ("unmark" . unmark))) + (marks + (if allow-deferred + (append marks (list '("*deferred" . deferred))) + marks)) + (mark (mu4e-read-option prompt marks)) + (target + (when (eq mark 'move) + (mu4e-ask-maildir-check-exists "Move message to: ")))) + (cons mark target))) + + +(defun mu4e-mark-resolve-deferred-marks () + "Check if there are any deferred marks. If there are such marks, +replace them with a _real_ mark (ask the user which one)." + (interactive) + (let ((markpair)) + (maphash + (lambda (docid val) + (let ((mark (car val)) (target (cdr val))) + (when (eql mark 'deferred) + (unless markpair + (setq markpair + (mu4e~mark-get-markpair "Set deferred mark to: " nil))) + (save-excursion + (when (mu4e~headers-goto-docid docid) + (mu4e-mark-set (car markpair) (cdr markpair))))))) + mu4e~mark-map))) + (defun mu4e-mark-execute-all (&optional no-confirmation) "Execute the actions for all marked messages in this buffer. After the actions have been executed succesfully, the @@ -194,13 +235,14 @@ If NO-CONFIRMATION is non-nil, don't ask user for confirmation." (let ((marknum (hash-table-count mu4e~mark-map))) (if (zerop marknum) (message "Nothing is marked") + (mu4e-mark-resolve-deferred-marks) (when (or no-confirmation (y-or-n-p (format "Are you sure you want to execute %d mark%s?" marknum (if (> marknum 1) "s" "")))) (maphash (lambda (docid val) - (let ((mark (nth 0 val)) (target (nth 1 val))) + (let ((mark (car val)) (target (cdr val))) (case mark (move (mu4e~proc-move docid target)) (read (mu4e~proc-move docid nil "+S-u-N")) @@ -211,7 +253,8 @@ If NO-CONFIRMATION is non-nil, don't ask user for confirmation." (unless mu4e-trash-folder (error "`mu4e-trash-folder' not set")) (mu4e~proc-move docid mu4e-trash-folder "+T")) - (delete (mu4e~proc-remove docid))))) + (delete (mu4e~proc-remove docid)) + (otherwise (error "Unrecognized mark %S" mark))))) mu4e~mark-map)) (mu4e-mark-unmark-all) (message nil)))) diff --git a/emacs/mu4e-view.el b/emacs/mu4e-view.el index e82b91f5..fd7ee42b 100644 --- a/emacs/mu4e-view.el +++ b/emacs/mu4e-view.el @@ -428,7 +428,11 @@ is nil, and otherwise open it." (define-key map (kbd "+") 'mu4e-view-mark-flag) (define-key map (kbd "-") 'mu4e-view-mark-unflag) - + + (define-key map (kbd "*") 'mu4e-view-mark-deferred) + (define-key map (kbd "") 'mu4e-view-mark-deferred) + (define-key map (kbd "#") 'mu4e-mark-resolve-deferred-marks) + ;; misc (define-key map "w" 'mu4e-view-toggle-wrap-lines) (define-key map "h" 'mu4e-view-toggle-hide-cited) @@ -973,6 +977,13 @@ user that unmarking only works in the header list." (mu4e~view-mark-set 'unflag) (mu4e-view-headers-next)) +(defun mu4e-view-mark-deferred () + "Mark the current message for unflagging." + (interactive) + (mu4e~view-mark-set 'deferred) + (mu4e-view-headers-next)) + + (defun mu4e-view-marked-execute () "Execute the marks." (interactive) diff --git a/emacs/mu4e.texi b/emacs/mu4e.texi index 0de5b24a..20630bab 100644 --- a/emacs/mu4e.texi +++ b/emacs/mu4e.texi @@ -561,6 +561,9 @@ U unmark *all* messages % mark based on a regular expression T,t mark whole thread, subthread +SPC deferred mark (decide what to mark for later) +* resolve deferred marks + x execute actions for the marked messages composition @@ -646,7 +649,6 @@ include the previously captured message as an attachment, using The file @file{mu4e-actions.el} in the @t{mu4e} source distribution contains a number of example actions. - @subsection Split view @@ -738,10 +740,12 @@ or by using the keyboard; the default bindings are: @verbatim key description ---- ----------- +============================================================== n,p go to next, previous message y select the headers view (if it's visible) +searching +--------- s search e edit last query / narrow the search @@ -752,23 +756,32 @@ j jump to maildir M-left previous query M-right next query -C-+,C-- increase / decrease the number of headers shown - -a execute some action on the message - +marking +------- d mark for moving to the trash folder DEL,D mark for immediate deletion m mark for moving to another maildir folder +,- mark for flagging/unflagging +o,r mark message as unread, read + u unmark message at point +U unmark *all* messages + % mark based on a regular expression T,t mark whole thread, subthread +SPC deferred mark (decide what to mark for later) +* resolve deferred marks + +x execute actions for the marked messages + +composition +----------- R,F,C reply/forward/compose E edit (only allowed for draft messages) -. show the raw message view. 'q' takes you back. - +actions +------- g go to (visit) numbered URL (using `browse-url') (or: or RET with point on url) e extract (save) attachment (asks for number) @@ -776,10 +789,17 @@ e extract (save) attachment (asks for number) C-u e will extract multiple attachments o open attachment (asks for number) (or: or S-RET with point on attachment) -A execute some action on an attachment + +a execute some custom action on the message +A execute some custom action on an attachment + +misc +---- w toggle line wrapping h toggle showing cited parts +. show the raw message view. 'q' takes you back. +C-+,C-- increase / decrease the number of headers shown H get help q,z leave the message view @end verbatim @@ -1251,18 +1271,23 @@ apply to messages: | unflag | - | remove 'flagged' mark | | read | r | mark as read | | unread | o | marks as unread | +| deferred | * | mark now, decide later | | unmark | u | remove mark at point | -| remove all | U | remove all marks | +| unmark all | U | remove all marks | @end verbatim -After marking a header for something, the leftmost columns shows a character -(same as the keybinding) to remind you what you marked it with. Next to that, -@t{mu4e} displays the name of the mark, on top of the beginning of the header -line. This latter display is informative, but if you often mark many -(thousands) messages, this may slow down things significantly@footnote{this -uses an emacs feature called @emph{overlays}, which are slow when used a lot -in a buffer}. For this reason, you can disable this by setting -@code{mu4e-headers-show-target} to @code{nil}. +After marking a header for something, the left-most columns shows a character +to remind you what you marked it with. Next to that, @t{mu4e} displays the +name of the mark, on top of the beginning of the header line. This latter +display is informative, but if you often mark many (thousands) messages, this +may slow down things significantly@footnote{this uses an emacs feature called +@emph{overlays}, which are slow when used a lot in a buffer}. For this reason, +you can disable this by setting @code{mu4e-headers-show-target} to @code{nil}. + +@t{deferred} is a special kind of mark; you can use it to mark some messages, +and then decide later what mark to use for them. At any time, you can set the +actual mark with @code{mu4e-mark-resolve-deferred-marks} (@key{#}), or +@t{mu4e} will ask you for it when you execute the marks (@key{x}). @node Executing the marks @section Executing the marks @@ -1304,12 +1329,14 @@ You can invoke the actions with @key{a} for actions on messages, and @key{A} for actions on attachments. In the following, we'll gives some examples of defining actions. -Note, the format of the actions has changed slightly since version 0.9.8.4; -@t{mu4e} warns you if you use the old format still. The older format was: -@code{(DESCRIPTION SHORTCUT [VALUE])}, while the new format is a cons-cell, -@code{(DESCRIPTION . VALUE)}; see below for some examples. If your shortcut is -not also the first character of the description, simply prefix the description -with that character. +Note, the format of the actions has changed since version 0.9.8.4, and you +must change your configuration to use the new format; @t{mu4e} warns you when +you are using the old format. + +The older format was: @code{(DESCRIPTION SHORTCUT [VALUE])}, while the new +format is a cons-cell, @code{(DESCRIPTION . VALUE)}; see below for some +examples. If your shortcut is not also the first character of the description, +simply prefix the description with that character. @subsection Functions for actions @@ -1334,7 +1361,7 @@ After you have defined your function, you can add it to the list of actions, either @code{mu4e-headers-actions}, @code{mu4e-view-actions} or @code{mu4e-view-attachment-actions}. -Let's now look at some simple examples. +Let's take a at some simple examples. @subsection Example: adding an action in the headers view @@ -1398,9 +1425,9 @@ To help a bit with this, all functions and variables in @t{mu4e} marked for letters, so they will only appear at the end of completion buffers and the like. -Functions that start with @t{mu4e-view-} and @t{mu4e-headers-} should be called -only from that particular context (the message view and the headers view, -respectively). +Functions that start with @t{mu4e-view-} and @t{mu4e-headers-} should be +called only from that particular context (the message view and the headers +view, respectively). @subsection Example actions @@ -1918,7 +1945,7 @@ can also use functions like @code{mu4e-headers-mark-thread} (@key{T}), time, and @code{mu4e-headers-mark-pattern} (@key{%}) to mark all messages matching a certain regular expression. @item @emph{How can I use @t{BBDB}?} Currently, there is no built-in for -address management with @t{BBDB}; instead, we recommend @xref{Maintaining an +address management with @t{BBDB}; instead, we recommend @ref{Maintaining an address-book with org-contacts} for now. @item @emph{mu4e seems to return a mere subset of all matches - how can I get all?}. Indeed, for speed reasons (and because, if you are like the author, you