X-Git-Url: https://git.notmuchmail.org/git?a=blobdiff_plain;f=emacs%2Fnotmuch-tag.el;h=a4dea39df71a732154770a008977bd24417ac226;hb=b9a777e0a3983a5b2062e927c5b426ca669da644;hp=f705ac5dff844c65dc853a491014f3e663f58d15;hpb=9f9a63c863e2eac1adebef16a103d7bc0ccdc291;p=notmuch diff --git a/emacs/notmuch-tag.el b/emacs/notmuch-tag.el index f705ac5d..a4dea39d 100644 --- a/emacs/notmuch-tag.el +++ b/emacs/notmuch-tag.el @@ -28,35 +28,9 @@ (require 'crm) (require 'notmuch-lib) -(defcustom notmuch-tag-formats - '(("unread" (propertize tag 'face '(:foreground "red"))) - ("flagged" (propertize tag 'face '(:foreground "blue")) - (notmuch-tag-format-image-data tag (notmuch-tag-star-icon)))) - "Custom formats for individual tags. - -This gives a list that maps from tag names to lists of formatting -expressions. The car of each element gives a tag name and the -cdr gives a list of Elisp expressions that modify the tag. If -the list is empty, the tag will simply be hidden. Otherwise, -each expression will be evaluated in order: for the first -expression, the variable `tag' will be bound to the tag name; for -each later expression, the variable `tag' will be bound to the -result of the previous expression. In this way, each expression -can build on the formatting performed by the previous expression. -The result of the last expression will displayed in place of the -tag. - -For example, to replace a tag with another string, simply use -that string as a formatting expression. To change the foreground -of a tag to red, use the expression - (propertize tag 'face '(:foreground \"red\")) - -See also `notmuch-tag-format-image', which can help replace tags -with images." - - :group 'notmuch-search - :group 'notmuch-show - :type '(alist :key-type (string :tag "Tag") +(define-widget 'notmuch-tag-format-type 'lazy + "Customize widget for notmuch-tag-format and friends" + :type '(alist :key-type (regexp :tag "Tag") :extra-offset -3 :value-type (radio :format "%v" @@ -82,6 +56,39 @@ with images." (string :tag "Custom"))) (sexp :tag "Custom"))))) +(defcustom notmuch-tag-formats + '(("unread" (propertize tag 'face '(:foreground "red"))) + ("flagged" (propertize tag 'face '(:foreground "blue")) + (notmuch-tag-format-image-data tag (notmuch-tag-star-icon)))) + "Custom formats for individual tags. + +This is an association list that maps from tag name regexps to +lists of formatting expressions. The first entry whose car +regexp-matches a tag will be used to format that tag. The regexp +is implicitly anchored, so to match a literal tag name, just use +that tag name (if it contains special regexp characters like +\".\" or \"*\", these have to be escaped). The cdr of the +matching entry gives a list of Elisp expressions that modify the +tag. If the list is empty, the tag will simply be hidden. +Otherwise, each expression will be evaluated in order: for the +first expression, the variable `tag' will be bound to the tag +name; for each later expression, the variable `tag' will be bound +to the result of the previous expression. In this way, each +expression can build on the formatting performed by the previous +expression. The result of the last expression will displayed in +place of the tag. + +For example, to replace a tag with another string, simply use +that string as a formatting expression. To change the foreground +of a tag to red, use the expression + (propertize tag 'face '(:foreground \"red\")) + +See also `notmuch-tag-format-image', which can help replace tags +with images." + :group 'notmuch-search + :group 'notmuch-show + :type 'notmuch-tag-format-type) + (defun notmuch-tag-format-image-data (tag data) "Replace TAG with image DATA, if available. @@ -135,28 +142,55 @@ This can be used with `notmuch-tag-format-image-data'." ") +(defvar notmuch-tag--format-cache (make-hash-table :test 'equal) + "Cache of tag format lookup. Internal to `notmuch-tag-format-tag'.") + +(defun notmuch-tag-clear-cache () + "Clear the internal cache of tag formats." + (clrhash notmuch-tag--format-cache)) + (defun notmuch-tag-format-tag (tag) - "Format TAG by looking into `notmuch-tag-formats'." - (let ((formats (assoc tag notmuch-tag-formats))) - (cond - ((null formats) ;; - Tag not in `notmuch-tag-formats', - tag) ;; the format is the tag itself. - ((null (cdr formats)) ;; - Tag was deliberately hidden, - nil) ;; no format must be returned - (t ;; - Tag was found and has formats, - (let ((tag tag)) ;; we must apply all the formats. - (dolist (format (cdr formats) tag) - (setq tag (eval format)))))))) - -(defun notmuch-tag-format-tags (tags) + "Format TAG by according to `notmuch-tag-formats'. + +Callers must ensure that the tag format cache has been recently cleared +via `notmuch-tag-clear-cache' before using this function. For example, +it would be appropriate to clear the cache just prior to filling a +buffer that uses formatted tags." + + (let ((formatted (gethash tag notmuch-tag--format-cache 'missing))) + (when (eq formatted 'missing) + (let* ((formats + (save-match-data + ;; Don't use assoc-default since there's no way to + ;; distinguish a missing key from a present key with a + ;; null cdr:. + (assoc* tag notmuch-tag-formats + :test (lambda (tag key) + (and (eq (string-match key tag) 0) + (= (match-end 0) (length tag)))))))) + (setq formatted + (cond + ((null formats) ;; - Tag not in `notmuch-tag-formats', + tag) ;; the format is the tag itself. + ((null (cdr formats)) ;; - Tag was deliberately hidden, + nil) ;; no format must be returned + (t ;; - Tag was found and has formats, + (let ((tag tag)) ;; we must apply all the formats. + (dolist (format (cdr formats) tag) + (setq tag (eval format))))))) + (puthash tag formatted notmuch-tag--format-cache))) + formatted)) + +(defun notmuch-tag-format-tags (tags &optional face) "Return a string representing formatted TAGS." - (notmuch-combine-face-text-property-string - (mapconcat #'identity - ;; nil indicated that the tag was deliberately hidden - (delq nil (mapcar #'notmuch-tag-format-tag tags)) - " ") - 'notmuch-tag-face - t)) + (let ((face (or face 'notmuch-tag-face))) + (notmuch-apply-face + (mapconcat #'identity + ;; nil indicated that the tag was deliberately hidden + (delq nil (mapcar #'notmuch-tag-format-tag tags)) + " ") + face + t))) (defcustom notmuch-before-tag-hook nil "Hooks that are run before tags of a message are modified. @@ -261,6 +295,12 @@ from TAGS if present." (error "Changed tag must be of the form `+this_tag' or `-that_tag'"))))) (sort result-tags 'string<))) +(defconst notmuch-tag-argument-limit 1000 + "Use batch tagging if the tagging query is longer than this. + +This limits the length of arguments passed to the notmuch CLI to +avoid system argument length limits and performance problems.") + (defun notmuch-tag (query tag-changes) "Add/remove tags in TAG-CHANGES to messages matching QUERY. @@ -279,8 +319,13 @@ notmuch-after-tag-hook will be run." tag-changes) (unless (null tag-changes) (run-hooks 'notmuch-before-tag-hook) - (apply 'notmuch-call-notmuch-process "tag" - (append tag-changes (list "--" query))) + (if (<= (length query) notmuch-tag-argument-limit) + (apply 'notmuch-call-notmuch-process "tag" + (append tag-changes (list "--" query))) + ;; Use batch tag mode to avoid argument length limitations + (let ((batch-op (concat (mapconcat #'notmuch-hex-encode tag-changes " ") + " -- " query))) + (notmuch-call-notmuch-process :stdin-string batch-op "tag" "--batch"))) (run-hooks 'notmuch-after-tag-hook))) (defun notmuch-tag-change-list (tags &optional reverse)