[RFC][PATCH v2] emacs: Replace `notmuch-search-result-format' evaluation.
David Edmondson
dme at dme.org
Tue Nov 25 01:06:56 PST 2014
Alternative implementation of code that interprets
`notmuch-search-result-format' to insert the results of a search.
---
Added some more documentation, as per bremner.
emacs/notmuch.el | 300 +++++++++++++++++++++++++++++++++----------------------
1 file changed, 178 insertions(+), 122 deletions(-)
diff --git a/emacs/notmuch.el b/emacs/notmuch.el
index 218486a..eb79a29 100644
--- a/emacs/notmuch.el
+++ b/emacs/notmuch.el
@@ -66,23 +66,103 @@
"Placeholder variable when notmuch-version.el[c] is not available."))
(defcustom notmuch-search-result-format
- `(("date" . "%12s ")
- ("count" . "%-7s ")
- ("authors" . "%-20s ")
- ("subject" . "%s ")
- ("tags" . "(%s)"))
- "Search result formatting. Supported fields are:
- date, count, authors, subject, tags
-For example:
- (setq notmuch-search-result-format \(\(\"authors\" . \"%-40s\"\)
- \(\"subject\" . \"%s\"\)\)\)
-Line breaks are permitted in format strings (though this is
-currently experimental). Note that a line break at the end of an
-\"authors\" field will get elided if the authors list is long;
-place it instead at the beginning of the following field. To
-enter a line break when setting this variable with setq, use \\n.
-To enter a line break in customize, press \\[quoted-insert] C-j."
- :type '(alist :key-type (string) :value-type (string))
+ '((:width -12 :date_relative) " "
+ (:width -7 (:concat "[" :matched "/" :total "]")) " "
+ ;; This splitting will not be necessary once
+ ;; id:1414172643-28270-1-git-send-email-dme at dme.org is integrated.
+ (:width 40 (:eval (let* ((split-authors (split-string
+ (plist-get notmuch-thread :authors)
+ "|" t split-string-default-separators))
+ ;; There will always be matched authors.
+ (matched-authors (car split-authors))
+ ;; There may not be non-matched authors.
+ (non-matched-authors (mapconcat #'identity (cdr split-authors) " ")))
+ (notmuch-search-format-authors matched-authors non-matched-authors)))) " "
+ :subject " "
+ "(" (:eval (notmuch-tag-format-tags (plist-get notmuch-thread :tags)
+ (plist-get notmuch-thread :orig-tags)))
+ ")" "\n")
+
+ "Template for displaying search results.
+
+The value is a list of items to insert in the search
+results. Individual items should be in one of the following
+forms:
+
+A string that is inserted directly (e.g. \" \").
+
+A number that is inserted directly (e.g. 5).
+
+A list that is recursively evaluated (e.g. `(:concat FORM1
+ FORM2)').
+
+A function that is evaluated with a single argument, the current
+ thread.
+
+A symbol corresponding to an attribute of the
+ thread. Currently available attributes include:
+
+ :date_relative -- a user-readable rendering of the Date: header
+ of the first matching message in the thread, as a string.
+
+ :timestamp -- the number of seconds since the Epoch, 1970-01-01
+ 00:00:00 +0000 (UTC), corresponding to the Date: header of the
+ first matching message in the thread.
+
+ :subject -- the subject of the first matching message in the
+ thread as a string.
+
+ :authors -- a comma separated string containing a list of the
+ authors of messages in the thread. If there are non-matching
+ authors (i.e. the thread contains messages which did not match
+ the search terms and those messages have authors who are not
+ also authors of messages that did match the search terms) then
+ the matching and non-matching authors are separated by a `|'
+ symbol in the results in place of a comma.
+
+ :matched -- the number of messages that matched search terms in
+ the thread (a number).
+
+ :total -- the total number of messages in the thread (i.e. the
+ count of both matching and non-matching messages).
+
+ :tags -- a list of tags associated with messages in the
+ thread. Each tag is included as a distinct string.
+
+ :orig-tags -- a list of tags that were associated with messages
+ in the thread when the search originally took place.
+
+ :query -- a list with two elements. The first element is a query
+ string that will return all of the matching messages in the
+ current thread. The second element is a query string that will
+ return all of the non-matching messages in the current thread.
+
+ :thread -- the thread id of the matching thread, as a string.
+
+ For a full list of the attributes available, see the source code
+ for notmuch itself.
+
+A directive that describes how to evaluate the remainder of the
+ list. The following directives are available:
+
+ `:concat FORM...' -- evaluate each element of FORM and return
+ the concatenated results.
+
+ `:width WIDTH FORM' -- evaluate FORM and return the result
+ truncated to WIDTH characters. The result will be padded with
+ spaces to WIDTH characters, with padding at on the left if the
+ WIDTH is negative.
+
+ `:tag TAG FORM' -- if the thread has the tag TAG, insert the
+ result of evaluating FORM.
+
+ `:eval COMPLEX-FORM' -- evaluate COMPLEX-FORM as emacs lisp and
+ evaluate the result.
+
+During the evaluation of `notmuch-search-result-format', the
+symbol `notmuch-thread' is bound to the current thread (a
+property list), which can then be used in `:eval' forms."
+ :type 'list
:group 'notmuch-search)
;; The name of this variable `notmuch-init-file' is consistent with the
@@ -672,109 +752,84 @@ foreground and blue background."
;; Reverse the list so earlier entries take precedence
(reverse notmuch-search-line-faces)))
-(defun notmuch-search-author-propertize (authors)
- "Split `authors' into matching and non-matching authors and
-propertize appropriately. If no boundary between authors and
-non-authors is found, assume that all of the authors match."
- (if (string-match "\\(.*\\)|\\(.*\\)" authors)
- (concat (propertize (concat (match-string 1 authors) ",")
- 'face 'notmuch-search-matching-authors)
- (propertize (match-string 2 authors)
- 'face 'notmuch-search-non-matching-authors))
- (propertize authors 'face 'notmuch-search-matching-authors)))
-
-(defun notmuch-search-insert-authors (format-string authors)
- ;; Save the match data to avoid interfering with
- ;; `notmuch-search-process-filter'.
- (save-match-data
- (let* ((formatted-authors (format format-string authors))
- (formatted-sample (format format-string ""))
- (visible-string formatted-authors)
- (invisible-string "")
- (padding ""))
-
- ;; Truncate the author string to fit the specification.
- (if (> (length formatted-authors)
- (length formatted-sample))
- (let ((visible-length (- (length formatted-sample)
- (length "... "))))
- ;; Truncate the visible string according to the width of
- ;; the display string.
- (setq visible-string (substring formatted-authors 0 visible-length)
- invisible-string (substring formatted-authors visible-length))
- ;; If possible, truncate the visible string at a natural
- ;; break (comma or pipe), as incremental search doesn't
- ;; match across the visible/invisible border.
- (when (string-match "\\(.*\\)\\([,|] \\)\\([^,|]*\\)" visible-string)
- ;; Second clause is destructive on `visible-string', so
- ;; order is important.
- (setq invisible-string (concat (match-string 3 visible-string)
- invisible-string)
- visible-string (concat (match-string 1 visible-string)
- (match-string 2 visible-string))))
- ;; `visible-string' may be shorter than the space allowed
- ;; by `format-string'. If so we must insert some padding
- ;; after `invisible-string'.
- (setq padding (make-string (- (length formatted-sample)
- (length visible-string)
- (length "..."))
- ? ))))
-
- ;; Use different faces to show matching and non-matching authors.
- (if (string-match "\\(.*\\)|\\(.*\\)" visible-string)
- ;; The visible string contains both matching and
- ;; non-matching authors.
- (setq visible-string (notmuch-search-author-propertize visible-string)
- ;; The invisible string must contain only non-matching
- ;; authors, as the visible-string contains both.
- invisible-string (propertize invisible-string
- 'face 'notmuch-search-non-matching-authors))
- ;; The visible string contains only matching authors.
- (setq visible-string (propertize visible-string
- 'face 'notmuch-search-matching-authors)
- ;; The invisible string may contain both matching and
- ;; non-matching authors.
- invisible-string (notmuch-search-author-propertize invisible-string)))
-
- ;; If there is any invisible text, add it as a tooltip to the
- ;; visible text.
- (when (not (string= invisible-string ""))
- (setq visible-string (propertize visible-string 'help-echo (concat "..." invisible-string))))
-
- ;; Insert the visible and, if present, invisible author strings.
- (insert visible-string)
- (when (not (string= invisible-string ""))
- (let ((start (point))
- overlay)
- (insert invisible-string)
- (setq overlay (make-overlay start (point)))
- (overlay-put overlay 'invisible 'ellipsis)
- (overlay-put overlay 'isearch-open-invisible #'delete-overlay)))
- (insert padding))))
-
-(defun notmuch-search-insert-field (field format-string result)
+(defun notmuch-search-format-authors (matched non-matched)
+ (if (string-equal "" non-matched)
+ (propertize matched 'face 'notmuch-search-matching-authors)
+ (concat (propertize (concat matched ", ")
+ 'face 'notmuch-search-matching-authors)
+ (propertize non-matched
+ 'face 'notmuch-search-non-matching-authors))))
+
+(defun notmuch-search-make-width (width string)
+ (let ((neg (< 0 width))
+ (width (abs width))
+ (orig-len (length string)))
+
+ (if (< width orig-len)
+ ;; A sub-set of the string will be visible.
+ (let* ((ellipsis "...")
+ (visible-len (- width (length ellipsis)))
+ (visible-string (substring string 0 visible-len))
+ (invisible-string (substring string visible-len)))
+
+ (concat (propertize (concat visible-string
+ ;; The ellipsis should share the
+ ;; face of the preceding
+ ;; character.
+ (propertize ellipsis 'face
+ (get-text-property visible-len 'face string)))
+ 'help-echo (concat ellipsis invisible-string))))
+
+ ;; All of the string is visible - pad it.
+ (concat (if neg string)
+ (make-string (- width orig-len) ? )
+ (if neg "" string)))))
+
+(defun notmuch-search-elem-repr (elem thread)
(cond
- ((string-equal field "date")
- (insert (propertize (format format-string (plist-get result :date_relative))
- 'face 'notmuch-search-date)))
- ((string-equal field "count")
- (insert (propertize (format format-string
- (format "[%s/%s]" (plist-get result :matched)
- (plist-get result :total)))
- 'face 'notmuch-search-count)))
- ((string-equal field "subject")
- (insert (propertize (format format-string
- (notmuch-sanitize (plist-get result :subject)))
- 'face 'notmuch-search-subject)))
-
- ((string-equal field "authors")
- (notmuch-search-insert-authors
- format-string (notmuch-sanitize (plist-get result :authors))))
-
- ((string-equal field "tags")
- (let ((tags (plist-get result :tags))
- (orig-tags (plist-get result :orig-tags)))
- (insert (format format-string (notmuch-tag-format-tags tags orig-tags)))))))
+ ((not elem)
+ "")
+
+ ((numberp elem)
+ (format "%d" elem))
+
+ ((stringp elem)
+ elem)
+
+ ((functionp elem)
+ (funcall elem thread))
+
+ ((listp elem)
+ (let ((op (car elem))
+ (rest (cdr elem)))
+ (case op
+ (:concat
+ (mapconcat (lambda (inner-elem)
+ (notmuch-search-elem-repr inner-elem thread))
+ rest ""))
+
+ (:width
+ (notmuch-search-make-width (car rest)
+ (notmuch-search-elem-repr (cadr rest) thread)))
+
+ (:tag
+ (when (member (car rest) (plist-get thread :tags))
+ (notmuch-search-elem-repr (cadr rest) thread)))
+
+ (:eval
+ (notmuch-search-elem-repr (apply #'eval rest) thread))
+
+ (otherwise
+ (mapconcat
+ (lambda (inner-elem) (notmuch-search-elem-repr inner-elem thread))
+ elem "")))))
+
+ (t
+ (let ((val (plist-get thread elem)))
+ (if val
+ (notmuch-search-elem-repr val thread)
+ (message "Unknown message attribute in `notmuch-search-elem-repr': %s" elem)
+ "")))))
(defun notmuch-search-show-result (result pos)
"Insert RESULT at POS."
@@ -782,9 +837,10 @@ non-authors is found, assume that all of the authors match."
(unless (= (plist-get result :matched) 0)
(save-excursion
(goto-char pos)
- (dolist (spec notmuch-search-result-format)
- (notmuch-search-insert-field (car spec) (cdr spec) result))
- (insert "\n")
+ ;; `notmuch-thread' is a well known symbol for functions to
+ ;; use during evaluation of `notmuch-search-result-format'.
+ (insert (let ((notmuch-thread result))
+ (notmuch-search-elem-repr notmuch-search-result-format notmuch-thread)))
(notmuch-search-color-line pos (point) (plist-get result :tags))
(put-text-property pos (point) 'notmuch-search-result result))))
--
2.1.3
More information about the notmuch
mailing list