From 2367ab2d676ef3583570b9a5b38a7ca097a20910 Mon Sep 17 00:00:00 2001 From: djcb Date: Thu, 14 Jun 2012 21:54:24 +0300 Subject: [PATCH] * mu4e: add support for custom matcher functions (WIP) --- emacs/mu4e-headers.el | 52 +++++++++++++++++++++++++++++++++++-------- emacs/mu4e-mark.el | 1 + emacs/mu4e-utils.el | 13 ++++++++--- emacs/mu4e-view.el | 14 +++++++----- emacs/mu4e.texi | 44 ++++++++++++++++++++++++++++++++++++ 5 files changed, 107 insertions(+), 17 deletions(-) diff --git a/emacs/mu4e-headers.el b/emacs/mu4e-headers.el index 4df93c23..20417c56 100644 --- a/emacs/mu4e-headers.el +++ b/emacs/mu4e-headers.el @@ -87,6 +87,26 @@ are of the form: * SHORTCUT is a one-character shortcut to call this action * FUNC is a function which receives a message plist as an argument.") +(defvar mu4e-headers-custom-markers + '(("Older than" + (lambda (msg date) (time-less-p (mu4e-msg-field msg :date) date)) + (lambda () (mu4e-get-time-date "Match messages before: "))) + ("Newer than" + (lambda (msg date) (time-less-p date (mu4e-msg-field msg :date))) + (lambda () (mu4e-get-time-date "Match messages after: "))) + ("Bigger than" + (lambda (msg bytes) (> (mu4e-msg-field msg :size) (* 1024 bytes))) + (lambda () (read-number "Match messages bigger than (Kbytes): ")))) + "List of custom markers -- functions to mark message that match +some custom function. Each of the list members has the following format: + (NAME PREDICATE-FUNC PARAM-FUNC) +* NAME is the name of the predicate function, and the first character +is the shortcut (so keep those unique). +* PREDICATE-FUNC is a function that takes to parameters, MSG and (optionally) PARAM, +and should return non-nil when there's a match. +* PARAM-FUNC is function that is evaluated once, and its value is then passed to +PREDICATE-FUNC as PARAM. This is useful for getting user-input.") + (defvar mu4e-headers-sortfield 'date "Field to sort the headers by. Field must be a symbol, one of: date, subject, size, prio, from, to.") @@ -401,7 +421,7 @@ after the end of the search results." (define-key map (kbd "u") 'mu4e~headers-mark-unmark) (define-key map (kbd "+") 'mu4e~headers-mark-flag) (define-key map (kbd "-") 'mu4e~headers-mark-unflag) - + (define-key map (kbd "&") 'mu4e-headers-mark-custom) (define-key map "m" 'mu4e-headers-mark-for-move-and-next) (define-key map (kbd "*") 'mu4e~headers-mark-deferred) @@ -737,6 +757,16 @@ header." (defvar mu4e~headers-regexp-hist nil "History list of regexps used.") + (defun mu4e~headers-mark-for-each-if (markpair mark-pred &optional param) + "Mark all headers for with predicate function MARK-PRED return +non-nil with MARKPAIR. MARK-PRED is function that takes two +arguments, MSG (the message at point) and PARAM (a user-specified +parameter). MARKPAIR is a cell (MARK . TARGET)." + (mu4e-headers-for-each + (lambda (msg) + (when (funcall mark-pred msg param) + (mu4e-mark-at-point (car markpair) (cdr markpair)))))) + (defun mu4e-headers-mark-pattern () "Ask user for a kind of mark (move, delete etc.), a field to match and a regular expression to match with. Then, mark all @@ -750,8 +780,9 @@ matching messages with that mark." (pattern (read-string (mu4e-format "Regexp:") nil 'mu4e~headers-regexp-hist))) - (mu4e-headers-for-each - (lambda (msg) + (mu4e~headers-mark-for-each-if + markpair + (lambda (msg param) (let* ((do-mark) (value (mu4e-msg-field msg field))) (setq do-mark (if (member field '(:to :from :cc :bcc :reply-to)) @@ -759,10 +790,16 @@ matching messages with that mark." (let ((name (car contact)) (email (cdr contact))) (or (and name (string-match pattern name)) (and email (string-match pattern email))))) value) - (string-match pattern (or value "")))) - (when do-mark - (mu4e-mark-at-point (car markpair) (cdr markpair)))))))) + (string-match pattern (or value ""))))))))) +(defun mu4e-headers-mark-custom () + "Mark messages based on a user-provided predicate function." + (interactive) + (let* ((pred (mu4e-read-option "Match function: " + mu4e-headers-custom-markers)) + (param (when (cdr pred) (eval (cdr pred)))) + (markpair (mu4e~mark-get-markpair "Mark matched messages with: " t))) + (mu4e~headers-mark-for-each-if markpair (car pred) param))) (defun mu4e~headers-get-thread-info (msg what) "Get WHAT (a symbol, either path or thread-id) for MSG." @@ -863,8 +900,6 @@ to get it from; it's a symbol, either 'future or 'past." (pop mu4e~headers-query-future)))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - - ;;; interactive functions ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (defvar mu4e~headers-search-hist nil @@ -1083,7 +1118,6 @@ maildir)." (mu4e-mark-handle-when-leaving) (mu4e-headers-search (concat "\"maildir:" maildir "\"")))) - (defun mu4e-headers-split-view-resize (n) "In horizontal split-view, increase the number of lines shown by N; in vertical split-view, increase the number of columns shown by diff --git a/emacs/mu4e-mark.el b/emacs/mu4e-mark.el index 2329e7cb..be2f79c5 100644 --- a/emacs/mu4e-mark.el +++ b/emacs/mu4e-mark.el @@ -300,4 +300,5 @@ action', return nil means 'don't do anything'" (when (eq what 'apply) (mu4e-mark-execute-all t)))))))) + (provide 'mu4e-mark) diff --git a/emacs/mu4e-utils.el b/emacs/mu4e-utils.el index 60a51cc0..ab99c400 100644 --- a/emacs/mu4e-utils.el +++ b/emacs/mu4e-utils.el @@ -31,6 +31,7 @@ (require 'html2text) (require 'mu4e-vars) (require 'doc-view) +(require 'org) ;; for org-parse-time-string (defcustom mu4e-html2text-command nil "Shell command that converts HTML from stdin into plain text on @@ -104,7 +105,8 @@ User now will be presented with a list: (optionsstr (mapconcat (lambda (option) - (when (consp (cdr option)) + ;; try to detect old-style options... + (when (or (characterp (cdr option)) (null (cdr option))) (error (concat "Please use the new format for options/actions; " "see the manual"))) (let* ((kar (substring (car option) 0 1)) @@ -325,8 +327,6 @@ http://cr.yp.to/proto/maildir.html " ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - - (defun mu4e-display-size (size) "Get a string representation of SIZE (in bytes)." (cond @@ -807,5 +807,12 @@ displaying it). Do _not_ bury the current buffer, though." (delete-window win)))))) nil t))) +(defun mu4e-get-time-date (prompt) + "Determine the emacs time value for the time/date entered by user + after PROMPT. Formats are all that are accepted by + `parse-time-string'." + (let ((timestr (read-string (mu4e-format "%s" prompt)))) + (apply 'encode-time (org-parse-time-string timestr)))) + (provide 'mu4e-utils) ;;; End of mu4e-utils.el diff --git a/emacs/mu4e-view.el b/emacs/mu4e-view.el index fd7ee42b..e5246416 100644 --- a/emacs/mu4e-view.el +++ b/emacs/mu4e-view.el @@ -423,9 +423,10 @@ is nil, and otherwise open it." (define-key map (kbd "") 'mu4e-view-mark-for-delete) (define-key map (kbd "") 'mu4e-mark-for-delete) - (define-key map "D" 'mu4e-view-mark-for-delete) - (define-key map "m" 'mu4e-view-mark-for-move) - + (define-key map (kbd "D") 'mu4e-view-mark-for-delete) + (define-key map (kbd "m") 'mu4e-view-mark-for-move) + (define-key map (kbd "&") 'mu4e-view-mark-custom) + (define-key map (kbd "+") 'mu4e-view-mark-flag) (define-key map (kbd "-") 'mu4e-view-mark-unflag) @@ -926,6 +927,10 @@ attachments) in response to a (mu4e~proc-extract 'temp ... )." (mu4e-mark-for-move-set) (mu4e-mark-at-point mark))))) +(defun mu4e-view-mark-custom () + "Run some custom mark function." + (mu4e~view-in-headers-context + (mu4e-headers-mark-custom))) (defun mu4e~split-view-p () "Return t if we're in split-view, nil otherwise." @@ -983,8 +988,7 @@ user that unmarking only works in the header list." (mu4e~view-mark-set 'deferred) (mu4e-view-headers-next)) - -(defun mu4e-view-marked-execute () + (defun mu4e-view-marked-execute () "Execute the marks." (interactive) (mu4e~view-in-headers-context diff --git a/emacs/mu4e.texi b/emacs/mu4e.texi index 20630bab..b043e00a 100644 --- a/emacs/mu4e.texi +++ b/emacs/mu4e.texi @@ -1235,6 +1235,7 @@ Marking can happen in both the @ref{Headers view} and the @ref{Message view}. * What to mark for:: * Executing the marks:: * Leaving the headers buffer:: +* Custom mark functions:: * Some marking examples:: @end menu @@ -1302,6 +1303,48 @@ When you quit the buffer (for example, but doing a new search) with marks being present, @t{mu4e} asks you what to do with them, depending on the value of the variable @code{mu4e-headers-leave-behavior} -- see its documentation. +@node Custom mark functions +@section Custom mark functions + +Sometimes, the built-in functions to mark messages may not be sufficient for +your needs. For this, @t{mu4e} offers an easy way to define your own custom +mark functions. You can choose one of the custom marker functions using +@key{&} in @ref{Headers view} and @ref{Message view}. + +Custom mark functions should be appended to the list +@code{mu4e-headers-custom-markers}. Each of the elements of this list +('markers') is a list with three (or two) elements: +@itemize +@item The name of the marker - as short string describing this marker. The +first character of this string will also be its shortcut, so these should be +unique. +@item a predicate function taking two arguments @t{msg} and @t{param}- first, +@t{msg}, which is the message +plist (see @ref{The message s-expression}); second is a parameter provided by +the third of the marker elements (next item). The predicate function should +return non-nil if the messages matches. +@item (optionally) a function that is evaluated once, and its result is passed as a +parameter to the predicate function. This is useful to ask for user-input. +@end itemize + +So, let's look at an example: suppose we want to match all messages that have +more than @emph{n} recipients. We could do it like this: + +@lisp +(add-to-list 'mu4e-headers-custom-markers + '("More than n recipients" + (lambda (msg n) (> (+ (length (mu4e-msg-field msg :to)) + (length (mu4e-msg-field msg :cc))) n)) + (lambda () (read-number "Match messages with more recipients than: "))) t) +@end lisp + +After evaluating this, pressing @key{&} should let you choose the custom +marker function, and ask you for the parameters. + +As you can see, it's not very hard to define simple functions to match +messages. There are some more examples in the defaults for +`mu4e-headers-custom-markers'; see @file{mu4e-headers.el}. + @node Some marking examples @section Some marking examples @@ -1316,6 +1359,7 @@ press @key{% + s hello RET}. Note, the menu system helps you here; all you need to remember is @key{%} for @code{mu4e-headers-mark-pattern}. @end itemize + @node Actions @chapter Actions