X-Git-Url: https://git.notmuchmail.org/git?p=notmuch;a=blobdiff_plain;f=emacs%2Fnotmuch-tag.el;h=75a438bfc5de5895183d6fee3208672dee66f332;hp=c25cff84ff75fa05a9e6af4971bec4966f74a12f;hb=b714a808a692a99c1b936be43186a40ab251aeca;hpb=eb8feb16664fd0296ea0e07f4924c2a87a5b3bc3 diff --git a/emacs/notmuch-tag.el b/emacs/notmuch-tag.el index c25cff84..75a438bf 100644 --- a/emacs/notmuch-tag.el +++ b/emacs/notmuch-tag.el @@ -1,5 +1,6 @@ ;; notmuch-tag.el --- tag messages within emacs ;; +;; Copyright © Damien Cassou ;; Copyright © Carl Worth ;; ;; This file is part of Notmuch. @@ -18,11 +19,144 @@ ;; along with Notmuch. If not, see . ;; ;; Authors: Carl Worth +;; Damien Cassou +;; +;;; Code: +;; -(eval-when-compile (require 'cl)) +(require 'cl) (require 'crm) (require 'notmuch-lib) +(defcustom notmuch-tag-formats + '(("unread" (propertize tag 'face '(:foreground "red"))) + ("flagged" (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") + :extra-offset -3 + :value-type + (radio :format "%v" + (const :tag "Hidden" nil) + (set :tag "Modified" + (string :tag "Display as") + (list :tag "Face" :extra-offset -4 + (const :format "" :inline t + (propertize tag 'face)) + (list :format "%v" + (const :format "" quote) + custom-face-edit)) + (list :format "%v" :extra-offset -4 + (const :format "" :inline t + (notmuch-tag-format-image-data tag)) + (choice :tag "Image" + (const :tag "Star" + (notmuch-tag-star-icon)) + (const :tag "Empty star" + (notmuch-tag-star-empty-icon)) + (const :tag "Tag" + (notmuch-tag-tag-icon)) + (string :tag "Custom"))) + (sexp :tag "Custom"))))) + +(defun notmuch-tag-format-image-data (tag data) + "Replace TAG with image DATA, if available. + +This function returns a propertized string that will display image +DATA in place of TAG.This is designed for use in +`notmuch-tag-formats'. + +DATA is the content of an SVG picture (e.g., as returned by +`notmuch-tag-star-icon')." + (propertize tag 'display + `(image :type svg + :data ,data + :ascent center + :mask heuristic))) + +(defun notmuch-tag-star-icon () + "Return SVG data representing a star icon. +This can be used with `notmuch-tag-format-image-data'." +" + + + + +") + +(defun notmuch-tag-star-empty-icon () + "Return SVG data representing an empty star icon. +This can be used with `notmuch-tag-format-image-data'." + " + + + + +") + +(defun notmuch-tag-tag-icon () + "Return SVG data representing a tag icon. +This can be used with `notmuch-tag-format-image-data'." + " + + + + +") + +(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) + "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)) + (defcustom notmuch-before-tag-hook nil "Hooks that are run before tags of a message are modified. @@ -108,18 +242,26 @@ from TAGS if present." (error "Changed tag must be of the form `+this_tag' or `-that_tag'"))))) (sort result-tags 'string<))) -(defun notmuch-tag (query &rest tag-changes) +(defun notmuch-tag (query &optional tag-changes) "Add/remove tags in TAG-CHANGES to messages matching QUERY. -TAG-CHANGES should be a list of strings of the form \"+tag\" or -\"-tag\" and QUERY should be a string containing the -search-query. +QUERY should be a string containing the search-terms. +TAG-CHANGES can take multiple forms. If TAG-CHANGES is a list of +strings of the form \"+tag\" or \"-tag\" then those are the tag +changes applied. If TAG-CHANGES is a string then it is +interpreted as a single tag change. If TAG-CHANGES is the string +\"-\" or \"+\", or null, then the user is prompted to enter the +tag changes. Note: Other code should always use this function alter tags of messages instead of running (notmuch-call-notmuch-process \"tag\" ..) directly, so that hooks specified in notmuch-before-tag-hook and notmuch-after-tag-hook will be run." ;; Perform some validation + (if (string-or-null-p tag-changes) + (if (or (string= tag-changes "-") (string= tag-changes "+") (null tag-changes)) + (setq tag-changes (notmuch-read-tag-changes tag-changes query)) + (setq tag-changes (list tag-changes)))) (mapc (lambda (tag-change) (unless (string-match-p "^[-+]\\S-+$" tag-change) (error "Tag must be of the form `+this_tag' or `-that_tag'"))) @@ -128,7 +270,24 @@ notmuch-after-tag-hook will be run." (run-hooks 'notmuch-before-tag-hook) (apply 'notmuch-call-notmuch-process "tag" (append tag-changes (list "--" query))) - (run-hooks 'notmuch-after-tag-hook))) + (run-hooks 'notmuch-after-tag-hook)) + ;; in all cases we return tag-changes as a list + tag-changes) + +(defun notmuch-tag-change-list (tags &optional reverse) + "Convert TAGS into a list of tag changes. + +Add a \"+\" prefix to any tag in TAGS list that doesn't already +begin with a \"+\" or a \"-\". If REVERSE is non-nil, replace all +\"+\" prefixes with \"-\" and vice versa in the result." + (mapcar (lambda (str) + (let ((s (if (string-match "^[+-]" str) str (concat "+" str)))) + (if reverse + (concat (if (= (string-to-char s) ?-) "+" "-") + (substring s 1)) + s))) + tags)) + ;;