* [PATCH 0/2] emacs: User-defined sections in notmuch-hello @ 2011-06-29 20:26 Daniel Schoepe 2011-06-29 20:27 ` [PATCH 1/2] " Daniel Schoepe ` (4 more replies) 0 siblings, 5 replies; 20+ messages in thread From: Daniel Schoepe @ 2011-06-29 20:26 UTC (permalink / raw) To: notmuch Unfortunately the customize-interface for more customized entries in notmuch-hello-sections looks a bit weird, but I couldn't figure out how to get rid of the empty lines produced by generating the lambda expression needed. To avoid unnecessary complexity in the code this patch removes aligning all the tag entries in different sections (e.g. saved searches and all tags) the same way. So if someone really thinks this was an important features, please yell loudly. Cheers, Daniel ^ permalink raw reply [flat|nested] 20+ messages in thread
* [PATCH 1/2] emacs: User-defined sections in notmuch-hello 2011-06-29 20:26 [PATCH 0/2] emacs: User-defined sections in notmuch-hello Daniel Schoepe @ 2011-06-29 20:27 ` Daniel Schoepe 2011-06-29 20:27 ` [PATCH 2/2] emacs: Tests for user-defined sections Daniel Schoepe ` (3 subsequent siblings) 4 siblings, 0 replies; 20+ messages in thread From: Daniel Schoepe @ 2011-06-29 20:27 UTC (permalink / raw) To: notmuch This patch makes the notmuch-hello screen fully customizable by allowing the user to add and remove arbitrary sections. It also provides some convenience functions for constructing sections, e.g. showing the unread message count for each tag. This is done by specifying a list of functions that will be run when notmuch-hello is invoked. --- emacs/notmuch-hello.el | 553 +++++++++++++++++++++++++++++------------------- 1 files changed, 339 insertions(+), 214 deletions(-) diff --git a/emacs/notmuch-hello.el b/emacs/notmuch-hello.el index 65fde75..e4c9307 100644 --- a/emacs/notmuch-hello.el +++ b/emacs/notmuch-hello.el @@ -55,26 +55,6 @@ :type 'boolean :group 'notmuch) -(defcustom notmuch-hello-tag-list-make-query nil - "Function or string to generate queries for the all tags list. - -This variable controls which query results are shown for each tag -in the \"all tags\" list. If nil, it will use all messages with -that tag. If this is set to a string, it is used as a filter for -messages having that tag (equivalent to \"tag:TAG and (THIS-VARIABLE)\"). -Finally this can be a function that will be called for each tag and -should return a filter for that tag, or nil to hide the tag." - :type '(choice (const :tag "All messages" nil) - (const :tag "Unread messages" "tag:unread") - (const :tag "Custom filter" string) - (const :tag "Custom filter function" function)) - :group 'notmuch) - -(defcustom notmuch-hello-hide-tags nil - "List of tags to be hidden in the \"all tags\"-section." - :type '(repeat string) - :group 'notmuch) - (defface notmuch-hello-logo-background '((((class color) (background dark)) @@ -123,6 +103,58 @@ Typically \",\" in the US and UK and \".\" in Europe." (defvar notmuch-hello-recent-searches nil) +(define-widget 'notmuch-hello-customized-section 'lazy + "Customize-type for notmuch-hello sections." + :tag "Customized section" + :type + (let ((opts + '((:title (string :tag "Title for this section")) + (:make-query (string :tag "Filter for each tag")) + (:make-count (string :tag "Different query to generate counts")) + (:hide-tags (repeat :tag "Tags that will be hidden" string)) + (:initially-hidden (boolean :tag "Hide this on startup?")) + (:hide-empty-tags (boolean :tag "Hide tags with no matching messages")) + (:hide-if-empty (boolean :tag "Hide if empty"))))) + `(list :tag "" (const :tag "" lambda) (const :tag "" nil) + (list :tag "Options" + (const :tag "" apply) + (const :tag "" (quote notmuch-hello-insert-all-tags)) + (list :tag "" (const :tag "" quote) + (plist :options ,opts)))))) + +(defcustom notmuch-hello-sections + (list #'notmuch-hello-insert-header + #'notmuch-hello-insert-saved-searches + #'notmuch-hello-insert-search + #'notmuch-hello-insert-recent-searches + #'notmuch-hello-insert-alltags + #'notmuch-hello-insert-footer) + "Sections for notmuch-hello. + +Each entry of this list should be a function of no arguments +that should return if notmuch-hello-target is produced as part +of its output and nil otherwise. The functions will be run +to construct the content of the notmuch-hello buffer +in the order they appear in this list." + :group 'notmuch + :type + '(repeat + (choice (function-item notmuch-hello-insert-header) + (function-item notmuch-hello-insert-saved-searches) + (function-item notmuch-hello-insert-search) + (function-item notmuch-hello-insert-recent-searches) + (function-item notmuch-hello-insert-alltags) + (function-item notmuch-hello-insert-footer) + notmuch-hello-customized-section))) + +;; only defined to avoid compilation warnings about free variables +(defvar notmuch-hello-target nil) + +(defvar notmuch-hello-hidden-sections nil + "List of query sections whose contents are hidden") + +(defvar notmuch-hello-first-run t) + (defun notmuch-hello-remember-search (search) (if (not (member search notmuch-hello-recent-searches)) (push search notmuch-hello-recent-searches)) @@ -238,12 +270,40 @@ should be. Returns a cons cell `(tags-per-line width)'." (* tags-per-line (+ 9 1)))) tags-per-line)))) -(defun notmuch-hello-insert-tags (tag-alist widest target) - (let* ((tags-and-width (notmuch-hello-tags-per-line widest)) +(defun notmuch-hello-query-entries (tag-alist &optional hide-empty) + "Compute list of counts and queries for TAG-ALIST. + +If HIDE-EMPTY is non-nil, entries with no matching messages will be +removed from the result." + (notmuch-remove-if-not + #'identity + (mapcar + (lambda (elem) + (let* ((name (car elem)) + (query-and-count (if (consp (cdr elem)) + ;; do we have a different query for the message count? + (cons (second elem) (third elem)) + (cons (cdr elem) (cdr elem)))) + (message-count + (string-to-number (notmuch-saved-search-count + (cdr query-and-count))))) + (and (or (not hide-empty) (> message-count 0)) + (list name (car query-and-count) message-count)))) + tag-alist))) + +(defun notmuch-hello-insert-tags (entries) + "Insert query items from ENTRIES. + +ENTRIES must be a list containing lists of the form (NAME QUERY COUNT), where +QUERY is the query to start when the button for the corresponding entry is +activated. COUNT should be the number of messages matching the query. +Such a list can be computed with `notmuch-hello-query-entries'." + (let* ((widest (notmuch-hello-longest-label entries)) + (tags-and-width (notmuch-hello-tags-per-line widest)) (tags-per-line (car tags-and-width)) (widest (cdr tags-and-width)) (count 0) - (reordered-list (notmuch-hello-reflect tag-alist tags-per-line)) + (reordered-list (notmuch-hello-reflect entries tags-per-line)) ;; Hack the display of the buttons used. (widget-push-button-prefix "") (widget-push-button-suffix "") @@ -253,13 +313,13 @@ should be. Returns a cons cell `(tags-per-line width)'." (mapc (lambda (elem) ;; (not elem) indicates an empty slot in the matrix. (when elem - (let* ((name (car elem)) - (query (cdr elem)) + (let* ((name (first elem)) + (query (second elem)) + (count (third elem)) (formatted-name (format "%s " name))) (widget-insert (format "%8s " - (notmuch-hello-nice-number - (string-to-number (notmuch-saved-search-count query))))) - (if (string= formatted-name target) + (notmuch-hello-nice-number count))) + (if (string= formatted-name notmuch-hello-target) (setq found-target-pos (point-marker))) (widget-create 'push-button :notify #'notmuch-hello-widget-search @@ -277,7 +337,7 @@ should be. Returns a cons cell `(tags-per-line width)'." (setq count (1+ count)) (if (eq (% count tags-per-line) 0) (widget-insert "\n"))) - reordered-list) + entries) ;; If the last line was not full (and hence did not include a ;; carriage return), insert one now. @@ -325,59 +385,266 @@ should be. Returns a cons cell `(tags-per-line width)'." (fset 'notmuch-hello-mode-map notmuch-hello-mode-map) (defun notmuch-hello-mode () - "Major mode for convenient notmuch navigation. This is your entry portal into notmuch. + "Major mode for convenient notmuch navigation. This is your entry portal into notmuch. Complete list of currently available key bindings: \\{notmuch-hello-mode-map}" - (interactive) - (kill-all-local-variables) - (use-local-map notmuch-hello-mode-map) - (setq major-mode 'notmuch-hello-mode - mode-name "notmuch-hello") - ;;(setq buffer-read-only t) -) - -(defun notmuch-hello-generate-tag-alist () + (interactive) + (kill-all-local-variables) + (use-local-map notmuch-hello-mode-map) + (setq major-mode 'notmuch-hello-mode + mode-name "notmuch-hello") + ;;(setq buffer-read-only t) + ) + +(defun notmuch-hello-make-query (tag make-query) + (cond + ((functionp make-query) + (let ((result (funcall make-query tag))) + (and result (concat "tag:" tag " and (" result ")")))) + ((stringp make-query) + (concat "tag:" tag " and (" make-query ")")) + (t (concat "tag:" tag)))) + +(defun notmuch-hello-generate-tag-alist (&optional hide-tags make-query make-count) "Return an alist from tags to queries to display in the all-tags section." (notmuch-remove-if-not - #'cdr + #'identity (mapcar (lambda (tag) - (cons tag - (cond - ((functionp notmuch-hello-tag-list-make-query) - (concat "tag:" tag " and (" - (funcall notmuch-hello-tag-list-make-query tag) ")")) - ((stringp notmuch-hello-tag-list-make-query) - (concat "tag:" tag " and (" - notmuch-hello-tag-list-make-query ")")) - (t (concat "tag:" tag))))) + (let ((query (notmuch-hello-make-query tag make-query))) + (when query + (if make-count + (list tag (notmuch-hello-make-query tag make-query) + (notmuch-hello-make-query tag make-count)) + (cons tag (notmuch-hello-make-query tag make-query)))))) (notmuch-remove-if-not (lambda (tag) - (not (member tag notmuch-hello-hide-tags))) + (not (member tag hide-tags))) (process-lines notmuch-command "search-tags"))))) +(defun notmuch-hello-insert-header () + "Insert the default notmuch-hello header." + (when notmuch-show-logo + (let ((image notmuch-hello-logo)) + ;; The notmuch logo uses transparency. That can display poorly + ;; when inserting the image into an emacs buffer (black logo on + ;; a black background), so force the background colour of the + ;; image. We use a face to represent the colour so that + ;; `defface' can be used to declare the different possible + ;; colours, which depend on whether the frame has a light or + ;; dark background. + (setq image (cons 'image + (append (cdr image) + (list :background (face-background 'notmuch-hello-logo-background))))) + (insert-image image)) + (widget-insert " ")) + + (widget-insert "Welcome to ") + ;; Hack the display of the links used. + (let ((widget-link-prefix "") + (widget-link-suffix "")) + (widget-create 'link + :notify (lambda (&rest ignore) + (browse-url notmuch-hello-url)) + :help-echo "Visit the notmuch website." + "notmuch") + (widget-insert ". ") + (widget-insert "You have ") + (widget-create 'link + :notify (lambda (&rest ignore) + (notmuch-hello-update)) + :help-echo "Refresh" + (notmuch-hello-nice-number + (string-to-number (car (process-lines notmuch-command "count"))))) + (widget-insert " messages."))) + + +(defun notmuch-hello-insert-saved-searches () + "Insert the saved-searches section." + (let ((entries (notmuch-hello-query-entries + notmuch-saved-searches + (not notmuch-show-empty-saved-searches))) + found-target-pos final-target-pos) + (when entries + (widget-insert "\nSaved searches: ") + (widget-create 'push-button + :notify (lambda (&rest ignore) + (customize-variable 'notmuch-saved-searches)) + "edit") + (widget-insert "\n\n") + (setq final-target-pos (point-marker)) + (let ((start (point))) + (setq found-target-pos + (notmuch-hello-insert-tags entries)) + (if found-target-pos + (setq final-target-pos found-target-pos)) + (indent-rigidly start (point) notmuch-hello-indent) + final-target-pos)))) + +(defun notmuch-hello-insert-search () + "Insert a search widget." + (widget-insert "Search: ") + (setq notmuch-hello-search-bar-marker (point-marker)) + (widget-create 'editable-field + ;; Leave some space at the start and end of the + ;; search boxes. + :size (max 8 (- (window-width) notmuch-hello-indent + (length "Search: "))) + :action (lambda (widget &rest ignore) + (notmuch-hello-search (widget-value widget))))) + +(defun notmuch-hello-insert-recent-searches () + "Insert recent searches." + (when notmuch-hello-recent-searches + (widget-insert "\nRecent searches: ") + (widget-create 'push-button + :notify (lambda (&rest ignore) + (setq notmuch-hello-recent-searches nil) + (notmuch-hello-update)) + "clear") + (widget-insert "\n\n") + (let ((start (point)) + (nth 0)) + (mapc '(lambda (search) + (let ((widget-symbol (intern (format "notmuch-hello-search-%d" nth)))) + (set widget-symbol + (widget-create 'editable-field + ;; Don't let the search boxes be + ;; less than 8 characters wide. + :size (max 8 + (- (window-width) + ;; Leave some space + ;; at the start and + ;; end of the + ;; boxes. + (* 2 notmuch-hello-indent) + ;; 1 for the space + ;; before the + ;; `[save]' button. 6 + ;; for the `[save]' + ;; button. + 1 6)) + :action (lambda (widget &rest ignore) + (notmuch-hello-search (widget-value widget))) + search)) + (widget-insert " ") + (widget-create 'push-button + :notify (lambda (widget &rest ignore) + (notmuch-hello-add-saved-search widget)) + :notmuch-saved-search-widget widget-symbol + "save")) + (widget-insert "\n") + (setq nth (1+ nth))) + notmuch-hello-recent-searches) + (indent-rigidly start (point) notmuch-hello-indent)))) + +(defun notmuch-hello-insert-query-list (title query-alist &rest options) + "Insert a section with TITLE showing tags from QUERY-ALIST. + +Supports the following entries in OPTIONS as a plist: +:initially-hidden - if non-nil, section will be hidden on startup +:hide-empty-tags - hide entries with no matching messages +:hide-if-empty - hide if no entries would be shown + (only makes sense with :hide-empty-tags) + +QUERY-ALIST must be a list containing elements of the form (NAME . QUERY) +or (NAME QUERY COUNT-QUERY). If the latter form is used, +COUNT-QUERY specifies an alternate query to be used to generate +the count for the associated tag." + (widget-insert title) + (if (and notmuch-hello-first-run (plist-get options :initially-hidden)) + (add-to-list 'notmuch-hello-hidden-sections title)) + (let ((is-hidden (member title notmuch-hello-hidden-sections)) + (start (point))) + (if is-hidden + (widget-create 'push-button + :notify `(lambda (widget &rest ignore) + (setq notmuch-hello-hidden-sections + (delete ,title notmuch-hello-hidden-sections)) + (notmuch-hello-update)) + "show") + (widget-create 'push-button + :notify `(lambda (widget &rest ignore) + (add-to-list 'notmuch-hello-hidden-sections + ,title) + (notmuch-hello-update)) + "hide")) + (widget-insert "\n") + (let (target-pos + (entries (notmuch-hello-query-entries + query-alist (plist-get options :hide-empty-tags)))) + (when (and (not is-hidden) + (or (not (plist-get options :hide-if-empty)) + entries)) + (widget-insert "\n") + (setq target-pos + (notmuch-hello-insert-tags entries)) + (indent-rigidly start (point) notmuch-hello-indent) + target-pos)))) + +(defun notmuch-hello-insert-tags-section (&rest options) + "Insert a section displaying all tags and message counts for each. + +Allowed options are those accepted by `notmuch-hello-insert-query-list' and the +following: + +:title - Title for this section, defaults to \"All tags: \" +:make-query - This can be a function that takes a tag as its argument and + returns a filter to be used for the query for that tag or nil to hide the + tag. This can also be a string that is used as a filter for messages with that tag. +:make-count - Seperate query to generate the count that should be displayed for each + tag. Accepts the same values as :make-query +:hide-tags - List of tags that should be excluded." + (apply 'notmuch-hello-insert-query-list + (or (plist-get options :title) "All tags: ") + (notmuch-hello-generate-tag-alist + (plist-get options :hide-tags) + (plist-get options :make-query) + (plist-get options :make-count)) + options)) + +(defun notmuch-hello-insert-alltags () + "Insert a section displaying all tags and associated message counts" + (notmuch-hello-insert-tags-section :initially-hidden + (not notmuch-show-all-tags-list))) + +(defun notmuch-hello-insert-footer () + "Insert the notmuch-hello footer." + (let ((start (point))) + (widget-insert "Type a search query and hit RET to view matching threads.\n") + (when notmuch-hello-recent-searches + (widget-insert "Hit RET to re-submit a previous search. Edit it first if you like.\n") + (widget-insert "Save recent searches with the `save' button.\n")) + (when notmuch-saved-searches + (widget-insert "Edit saved searches with the `edit' button.\n")) + (widget-insert "Hit RET or click on a saved search or tag name to view matching threads.\n") + (widget-insert "`=' refreshes this screen. `s' jumps to the search box. `q' to quit.\n") + (let ((fill-column (- (window-width) notmuch-hello-indent))) + (center-region start (point))))) + + ;;;###autoload (defun notmuch-hello (&optional no-display) "Run notmuch and display saved searches, known tags, etc." (interactive) - ; Jump through a hoop to get this value from the deprecated variable - ; name (`notmuch-folders') or from the default value. + ; Jump through a hoop to get this value from the deprecated variable + ; name (`notmuch-folders') or from the default value. (if (not notmuch-saved-searches) - (setq notmuch-saved-searches (notmuch-saved-searches))) + (setq notmuch-saved-searches (notmuch-saved-searches))) (if no-display (set-buffer "*notmuch-hello*") (switch-to-buffer "*notmuch-hello*")) - (let ((target (if (widget-at) - (widget-value (widget-at)) - (condition-case nil - (progn - (widget-forward 1) - (widget-value (widget-at))) - (error nil))))) + (let ((notmuch-hello-target (if (widget-at) + (widget-value (widget-at)) + (condition-case nil + (progn + (widget-forward 1) + (widget-value (widget-at))) + (error nil))))) (kill-all-local-variables) (let ((inhibit-read-only t)) @@ -391,167 +658,25 @@ Complete list of currently available key bindings: (mapc 'delete-overlay (car all)) (mapc 'delete-overlay (cdr all))) - (when notmuch-show-logo - (let ((image notmuch-hello-logo)) - ;; The notmuch logo uses transparency. That can display poorly - ;; when inserting the image into an emacs buffer (black logo on - ;; a black background), so force the background colour of the - ;; image. We use a face to represent the colour so that - ;; `defface' can be used to declare the different possible - ;; colours, which depend on whether the frame has a light or - ;; dark background. - (setq image (cons 'image - (append (cdr image) - (list :background (face-background 'notmuch-hello-logo-background))))) - (insert-image image)) - (widget-insert " ")) - - (widget-insert "Welcome to ") - ;; Hack the display of the links used. - (let ((widget-link-prefix "") - (widget-link-suffix "")) - (widget-create 'link - :notify (lambda (&rest ignore) - (browse-url notmuch-hello-url)) - :help-echo "Visit the notmuch website." - "notmuch") - (widget-insert ". ") - (widget-insert "You have ") - (widget-create 'link - :notify (lambda (&rest ignore) - (notmuch-hello-update)) - :help-echo "Refresh" - (notmuch-hello-nice-number - (string-to-number (car (process-lines notmuch-command "count"))))) - (widget-insert " messages.\n")) - - (let ((found-target-pos nil) - (final-target-pos nil)) - (let* ((saved-alist - ;; Filter out empty saved searches if required. - (if notmuch-show-empty-saved-searches - notmuch-saved-searches - (loop for elem in notmuch-saved-searches - if (> (string-to-number (notmuch-saved-search-count (cdr elem))) 0) - collect elem))) - (saved-widest (notmuch-hello-longest-label saved-alist)) - (alltags-alist (if notmuch-show-all-tags-list (notmuch-hello-generate-tag-alist))) - (alltags-widest (notmuch-hello-longest-label alltags-alist)) - (widest (max saved-widest alltags-widest))) - - (when saved-alist - (widget-insert "\nSaved searches: ") - (widget-create 'push-button - :notify (lambda (&rest ignore) - (customize-variable 'notmuch-saved-searches)) - "edit") - (widget-insert "\n\n") - (setq final-target-pos (point-marker)) - (let ((start (point))) - (setq found-target-pos (notmuch-hello-insert-tags saved-alist widest target)) - (if found-target-pos - (setq final-target-pos found-target-pos)) - (indent-rigidly start (point) notmuch-hello-indent))) - - (widget-insert "\nSearch: ") - (setq notmuch-hello-search-bar-marker (point-marker)) - (widget-create 'editable-field - ;; Leave some space at the start and end of the - ;; search boxes. - :size (max 8 (- (window-width) notmuch-hello-indent - (length "Search: "))) - :action (lambda (widget &rest ignore) - (notmuch-hello-search (widget-value widget)))) - (widget-insert "\n") - - (when notmuch-hello-recent-searches - (widget-insert "\nRecent searches: ") - (widget-create 'push-button - :notify (lambda (&rest ignore) - (setq notmuch-hello-recent-searches nil) - (notmuch-hello-update)) - "clear") - (widget-insert "\n\n") - (let ((start (point)) - (nth 0)) - (mapc '(lambda (search) - (let ((widget-symbol (intern (format "notmuch-hello-search-%d" nth)))) - (set widget-symbol - (widget-create 'editable-field - ;; Don't let the search boxes be - ;; less than 8 characters wide. - :size (max 8 - (- (window-width) - ;; Leave some space - ;; at the start and - ;; end of the - ;; boxes. - (* 2 notmuch-hello-indent) - ;; 1 for the space - ;; before the - ;; `[save]' button. 6 - ;; for the `[save]' - ;; button. - 1 6)) - :action (lambda (widget &rest ignore) - (notmuch-hello-search (widget-value widget))) - search)) - (widget-insert " ") - (widget-create 'push-button - :notify (lambda (widget &rest ignore) - (notmuch-hello-add-saved-search widget)) - :notmuch-saved-search-widget widget-symbol - "save")) - (widget-insert "\n") - (setq nth (1+ nth))) - notmuch-hello-recent-searches) - (indent-rigidly start (point) notmuch-hello-indent))) - - (when alltags-alist - (widget-insert "\nAll tags: ") - (widget-create 'push-button - :notify (lambda (widget &rest ignore) - (setq notmuch-show-all-tags-list nil) - (notmuch-hello-update)) - "hide") - (widget-insert "\n\n") - (let ((start (point))) - (setq found-target-pos (notmuch-hello-insert-tags alltags-alist widest target)) - (if (not final-target-pos) - (setq final-target-pos found-target-pos)) - (indent-rigidly start (point) notmuch-hello-indent))) - - (widget-insert "\n") - - (if (not notmuch-show-all-tags-list) - (widget-create 'push-button - :notify (lambda (widget &rest ignore) - (setq notmuch-show-all-tags-list t) - (notmuch-hello-update)) - "Show all tags"))) - - (let ((start (point))) - (widget-insert "\n\n") - (widget-insert "Type a search query and hit RET to view matching threads.\n") - (when notmuch-hello-recent-searches - (widget-insert "Hit RET to re-submit a previous search. Edit it first if you like.\n") - (widget-insert "Save recent searches with the `save' button.\n")) - (when notmuch-saved-searches - (widget-insert "Edit saved searches with the `edit' button.\n")) - (widget-insert "Hit RET or click on a saved search or tag name to view matching threads.\n") - (widget-insert "`=' refreshes this screen. `s' jumps to the search box. `q' to quit.\n") - (let ((fill-column (- (window-width) notmuch-hello-indent))) - (center-region start (point)))) + (let (final-target-pos) + (mapc + (lambda (section-fun) + (let ((result (funcall section-fun))) + (if (and (not final-target-pos) (integer-or-marker-p result)) + (setq final-target-pos result)) + (widget-insert "\n"))) + notmuch-hello-sections) (widget-setup) - + (when final-target-pos (goto-char final-target-pos) (unless (widget-at) (widget-forward 1))) - + (unless (widget-at) - (notmuch-hello-goto-search))))) + (notmuch-hello-goto-search))) + (setq notmuch-hello-first-run nil))) (defun notmuch-folder () "Deprecated function for invoking notmuch---calling `notmuch' is preferred now." -- 1.7.5.4 ^ permalink raw reply related [flat|nested] 20+ messages in thread
* [PATCH 2/2] emacs: Tests for user-defined sections 2011-06-29 20:26 [PATCH 0/2] emacs: User-defined sections in notmuch-hello Daniel Schoepe 2011-06-29 20:27 ` [PATCH 1/2] " Daniel Schoepe @ 2011-06-29 20:27 ` Daniel Schoepe 2011-07-02 13:31 ` [PATCH v2 0/2] emacs: User-defined sections in notmuch-hello Daniel Schoepe ` (2 subsequent siblings) 4 siblings, 0 replies; 20+ messages in thread From: Daniel Schoepe @ 2011-06-29 20:27 UTC (permalink / raw) To: notmuch --- test/emacs | 37 ++++++++++++++++++++ test/emacs.expected-output/notmuch-hello | 3 +- .../notmuch-hello-new-section | 4 ++ .../notmuch-hello-no-saved-searches | 3 +- .../notmuch-hello-section-before | 18 +++++++++ .../notmuch-hello-section-counts | 5 +++ | 4 ++ .../notmuch-hello-section-with-empty | 4 ++ .../emacs.expected-output/notmuch-hello-with-empty | 3 +- 9 files changed, 78 insertions(+), 3 deletions(-) create mode 100644 test/emacs.expected-output/notmuch-hello-new-section create mode 100644 test/emacs.expected-output/notmuch-hello-section-before create mode 100644 test/emacs.expected-output/notmuch-hello-section-counts create mode 100644 test/emacs.expected-output/notmuch-hello-section-hidden-tag create mode 100644 test/emacs.expected-output/notmuch-hello-section-with-empty diff --git a/test/emacs b/test/emacs index e59de47..7d795d7 100755 --- a/test/emacs +++ b/test/emacs @@ -34,6 +34,43 @@ test_emacs '(let ((notmuch-saved-searches (test-output))' test_expect_equal_file OUTPUT $EXPECTED/notmuch-hello-no-saved-searches +test_begin_subtest "User defined section with inbox tag" +test_emacs "(let ((notmuch-hello-sections + (list (lambda () (notmuch-hello-insert-query-list + \"Test: \" '((\"inbox\" . \"tag:inbox\"))))))) + (notmuch-hello) + (test-output))" +test_expect_equal_file OUTPUT $EXPECTED/notmuch-hello-new-section + +test_begin_subtest "User defined section with empty, hidden entry" +test_emacs "(let ((notmuch-hello-sections + (list (lambda () (notmuch-hello-insert-query-list + \"Test-with-empty:\" + '((\"inbox\" . \"tag:inbox\") + (\"doesnotexist\" . \"tag:doesnotexist\")) + :hide-empty-tags t))))) + (notmuch-hello) + (test-output))" +test_expect_equal_file OUTPUT $EXPECTED/notmuch-hello-section-with-empty + +test_begin_subtest "User defined section, unread tag filtered out" +test_emacs "(let ((notmuch-hello-sections + (list (lambda () (notmuch-hello-insert-tags-section + :title \"Test-with-filtered: \" + :hide-tags '(\"unread\")))))) + (notmuch-hello) + (test-output))" +test_expect_equal_file OUTPUT $EXPECTED/notmuch-hello-section-hidden-tag + +test_begin_subtest "User defined section, different query for counts" +test_emacs "(let ((notmuch-hello-sections + (list (lambda () (notmuch-hello-insert-tags-section + :title \"Test-with-counts: \" + :make-count \"tag:signed\"))))) + (notmuch-hello) + (test-output))" +test_expect_equal_file OUTPUT $EXPECTED/notmuch-hello-section-counts + test_begin_subtest "Basic notmuch-search view in emacs" test_emacs '(notmuch-search "tag:inbox") (notmuch-test-wait) diff --git a/test/emacs.expected-output/notmuch-hello b/test/emacs.expected-output/notmuch-hello index 64b7e42..9666327 100644 --- a/test/emacs.expected-output/notmuch-hello +++ b/test/emacs.expected-output/notmuch-hello @@ -6,9 +6,10 @@ Saved searches: [edit] Search: -[Show all tags] +All tags: [show] Type a search query and hit RET to view matching threads. Edit saved searches with the `edit' button. Hit RET or click on a saved search or tag name to view matching threads. `=' refreshes this screen. `s' jumps to the search box. `q' to quit. + diff --git a/test/emacs.expected-output/notmuch-hello-new-section b/test/emacs.expected-output/notmuch-hello-new-section new file mode 100644 index 0000000..be7b26a --- /dev/null +++ b/test/emacs.expected-output/notmuch-hello-new-section @@ -0,0 +1,4 @@ +Test: [hide] + + 50 inbox + diff --git a/test/emacs.expected-output/notmuch-hello-no-saved-searches b/test/emacs.expected-output/notmuch-hello-no-saved-searches index 7f8206a..744a8f1 100644 --- a/test/emacs.expected-output/notmuch-hello-no-saved-searches +++ b/test/emacs.expected-output/notmuch-hello-no-saved-searches @@ -2,9 +2,10 @@ Search: -[Show all tags] +All tags: [show] Type a search query and hit RET to view matching threads. Edit saved searches with the `edit' button. Hit RET or click on a saved search or tag name to view matching threads. `=' refreshes this screen. `s' jumps to the search box. `q' to quit. + diff --git a/test/emacs.expected-output/notmuch-hello-section-before b/test/emacs.expected-output/notmuch-hello-section-before new file mode 100644 index 0000000..a5781ce --- /dev/null +++ b/test/emacs.expected-output/notmuch-hello-section-before @@ -0,0 +1,18 @@ + Welcome to notmuch. You have 50 messages. + +Saved searches: [edit] + + 50 inbox 50 unread + +Test-before [hide] + + 4 attachment 7 signed + 50 inbox 50 unread + +Search: + + + Type a search query and hit RET to view matching threads. + Edit saved searches with the `edit' button. + Hit RET or click on a saved search or tag name to view matching threads. + `=' refreshes this screen. `s' jumps to the search box. `q' to quit. diff --git a/test/emacs.expected-output/notmuch-hello-section-counts b/test/emacs.expected-output/notmuch-hello-section-counts new file mode 100644 index 0000000..12d19ed --- /dev/null +++ b/test/emacs.expected-output/notmuch-hello-section-counts @@ -0,0 +1,5 @@ +Test-with-counts: [hide] + + 2 attachment 7 inbox 7 signed + 7 unread + eb21c07 --- /dev/null +++ b/test/emacs.expected-output/notmuch-hello-section-hidden-tag --git a/test/emacs.expected-output/notmuch-hello-section-hidden-tag b/test/emacs.expected-output/notmuch-hello-section-hidden-tag new file mode 100644 index 0000000..@@ -0,0 +1,4 @@ +Test-with-filtered: [hide] + + 4 attachment 50 inbox 7 signed + diff --git a/test/emacs.expected-output/notmuch-hello-section-with-empty b/test/emacs.expected-output/notmuch-hello-section-with-empty new file mode 100644 index 0000000..c5b6623 --- /dev/null +++ b/test/emacs.expected-output/notmuch-hello-section-with-empty @@ -0,0 +1,4 @@ +Test-with-empty:[hide] + + 50 inbox + diff --git a/test/emacs.expected-output/notmuch-hello-with-empty b/test/emacs.expected-output/notmuch-hello-with-empty index a9ed630..e9c8eb9 100644 --- a/test/emacs.expected-output/notmuch-hello-with-empty +++ b/test/emacs.expected-output/notmuch-hello-with-empty @@ -6,9 +6,10 @@ Saved searches: [edit] Search: -[Show all tags] +All tags: [show] Type a search query and hit RET to view matching threads. Edit saved searches with the `edit' button. Hit RET or click on a saved search or tag name to view matching threads. `=' refreshes this screen. `s' jumps to the search box. `q' to quit. + -- 1.7.5.4 ^ permalink raw reply related [flat|nested] 20+ messages in thread
* [PATCH v2 0/2] emacs: User-defined sections in notmuch-hello 2011-06-29 20:26 [PATCH 0/2] emacs: User-defined sections in notmuch-hello Daniel Schoepe 2011-06-29 20:27 ` [PATCH 1/2] " Daniel Schoepe 2011-06-29 20:27 ` [PATCH 2/2] emacs: Tests for user-defined sections Daniel Schoepe @ 2011-07-02 13:31 ` Daniel Schoepe 2011-07-02 13:31 ` [PATCH 1/2] " Daniel Schoepe 2011-07-02 13:31 ` [PATCH 2/2] emacs: Tests for user-defined sections Daniel Schoepe 2011-07-05 0:00 ` [PATCH 0/2] emacs: User-defined sections in notmuch-hello Michal Sojka 2011-07-05 16:23 ` [PATCH v3 " Daniel Schoepe 4 siblings, 2 replies; 20+ messages in thread From: Daniel Schoepe @ 2011-07-02 13:31 UTC (permalink / raw) To: notmuch Rebased against current master and some improvements for using customize. ^ permalink raw reply [flat|nested] 20+ messages in thread
* [PATCH 1/2] emacs: User-defined sections in notmuch-hello 2011-07-02 13:31 ` [PATCH v2 0/2] emacs: User-defined sections in notmuch-hello Daniel Schoepe @ 2011-07-02 13:31 ` Daniel Schoepe 2011-07-02 13:31 ` [PATCH 2/2] emacs: Tests for user-defined sections Daniel Schoepe 1 sibling, 0 replies; 20+ messages in thread From: Daniel Schoepe @ 2011-07-02 13:31 UTC (permalink / raw) To: notmuch This patch makes the notmuch-hello screen fully customizable by allowing the user to add and remove arbitrary sections. It also provides some convenience functions for constructing sections, e.g. showing the unread message count for each tag. This is done by specifying a list of functions that will be run when notmuch-hello is invoked. --- emacs/notmuch-hello.el | 570 ++++++++++++++++++++++++++++++------------------ 1 files changed, 356 insertions(+), 214 deletions(-) diff --git a/emacs/notmuch-hello.el b/emacs/notmuch-hello.el index 65fde75..19756e9 100644 --- a/emacs/notmuch-hello.el +++ b/emacs/notmuch-hello.el @@ -55,26 +55,6 @@ :type 'boolean :group 'notmuch) -(defcustom notmuch-hello-tag-list-make-query nil - "Function or string to generate queries for the all tags list. - -This variable controls which query results are shown for each tag -in the \"all tags\" list. If nil, it will use all messages with -that tag. If this is set to a string, it is used as a filter for -messages having that tag (equivalent to \"tag:TAG and (THIS-VARIABLE)\"). -Finally this can be a function that will be called for each tag and -should return a filter for that tag, or nil to hide the tag." - :type '(choice (const :tag "All messages" nil) - (const :tag "Unread messages" "tag:unread") - (const :tag "Custom filter" string) - (const :tag "Custom filter function" function)) - :group 'notmuch) - -(defcustom notmuch-hello-hide-tags nil - "List of tags to be hidden in the \"all tags\"-section." - :type '(repeat string) - :group 'notmuch) - (defface notmuch-hello-logo-background '((((class color) (background dark)) @@ -123,6 +103,73 @@ Typically \",\" in the US and UK and \".\" in Europe." (defvar notmuch-hello-recent-searches nil) +(define-widget 'notmuch-hello-tags-section 'lazy + "Customize-type for notmuch-hello tag-list sections." + :tag "Customized tag-list" + :type + (let ((opts + '((:title (string :tag "Title for this section")) + (:make-query (string :tag "Filter for each tag")) + (:make-count (string :tag "Different query to generate counts")) + (:hide-tags (repeat :tag "Tags that will be hidden" string)) + (:initially-hidden (boolean :tag "Hide this on startup?")) + (:hide-empty-tags (boolean :tag "Hide tags with no matching messages")) + (:hide-if-empty (boolean :tag "Hide if empty"))))) + `(list (const :tag "" notmuch-hello-insert-tags-section) + (plist :inline t :options ,opts)))) + +(define-widget 'notmuch-hello-query-section 'lazy + "Customize-type for custom saved-search-like sections" + :tag "Customized queries section" + :type + '(list (const :tag "" notmuch-hello-insert-query-list) + (string :tag "Title for this section") + (repeat :tag "Queries" + (cons (string :tag "Name") (string :tag "Query"))) + (plist :inline t + :options + ((:initially-hidden (boolean :tag "Hide this on startup?")) + (:hide-empty-tags + (boolean :tag "Hide tags with no matching messages")) + (:hide-if-empty (boolean :tag "Hide if empty")))))) + +(defcustom notmuch-hello-sections + (list #'notmuch-hello-insert-header + #'notmuch-hello-insert-saved-searches + #'notmuch-hello-insert-search + #'notmuch-hello-insert-recent-searches + #'notmuch-hello-insert-alltags + #'notmuch-hello-insert-footer) + "Sections for notmuch-hello. + +Each entry of this list should be a function of no arguments that +should return if notmuch-hello-target is produced as part of its +output and nil otherwise. For convenience an element can also be +a list of the form (FUNC ARG1 ARG2 .. ARGN) in which case FUNC +will be applied to the rest of the list. + +The functions will be run to construct the content of the +notmuch-hello buffer in the order they appear in this list." + :group 'notmuch + :type + '(repeat + (choice (function-item notmuch-hello-insert-header) + (function-item notmuch-hello-insert-saved-searches) + (function-item notmuch-hello-insert-search) + (function-item notmuch-hello-insert-recent-searches) + (function-item notmuch-hello-insert-alltags) + (function-item notmuch-hello-insert-footer) + notmuch-hello-tags-section + notmuch-hello-query-section))) + +;; only defined to avoid compilation warnings about free variables +(defvar notmuch-hello-target nil) + +(defvar notmuch-hello-hidden-sections nil + "List of query sections whose contents are hidden") + +(defvar notmuch-hello-first-run t) + (defun notmuch-hello-remember-search (search) (if (not (member search notmuch-hello-recent-searches)) (push search notmuch-hello-recent-searches)) @@ -238,12 +285,40 @@ should be. Returns a cons cell `(tags-per-line width)'." (* tags-per-line (+ 9 1)))) tags-per-line)))) -(defun notmuch-hello-insert-tags (tag-alist widest target) - (let* ((tags-and-width (notmuch-hello-tags-per-line widest)) +(defun notmuch-hello-query-entries (tag-alist &optional hide-empty) + "Compute list of counts and queries for TAG-ALIST. + +If HIDE-EMPTY is non-nil, entries with no matching messages will be +removed from the result." + (notmuch-remove-if-not + #'identity + (mapcar + (lambda (elem) + (let* ((name (car elem)) + (query-and-count (if (consp (cdr elem)) + ;; do we have a different query for the message count? + (cons (second elem) (third elem)) + (cons (cdr elem) (cdr elem)))) + (message-count + (string-to-number (notmuch-saved-search-count + (cdr query-and-count))))) + (and (or (not hide-empty) (> message-count 0)) + (list name (car query-and-count) message-count)))) + tag-alist))) + +(defun notmuch-hello-insert-tags (entries) + "Insert query items from ENTRIES. + +ENTRIES must be a list containing lists of the form (NAME QUERY COUNT), where +QUERY is the query to start when the button for the corresponding entry is +activated. COUNT should be the number of messages matching the query. +Such a list can be computed with `notmuch-hello-query-entries'." + (let* ((widest (notmuch-hello-longest-label entries)) + (tags-and-width (notmuch-hello-tags-per-line widest)) (tags-per-line (car tags-and-width)) (widest (cdr tags-and-width)) (count 0) - (reordered-list (notmuch-hello-reflect tag-alist tags-per-line)) + (reordered-list (notmuch-hello-reflect entries tags-per-line)) ;; Hack the display of the buttons used. (widget-push-button-prefix "") (widget-push-button-suffix "") @@ -253,13 +328,13 @@ should be. Returns a cons cell `(tags-per-line width)'." (mapc (lambda (elem) ;; (not elem) indicates an empty slot in the matrix. (when elem - (let* ((name (car elem)) - (query (cdr elem)) + (let* ((name (first elem)) + (query (second elem)) + (count (third elem)) (formatted-name (format "%s " name))) (widget-insert (format "%8s " - (notmuch-hello-nice-number - (string-to-number (notmuch-saved-search-count query))))) - (if (string= formatted-name target) + (notmuch-hello-nice-number count))) + (if (string= formatted-name notmuch-hello-target) (setq found-target-pos (point-marker))) (widget-create 'push-button :notify #'notmuch-hello-widget-search @@ -277,7 +352,7 @@ should be. Returns a cons cell `(tags-per-line width)'." (setq count (1+ count)) (if (eq (% count tags-per-line) 0) (widget-insert "\n"))) - reordered-list) + entries) ;; If the last line was not full (and hence did not include a ;; carriage return), insert one now. @@ -325,59 +400,266 @@ should be. Returns a cons cell `(tags-per-line width)'." (fset 'notmuch-hello-mode-map notmuch-hello-mode-map) (defun notmuch-hello-mode () - "Major mode for convenient notmuch navigation. This is your entry portal into notmuch. + "Major mode for convenient notmuch navigation. This is your entry portal into notmuch. Complete list of currently available key bindings: \\{notmuch-hello-mode-map}" - (interactive) - (kill-all-local-variables) - (use-local-map notmuch-hello-mode-map) - (setq major-mode 'notmuch-hello-mode - mode-name "notmuch-hello") - ;;(setq buffer-read-only t) -) - -(defun notmuch-hello-generate-tag-alist () + (interactive) + (kill-all-local-variables) + (use-local-map notmuch-hello-mode-map) + (setq major-mode 'notmuch-hello-mode + mode-name "notmuch-hello") + ;;(setq buffer-read-only t) + ) + +(defun notmuch-hello-make-query (tag make-query) + (cond + ((functionp make-query) + (let ((result (funcall make-query tag))) + (and result (concat "tag:" tag " and (" result ")")))) + ((stringp make-query) + (concat "tag:" tag " and (" make-query ")")) + (t (concat "tag:" tag)))) + +(defun notmuch-hello-generate-tag-alist (&optional hide-tags make-query make-count) "Return an alist from tags to queries to display in the all-tags section." (notmuch-remove-if-not - #'cdr + #'identity (mapcar (lambda (tag) - (cons tag - (cond - ((functionp notmuch-hello-tag-list-make-query) - (concat "tag:" tag " and (" - (funcall notmuch-hello-tag-list-make-query tag) ")")) - ((stringp notmuch-hello-tag-list-make-query) - (concat "tag:" tag " and (" - notmuch-hello-tag-list-make-query ")")) - (t (concat "tag:" tag))))) + (let ((query (notmuch-hello-make-query tag make-query))) + (when query + (if make-count + (list tag (notmuch-hello-make-query tag make-query) + (notmuch-hello-make-query tag make-count)) + (cons tag (notmuch-hello-make-query tag make-query)))))) (notmuch-remove-if-not (lambda (tag) - (not (member tag notmuch-hello-hide-tags))) + (not (member tag hide-tags))) (process-lines notmuch-command "search-tags"))))) +(defun notmuch-hello-insert-header () + "Insert the default notmuch-hello header." + (when notmuch-show-logo + (let ((image notmuch-hello-logo)) + ;; The notmuch logo uses transparency. That can display poorly + ;; when inserting the image into an emacs buffer (black logo on + ;; a black background), so force the background colour of the + ;; image. We use a face to represent the colour so that + ;; `defface' can be used to declare the different possible + ;; colours, which depend on whether the frame has a light or + ;; dark background. + (setq image (cons 'image + (append (cdr image) + (list :background (face-background 'notmuch-hello-logo-background))))) + (insert-image image)) + (widget-insert " ")) + + (widget-insert "Welcome to ") + ;; Hack the display of the links used. + (let ((widget-link-prefix "") + (widget-link-suffix "")) + (widget-create 'link + :notify (lambda (&rest ignore) + (browse-url notmuch-hello-url)) + :help-echo "Visit the notmuch website." + "notmuch") + (widget-insert ". ") + (widget-insert "You have ") + (widget-create 'link + :notify (lambda (&rest ignore) + (notmuch-hello-update)) + :help-echo "Refresh" + (notmuch-hello-nice-number + (string-to-number (car (process-lines notmuch-command "count"))))) + (widget-insert " messages."))) + + +(defun notmuch-hello-insert-saved-searches () + "Insert the saved-searches section." + (let ((entries (notmuch-hello-query-entries + notmuch-saved-searches + (not notmuch-show-empty-saved-searches))) + found-target-pos final-target-pos) + (when entries + (widget-insert "\nSaved searches: ") + (widget-create 'push-button + :notify (lambda (&rest ignore) + (customize-variable 'notmuch-saved-searches)) + "edit") + (widget-insert "\n\n") + (setq final-target-pos (point-marker)) + (let ((start (point))) + (setq found-target-pos + (notmuch-hello-insert-tags entries)) + (if found-target-pos + (setq final-target-pos found-target-pos)) + (indent-rigidly start (point) notmuch-hello-indent) + final-target-pos)))) + +(defun notmuch-hello-insert-search () + "Insert a search widget." + (widget-insert "Search: ") + (setq notmuch-hello-search-bar-marker (point-marker)) + (widget-create 'editable-field + ;; Leave some space at the start and end of the + ;; search boxes. + :size (max 8 (- (window-width) notmuch-hello-indent + (length "Search: "))) + :action (lambda (widget &rest ignore) + (notmuch-hello-search (widget-value widget))))) + +(defun notmuch-hello-insert-recent-searches () + "Insert recent searches." + (when notmuch-hello-recent-searches + (widget-insert "\nRecent searches: ") + (widget-create 'push-button + :notify (lambda (&rest ignore) + (setq notmuch-hello-recent-searches nil) + (notmuch-hello-update)) + "clear") + (widget-insert "\n\n") + (let ((start (point)) + (nth 0)) + (mapc '(lambda (search) + (let ((widget-symbol (intern (format "notmuch-hello-search-%d" nth)))) + (set widget-symbol + (widget-create 'editable-field + ;; Don't let the search boxes be + ;; less than 8 characters wide. + :size (max 8 + (- (window-width) + ;; Leave some space + ;; at the start and + ;; end of the + ;; boxes. + (* 2 notmuch-hello-indent) + ;; 1 for the space + ;; before the + ;; `[save]' button. 6 + ;; for the `[save]' + ;; button. + 1 6)) + :action (lambda (widget &rest ignore) + (notmuch-hello-search (widget-value widget))) + search)) + (widget-insert " ") + (widget-create 'push-button + :notify (lambda (widget &rest ignore) + (notmuch-hello-add-saved-search widget)) + :notmuch-saved-search-widget widget-symbol + "save")) + (widget-insert "\n") + (setq nth (1+ nth))) + notmuch-hello-recent-searches) + (indent-rigidly start (point) notmuch-hello-indent)))) + +(defun notmuch-hello-insert-query-list (title query-alist &rest options) + "Insert a section with TITLE showing tags from QUERY-ALIST. + +Supports the following entries in OPTIONS as a plist: +:initially-hidden - if non-nil, section will be hidden on startup +:hide-empty-tags - hide entries with no matching messages +:hide-if-empty - hide if no entries would be shown + (only makes sense with :hide-empty-tags) + +QUERY-ALIST must be a list containing elements of the form (NAME . QUERY) +or (NAME QUERY COUNT-QUERY). If the latter form is used, +COUNT-QUERY specifies an alternate query to be used to generate +the count for the associated tag." + (widget-insert title) + (if (and notmuch-hello-first-run (plist-get options :initially-hidden)) + (add-to-list 'notmuch-hello-hidden-sections title)) + (let ((is-hidden (member title notmuch-hello-hidden-sections)) + (start (point))) + (if is-hidden + (widget-create 'push-button + :notify `(lambda (widget &rest ignore) + (setq notmuch-hello-hidden-sections + (delete ,title notmuch-hello-hidden-sections)) + (notmuch-hello-update)) + "show") + (widget-create 'push-button + :notify `(lambda (widget &rest ignore) + (add-to-list 'notmuch-hello-hidden-sections + ,title) + (notmuch-hello-update)) + "hide")) + (widget-insert "\n") + (let (target-pos + (entries (notmuch-hello-query-entries + query-alist (plist-get options :hide-empty-tags)))) + (when (and (not is-hidden) + (or (not (plist-get options :hide-if-empty)) + entries)) + (widget-insert "\n") + (setq target-pos + (notmuch-hello-insert-tags entries)) + (indent-rigidly start (point) notmuch-hello-indent) + target-pos)))) + +(defun notmuch-hello-insert-tags-section (&rest options) + "Insert a section displaying all tags and message counts for each. + +Allowed options are those accepted by `notmuch-hello-insert-query-list' and the +following: + +:title - Title for this section, defaults to \"All tags: \" +:make-query - This can be a function that takes a tag as its argument and + returns a filter to be used for the query for that tag or nil to hide the + tag. This can also be a string that is used as a filter for messages with that tag. +:make-count - Seperate query to generate the count that should be displayed for each + tag. Accepts the same values as :make-query +:hide-tags - List of tags that should be excluded." + (apply 'notmuch-hello-insert-query-list + (or (plist-get options :title) "All tags: ") + (notmuch-hello-generate-tag-alist + (plist-get options :hide-tags) + (plist-get options :make-query) + (plist-get options :make-count)) + options)) + +(defun notmuch-hello-insert-alltags () + "Insert a section displaying all tags and associated message counts" + (notmuch-hello-insert-tags-section :initially-hidden + (not notmuch-show-all-tags-list))) + +(defun notmuch-hello-insert-footer () + "Insert the notmuch-hello footer." + (let ((start (point))) + (widget-insert "Type a search query and hit RET to view matching threads.\n") + (when notmuch-hello-recent-searches + (widget-insert "Hit RET to re-submit a previous search. Edit it first if you like.\n") + (widget-insert "Save recent searches with the `save' button.\n")) + (when notmuch-saved-searches + (widget-insert "Edit saved searches with the `edit' button.\n")) + (widget-insert "Hit RET or click on a saved search or tag name to view matching threads.\n") + (widget-insert "`=' refreshes this screen. `s' jumps to the search box. `q' to quit.\n") + (let ((fill-column (- (window-width) notmuch-hello-indent))) + (center-region start (point))))) + + ;;;###autoload (defun notmuch-hello (&optional no-display) "Run notmuch and display saved searches, known tags, etc." (interactive) - ; Jump through a hoop to get this value from the deprecated variable - ; name (`notmuch-folders') or from the default value. + ; Jump through a hoop to get this value from the deprecated variable + ; name (`notmuch-folders') or from the default value. (if (not notmuch-saved-searches) - (setq notmuch-saved-searches (notmuch-saved-searches))) + (setq notmuch-saved-searches (notmuch-saved-searches))) (if no-display (set-buffer "*notmuch-hello*") (switch-to-buffer "*notmuch-hello*")) - (let ((target (if (widget-at) - (widget-value (widget-at)) - (condition-case nil - (progn - (widget-forward 1) - (widget-value (widget-at))) - (error nil))))) + (let ((notmuch-hello-target (if (widget-at) + (widget-value (widget-at)) + (condition-case nil + (progn + (widget-forward 1) + (widget-value (widget-at))) + (error nil))))) (kill-all-local-variables) (let ((inhibit-read-only t)) @@ -391,167 +673,27 @@ Complete list of currently available key bindings: (mapc 'delete-overlay (car all)) (mapc 'delete-overlay (cdr all))) - (when notmuch-show-logo - (let ((image notmuch-hello-logo)) - ;; The notmuch logo uses transparency. That can display poorly - ;; when inserting the image into an emacs buffer (black logo on - ;; a black background), so force the background colour of the - ;; image. We use a face to represent the colour so that - ;; `defface' can be used to declare the different possible - ;; colours, which depend on whether the frame has a light or - ;; dark background. - (setq image (cons 'image - (append (cdr image) - (list :background (face-background 'notmuch-hello-logo-background))))) - (insert-image image)) - (widget-insert " ")) - - (widget-insert "Welcome to ") - ;; Hack the display of the links used. - (let ((widget-link-prefix "") - (widget-link-suffix "")) - (widget-create 'link - :notify (lambda (&rest ignore) - (browse-url notmuch-hello-url)) - :help-echo "Visit the notmuch website." - "notmuch") - (widget-insert ". ") - (widget-insert "You have ") - (widget-create 'link - :notify (lambda (&rest ignore) - (notmuch-hello-update)) - :help-echo "Refresh" - (notmuch-hello-nice-number - (string-to-number (car (process-lines notmuch-command "count"))))) - (widget-insert " messages.\n")) - - (let ((found-target-pos nil) - (final-target-pos nil)) - (let* ((saved-alist - ;; Filter out empty saved searches if required. - (if notmuch-show-empty-saved-searches - notmuch-saved-searches - (loop for elem in notmuch-saved-searches - if (> (string-to-number (notmuch-saved-search-count (cdr elem))) 0) - collect elem))) - (saved-widest (notmuch-hello-longest-label saved-alist)) - (alltags-alist (if notmuch-show-all-tags-list (notmuch-hello-generate-tag-alist))) - (alltags-widest (notmuch-hello-longest-label alltags-alist)) - (widest (max saved-widest alltags-widest))) - - (when saved-alist - (widget-insert "\nSaved searches: ") - (widget-create 'push-button - :notify (lambda (&rest ignore) - (customize-variable 'notmuch-saved-searches)) - "edit") - (widget-insert "\n\n") - (setq final-target-pos (point-marker)) - (let ((start (point))) - (setq found-target-pos (notmuch-hello-insert-tags saved-alist widest target)) - (if found-target-pos - (setq final-target-pos found-target-pos)) - (indent-rigidly start (point) notmuch-hello-indent))) - - (widget-insert "\nSearch: ") - (setq notmuch-hello-search-bar-marker (point-marker)) - (widget-create 'editable-field - ;; Leave some space at the start and end of the - ;; search boxes. - :size (max 8 (- (window-width) notmuch-hello-indent - (length "Search: "))) - :action (lambda (widget &rest ignore) - (notmuch-hello-search (widget-value widget)))) - (widget-insert "\n") - - (when notmuch-hello-recent-searches - (widget-insert "\nRecent searches: ") - (widget-create 'push-button - :notify (lambda (&rest ignore) - (setq notmuch-hello-recent-searches nil) - (notmuch-hello-update)) - "clear") - (widget-insert "\n\n") - (let ((start (point)) - (nth 0)) - (mapc '(lambda (search) - (let ((widget-symbol (intern (format "notmuch-hello-search-%d" nth)))) - (set widget-symbol - (widget-create 'editable-field - ;; Don't let the search boxes be - ;; less than 8 characters wide. - :size (max 8 - (- (window-width) - ;; Leave some space - ;; at the start and - ;; end of the - ;; boxes. - (* 2 notmuch-hello-indent) - ;; 1 for the space - ;; before the - ;; `[save]' button. 6 - ;; for the `[save]' - ;; button. - 1 6)) - :action (lambda (widget &rest ignore) - (notmuch-hello-search (widget-value widget))) - search)) - (widget-insert " ") - (widget-create 'push-button - :notify (lambda (widget &rest ignore) - (notmuch-hello-add-saved-search widget)) - :notmuch-saved-search-widget widget-symbol - "save")) - (widget-insert "\n") - (setq nth (1+ nth))) - notmuch-hello-recent-searches) - (indent-rigidly start (point) notmuch-hello-indent))) - - (when alltags-alist - (widget-insert "\nAll tags: ") - (widget-create 'push-button - :notify (lambda (widget &rest ignore) - (setq notmuch-show-all-tags-list nil) - (notmuch-hello-update)) - "hide") - (widget-insert "\n\n") - (let ((start (point))) - (setq found-target-pos (notmuch-hello-insert-tags alltags-alist widest target)) - (if (not final-target-pos) - (setq final-target-pos found-target-pos)) - (indent-rigidly start (point) notmuch-hello-indent))) - - (widget-insert "\n") - - (if (not notmuch-show-all-tags-list) - (widget-create 'push-button - :notify (lambda (widget &rest ignore) - (setq notmuch-show-all-tags-list t) - (notmuch-hello-update)) - "Show all tags"))) - - (let ((start (point))) - (widget-insert "\n\n") - (widget-insert "Type a search query and hit RET to view matching threads.\n") - (when notmuch-hello-recent-searches - (widget-insert "Hit RET to re-submit a previous search. Edit it first if you like.\n") - (widget-insert "Save recent searches with the `save' button.\n")) - (when notmuch-saved-searches - (widget-insert "Edit saved searches with the `edit' button.\n")) - (widget-insert "Hit RET or click on a saved search or tag name to view matching threads.\n") - (widget-insert "`=' refreshes this screen. `s' jumps to the search box. `q' to quit.\n") - (let ((fill-column (- (window-width) notmuch-hello-indent))) - (center-region start (point)))) + (let (final-target-pos) + (mapc + (lambda (section) + (let ((result (if (functionp section) + (funcall section) + (apply (car section) (cdr section))))) + (if (and (not final-target-pos) (integer-or-marker-p result)) + (setq final-target-pos result)) + (widget-insert "\n"))) + notmuch-hello-sections) (widget-setup) - + (when final-target-pos (goto-char final-target-pos) (unless (widget-at) (widget-forward 1))) - + (unless (widget-at) - (notmuch-hello-goto-search))))) + (notmuch-hello-goto-search))) + (setq notmuch-hello-first-run nil))) (defun notmuch-folder () "Deprecated function for invoking notmuch---calling `notmuch' is preferred now." -- 1.7.5.4 ^ permalink raw reply related [flat|nested] 20+ messages in thread
* [PATCH 2/2] emacs: Tests for user-defined sections 2011-07-02 13:31 ` [PATCH v2 0/2] emacs: User-defined sections in notmuch-hello Daniel Schoepe 2011-07-02 13:31 ` [PATCH 1/2] " Daniel Schoepe @ 2011-07-02 13:31 ` Daniel Schoepe 1 sibling, 0 replies; 20+ messages in thread From: Daniel Schoepe @ 2011-07-02 13:31 UTC (permalink / raw) To: notmuch --- test/emacs | 37 ++++++++++++++++++++ test/emacs.expected-output/notmuch-hello | 3 +- .../notmuch-hello-new-section | 4 ++ .../notmuch-hello-no-saved-searches | 3 +- .../notmuch-hello-section-before | 18 +++++++++ .../notmuch-hello-section-counts | 5 +++ | 4 ++ .../notmuch-hello-section-with-empty | 4 ++ .../emacs.expected-output/notmuch-hello-with-empty | 3 +- 9 files changed, 78 insertions(+), 3 deletions(-) create mode 100644 test/emacs.expected-output/notmuch-hello-new-section create mode 100644 test/emacs.expected-output/notmuch-hello-section-before create mode 100644 test/emacs.expected-output/notmuch-hello-section-counts create mode 100644 test/emacs.expected-output/notmuch-hello-section-hidden-tag create mode 100644 test/emacs.expected-output/notmuch-hello-section-with-empty diff --git a/test/emacs b/test/emacs index 53f455a..e5ae509 100755 --- a/test/emacs +++ b/test/emacs @@ -34,6 +34,43 @@ test_emacs '(let ((notmuch-saved-searches (test-output))' test_expect_equal_file OUTPUT $EXPECTED/notmuch-hello-no-saved-searches +test_begin_subtest "User defined section with inbox tag" +test_emacs "(let ((notmuch-hello-sections + (list (lambda () (notmuch-hello-insert-query-list + \"Test: \" '((\"inbox\" . \"tag:inbox\"))))))) + (notmuch-hello) + (test-output))" +test_expect_equal_file OUTPUT $EXPECTED/notmuch-hello-new-section + +test_begin_subtest "User defined section with empty, hidden entry" +test_emacs "(let ((notmuch-hello-sections + (list (lambda () (notmuch-hello-insert-query-list + \"Test-with-empty:\" + '((\"inbox\" . \"tag:inbox\") + (\"doesnotexist\" . \"tag:doesnotexist\")) + :hide-empty-tags t))))) + (notmuch-hello) + (test-output))" +test_expect_equal_file OUTPUT $EXPECTED/notmuch-hello-section-with-empty + +test_begin_subtest "User defined section, unread tag filtered out" +test_emacs "(let ((notmuch-hello-sections + (list (lambda () (notmuch-hello-insert-tags-section + :title \"Test-with-filtered: \" + :hide-tags '(\"unread\")))))) + (notmuch-hello) + (test-output))" +test_expect_equal_file OUTPUT $EXPECTED/notmuch-hello-section-hidden-tag + +test_begin_subtest "User defined section, different query for counts" +test_emacs "(let ((notmuch-hello-sections + (list (lambda () (notmuch-hello-insert-tags-section + :title \"Test-with-counts: \" + :make-count \"tag:signed\"))))) + (notmuch-hello) + (test-output))" +test_expect_equal_file OUTPUT $EXPECTED/notmuch-hello-section-counts + test_begin_subtest "Basic notmuch-search view in emacs" test_emacs '(notmuch-search "tag:inbox") (notmuch-test-wait) diff --git a/test/emacs.expected-output/notmuch-hello b/test/emacs.expected-output/notmuch-hello index 64b7e42..9666327 100644 --- a/test/emacs.expected-output/notmuch-hello +++ b/test/emacs.expected-output/notmuch-hello @@ -6,9 +6,10 @@ Saved searches: [edit] Search: -[Show all tags] +All tags: [show] Type a search query and hit RET to view matching threads. Edit saved searches with the `edit' button. Hit RET or click on a saved search or tag name to view matching threads. `=' refreshes this screen. `s' jumps to the search box. `q' to quit. + diff --git a/test/emacs.expected-output/notmuch-hello-new-section b/test/emacs.expected-output/notmuch-hello-new-section new file mode 100644 index 0000000..be7b26a --- /dev/null +++ b/test/emacs.expected-output/notmuch-hello-new-section @@ -0,0 +1,4 @@ +Test: [hide] + + 50 inbox + diff --git a/test/emacs.expected-output/notmuch-hello-no-saved-searches b/test/emacs.expected-output/notmuch-hello-no-saved-searches index 7f8206a..744a8f1 100644 --- a/test/emacs.expected-output/notmuch-hello-no-saved-searches +++ b/test/emacs.expected-output/notmuch-hello-no-saved-searches @@ -2,9 +2,10 @@ Search: -[Show all tags] +All tags: [show] Type a search query and hit RET to view matching threads. Edit saved searches with the `edit' button. Hit RET or click on a saved search or tag name to view matching threads. `=' refreshes this screen. `s' jumps to the search box. `q' to quit. + diff --git a/test/emacs.expected-output/notmuch-hello-section-before b/test/emacs.expected-output/notmuch-hello-section-before new file mode 100644 index 0000000..a5781ce --- /dev/null +++ b/test/emacs.expected-output/notmuch-hello-section-before @@ -0,0 +1,18 @@ + Welcome to notmuch. You have 50 messages. + +Saved searches: [edit] + + 50 inbox 50 unread + +Test-before [hide] + + 4 attachment 7 signed + 50 inbox 50 unread + +Search: + + + Type a search query and hit RET to view matching threads. + Edit saved searches with the `edit' button. + Hit RET or click on a saved search or tag name to view matching threads. + `=' refreshes this screen. `s' jumps to the search box. `q' to quit. diff --git a/test/emacs.expected-output/notmuch-hello-section-counts b/test/emacs.expected-output/notmuch-hello-section-counts new file mode 100644 index 0000000..12d19ed --- /dev/null +++ b/test/emacs.expected-output/notmuch-hello-section-counts @@ -0,0 +1,5 @@ +Test-with-counts: [hide] + + 2 attachment 7 inbox 7 signed + 7 unread + eb21c07 --- /dev/null +++ b/test/emacs.expected-output/notmuch-hello-section-hidden-tag --git a/test/emacs.expected-output/notmuch-hello-section-hidden-tag b/test/emacs.expected-output/notmuch-hello-section-hidden-tag new file mode 100644 index 0000000..@@ -0,0 +1,4 @@ +Test-with-filtered: [hide] + + 4 attachment 50 inbox 7 signed + diff --git a/test/emacs.expected-output/notmuch-hello-section-with-empty b/test/emacs.expected-output/notmuch-hello-section-with-empty new file mode 100644 index 0000000..c5b6623 --- /dev/null +++ b/test/emacs.expected-output/notmuch-hello-section-with-empty @@ -0,0 +1,4 @@ +Test-with-empty:[hide] + + 50 inbox + diff --git a/test/emacs.expected-output/notmuch-hello-with-empty b/test/emacs.expected-output/notmuch-hello-with-empty index a9ed630..e9c8eb9 100644 --- a/test/emacs.expected-output/notmuch-hello-with-empty +++ b/test/emacs.expected-output/notmuch-hello-with-empty @@ -6,9 +6,10 @@ Saved searches: [edit] Search: -[Show all tags] +All tags: [show] Type a search query and hit RET to view matching threads. Edit saved searches with the `edit' button. Hit RET or click on a saved search or tag name to view matching threads. `=' refreshes this screen. `s' jumps to the search box. `q' to quit. + -- 1.7.5.4 ^ permalink raw reply related [flat|nested] 20+ messages in thread
* Re: [PATCH 0/2] emacs: User-defined sections in notmuch-hello 2011-06-29 20:26 [PATCH 0/2] emacs: User-defined sections in notmuch-hello Daniel Schoepe ` (2 preceding siblings ...) 2011-07-02 13:31 ` [PATCH v2 0/2] emacs: User-defined sections in notmuch-hello Daniel Schoepe @ 2011-07-05 0:00 ` Michal Sojka 2011-07-05 8:24 ` Daniel Schoepe 2011-07-05 16:23 ` [PATCH v3 " Daniel Schoepe 4 siblings, 1 reply; 20+ messages in thread From: Michal Sojka @ 2011-07-05 0:00 UTC (permalink / raw) To: Daniel Schoepe, notmuch On Wed, 29 Jun 2011, Daniel Schoepe wrote: > Unfortunately the customize-interface for more customized entries in > notmuch-hello-sections looks a bit weird, but I couldn't figure out how > to get rid of the empty lines produced by generating the lambda expression > needed. Hi Daniel, this looks really great and it comes at the right time for me. I can use it for creating a custom section "What's in your inbox" - a functionality which I previously implemented in my personal branch [*] but I was unable to rebase it on the current HEAD. Basically, it took all saved searches and tag searches, filtered them with tag:inbox and displayed the buttons for those searches. With your patch it is "quite easy" to implement such a functionality. There are a few problems, though. First, the customization interface for the custom sections (not the predefined ones) is very confusing. I was not able to use it at all. Instead I hacked the source code (see below) to add my section to the notmuch-hello-sections list. Second, when I tried to understand your patch, I found some names of functions and variables quite confusing. In the patch below, I tried to give them a better names and I also updated the documentation. Feel free to use my changes for your later patch submission. And last but not least, you allow quite wild modifications of tag searches (e.g. in notmuch-hello-generate-tag-alist), but is might be also useful to use such modifications for other searches. For example, I want to modify the saved searches in a similar way. > To avoid unnecessary complexity in the code this patch removes > aligning all the tag entries in different sections (e.g. saved > searches and all tags) the same way. So if someone really thinks this > was an important features, please yell loudly. I do not miss this at all. And here are my changes to your patch, which you might use as a suggestions for a next version of your patch. As I wrote above, it renames some functions and variables to more understandable names, updates documentation and adds a custom section for my personal use (I can live with this custom section in my .emacs if others do not like it). Cheers, -Michal diff --git a/emacs/notmuch-hello.el b/emacs/notmuch-hello.el index e4c9307..226024c 100644 --- a/emacs/notmuch-hello.el +++ b/emacs/notmuch-hello.el @@ -124,6 +124,7 @@ Typically \",\" in the US and UK and \".\" in Europe." (defcustom notmuch-hello-sections (list #'notmuch-hello-insert-header + #'notmuch-hello-insert-inbox #'notmuch-hello-insert-saved-searches #'notmuch-hello-insert-search #'notmuch-hello-insert-recent-searches @@ -205,8 +206,8 @@ in the order they appear in this list." (message "Saved '%s' as '%s'." search name) (notmuch-hello-update))) -(defun notmuch-hello-longest-label (tag-alist) - (or (loop for elem in tag-alist +(defun notmuch-hello-longest-label (searches-alist) + (or (loop for elem in searches-alist maximize (length (car elem))) 0)) @@ -270,11 +271,18 @@ should be. Returns a cons cell `(tags-per-line width)'." (* tags-per-line (+ 9 1)))) tags-per-line)))) -(defun notmuch-hello-query-entries (tag-alist &optional hide-empty) - "Compute list of counts and queries for TAG-ALIST. +(defun notmuch-hello-query-counts (query-alist &optional hide-empty) + "Compute list of counts of matched messages from QUERY-ALIST. -If HIDE-EMPTY is non-nil, entries with no matching messages will be -removed from the result." +QUERY-ALIST must be a list containing elements of the form (NAME . QUERY) +or (NAME QUERY COUNT-QUERY). If the latter form is used, +COUNT-QUERY specifies an alternate query to be used to generate +the count for the associated query. + +The result is the list of elements of the form (NAME QUERY COUNT). + +If HIDE-EMPTY is non-nil, searches with no matching messages +(COUNT equal to zero) will be removed from the result." (notmuch-remove-if-not #'identity (mapcar @@ -289,21 +297,21 @@ removed from the result." (cdr query-and-count))))) (and (or (not hide-empty) (> message-count 0)) (list name (car query-and-count) message-count)))) - tag-alist))) + query-alist))) -(defun notmuch-hello-insert-tags (entries) - "Insert query items from ENTRIES. +(defun notmuch-hello-insert-buttons (searches) + "Insert buttons for SEARCHES. -ENTRIES must be a list containing lists of the form (NAME QUERY COUNT), where +SEARCHES must be a list containing lists of the form (NAME QUERY COUNT), where QUERY is the query to start when the button for the corresponding entry is activated. COUNT should be the number of messages matching the query. -Such a list can be computed with `notmuch-hello-query-entries'." - (let* ((widest (notmuch-hello-longest-label entries)) +Such a list can be computed with `notmuch-hello-query-counts'." + (let* ((widest (notmuch-hello-longest-label searches)) (tags-and-width (notmuch-hello-tags-per-line widest)) (tags-per-line (car tags-and-width)) (widest (cdr tags-and-width)) (count 0) - (reordered-list (notmuch-hello-reflect entries tags-per-line)) + (reordered-list (notmuch-hello-reflect searches tags-per-line)) ;; Hack the display of the buttons used. (widget-push-button-prefix "") (widget-push-button-suffix "") @@ -337,7 +345,7 @@ Such a list can be computed with `notmuch-hello-query-entries'." (setq count (1+ count)) (if (eq (% count tags-per-line) 0) (widget-insert "\n"))) - entries) + searches) ;; If the last line was not full (and hence did not include a ;; carriage return), insert one now. @@ -398,26 +406,36 @@ Complete list of currently available key bindings: ;;(setq buffer-read-only t) ) -(defun notmuch-hello-make-query (tag make-query) +(defun notmuch-hello-make-tag-query (tag filter) + "Constructs a query to search all messages tagged by TAG and +matching FILTER. + +If FILTER is a string, it is directly used in the returned query. + +If FILTER is a function, it is called with TAG as a parameter and +the string it returns is used as the filter. + +Finally, if FILTER is `t' it is ignored. +" (cond - ((functionp make-query) - (let ((result (funcall make-query tag))) + ((functionp filter) + (let ((result (funcall filter tag))) (and result (concat "tag:" tag " and (" result ")")))) - ((stringp make-query) - (concat "tag:" tag " and (" make-query ")")) + ((stringp filter) + (concat "tag:" tag " and (" filter ")")) (t (concat "tag:" tag)))) -(defun notmuch-hello-generate-tag-alist (&optional hide-tags make-query make-count) +(defun notmuch-hello-generate-tag-alist (&optional hide-tags filter-query filter-count) "Return an alist from tags to queries to display in the all-tags section." (notmuch-remove-if-not #'identity (mapcar (lambda (tag) - (let ((query (notmuch-hello-make-query tag make-query))) + (let ((query (notmuch-hello-make-tag-query tag filter-query))) (when query - (if make-count - (list tag (notmuch-hello-make-query tag make-query) - (notmuch-hello-make-query tag make-count)) - (cons tag (notmuch-hello-make-query tag make-query)))))) + (if filter-count + (list tag (notmuch-hello-make-tag-query tag filter-query) + (notmuch-hello-make-tag-query tag filter-count)) + (cons tag (notmuch-hello-make-tag-query tag filter-query)))))) (notmuch-remove-if-not (lambda (tag) (not (member tag hide-tags))) @@ -462,11 +480,11 @@ Complete list of currently available key bindings: (defun notmuch-hello-insert-saved-searches () "Insert the saved-searches section." - (let ((entries (notmuch-hello-query-entries + (let ((searches (notmuch-hello-query-counts notmuch-saved-searches (not notmuch-show-empty-saved-searches))) found-target-pos final-target-pos) - (when entries + (when searches (widget-insert "\nSaved searches: ") (widget-create 'push-button :notify (lambda (&rest ignore) @@ -476,7 +494,7 @@ Complete list of currently available key bindings: (setq final-target-pos (point-marker)) (let ((start (point))) (setq found-target-pos - (notmuch-hello-insert-tags entries)) + (notmuch-hello-insert-buttons searches)) (if found-target-pos (setq final-target-pos found-target-pos)) (indent-rigidly start (point) notmuch-hello-indent) @@ -539,19 +557,19 @@ Complete list of currently available key bindings: notmuch-hello-recent-searches) (indent-rigidly start (point) notmuch-hello-indent)))) -(defun notmuch-hello-insert-query-list (title query-alist &rest options) - "Insert a section with TITLE showing tags from QUERY-ALIST. - -Supports the following entries in OPTIONS as a plist: -:initially-hidden - if non-nil, section will be hidden on startup -:hide-empty-tags - hide entries with no matching messages -:hide-if-empty - hide if no entries would be shown - (only makes sense with :hide-empty-tags) +(defun notmuch-hello-insert-searches (title query-alist &rest options) + "Insert a section with TITLE showing a list of buttons made from QUERY-ALIST. QUERY-ALIST must be a list containing elements of the form (NAME . QUERY) or (NAME QUERY COUNT-QUERY). If the latter form is used, COUNT-QUERY specifies an alternate query to be used to generate -the count for the associated tag." +the count for the associated tag. + +Supports the following entries in OPTIONS as a plist: +:initially-hidden - if non-nil, section will be hidden on startup +:hide-empty-searches - hide buttons with no matching messages +:hide-if-empty - hide if no buttons would be shown + (only makes sense with :hide-empty-searches)" (widget-insert title) (if (and notmuch-hello-first-run (plist-get options :initially-hidden)) (add-to-list 'notmuch-hello-hidden-sections title)) @@ -572,21 +590,21 @@ the count for the associated tag." "hide")) (widget-insert "\n") (let (target-pos - (entries (notmuch-hello-query-entries - query-alist (plist-get options :hide-empty-tags)))) + (searches (notmuch-hello-query-counts + query-alist (plist-get options :hide-empty-searches)))) (when (and (not is-hidden) (or (not (plist-get options :hide-if-empty)) - entries)) + searches)) (widget-insert "\n") (setq target-pos - (notmuch-hello-insert-tags entries)) + (notmuch-hello=-insert-buttons searches)) (indent-rigidly start (point) notmuch-hello-indent) target-pos)))) (defun notmuch-hello-insert-tags-section (&rest options) "Insert a section displaying all tags and message counts for each. -Allowed options are those accepted by `notmuch-hello-insert-query-list' and the +Allowed options are those accepted by `notmuch-hello-insert-searches' and the following: :title - Title for this section, defaults to \"All tags: \" @@ -596,7 +614,7 @@ following: :make-count - Seperate query to generate the count that should be displayed for each tag. Accepts the same values as :make-query :hide-tags - List of tags that should be excluded." - (apply 'notmuch-hello-insert-query-list + (apply 'notmuch-hello-insert-searches (or (plist-get options :title) "All tags: ") (notmuch-hello-generate-tag-alist (plist-get options :hide-tags) @@ -604,6 +622,15 @@ following: (plist-get options :make-count)) options)) +(defun notmuch-hello-insert-inbox () + (notmuch-hello-insert-searches "What's in your inbox: " + (mapcar (lambda (elem) + (cons (car elem) (concat "tag:inbox and (" (cdr elem) ")"))) + (append + (notmuch-saved-searches) + (notmuch-hello-generate-tag-alist))) + :hide-empty-searches t)) + (defun notmuch-hello-insert-alltags () "Insert a section displaying all tags and associated message counts" (notmuch-hello-insert-tags-section :initially-hidden -- 1.7.5.4 Footnotes: [*] id:"1275633449-17134-1-git-send-email-sojkam1@fel.cvut.cz" ^ permalink raw reply related [flat|nested] 20+ messages in thread
* Re: [PATCH 0/2] emacs: User-defined sections in notmuch-hello 2011-07-05 0:00 ` [PATCH 0/2] emacs: User-defined sections in notmuch-hello Michal Sojka @ 2011-07-05 8:24 ` Daniel Schoepe 2011-07-05 14:09 ` Michal Sojka 0 siblings, 1 reply; 20+ messages in thread From: Daniel Schoepe @ 2011-07-05 8:24 UTC (permalink / raw) To: Michal Sojka, notmuch [-- Attachment #1: Type: text/plain, Size: 2515 bytes --] On Tue, 05 Jul 2011 02:00:33 +0200, Michal Sojka <sojkam1@fel.cvut.cz> wrote: > First, the customization interface for the custom sections (not the > predefined ones) is very confusing. I was not able to use it at all. > Instead I hacked the source code (see below) to add my section to the > notmuch-hello-sections list. What specifically did you find confusing? The first customization-option is intended for sections like the all tags section which various configuration options which should be explained by the docstrings next to the options. The second item allows saved-searches-like sections. By the way: Instead of editing the source directly, you can also put all customizations like that in your configuration files and set notmuch-hello-sections from there. Nevertheless your inbox-display functions look useful in general so they should indeed be included. > Second, when I tried to understand your patch, I found some names of > functions and variables quite confusing. In the patch below, I tried to > give them a better names and I also updated the documentation. Feel free > to use my changes for your later patch submission. Thanks, those look quite a bit clearer, naming things like that is really not my strong suit. I'll include them in the next version of the patch. > > And last but not least, you allow quite wild modifications of tag > searches (e.g. in notmuch-hello-generate-tag-alist), but is might be > also useful to use such modifications for other searches. For example, I > want to modify the saved searches in a similar way. > Are you referring to things like specifying a filter for each tag or count in saved-searches? My thinking was that since you specify each item manually anyway, you can append any filter you might want to use there. But I guess you're right that it's a bit more convenient to be able to specify those once and not having to repeat it for each item. > And here are my changes to your patch, which you might use as a > suggestions for a next version of your patch. As I wrote above, it > renames some functions and variables to more understandable names, > updates documentation and adds a custom section for my personal use (I > can live with this custom section in my .emacs if others do not like > it). If we add the same configuration options that notmuch-hello-generate-tag-alist accepts, this section should be easy to specify in your ~/.emacs with one or two lines anyway. Cheers, Daniel [-- Attachment #2: Type: application/pgp-signature, Size: 835 bytes --] ^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: [PATCH 0/2] emacs: User-defined sections in notmuch-hello 2011-07-05 8:24 ` Daniel Schoepe @ 2011-07-05 14:09 ` Michal Sojka 2011-07-05 14:55 ` Daniel Schoepe 2011-07-05 16:43 ` Daniel Schoepe 0 siblings, 2 replies; 20+ messages in thread From: Michal Sojka @ 2011-07-05 14:09 UTC (permalink / raw) To: Daniel Schoepe, notmuch On Tue, 05 Jul 2011, Daniel Schoepe wrote: Non-text part: multipart/signed > On Tue, 05 Jul 2011 02:00:33 +0200, Michal Sojka <sojkam1@fel.cvut.cz> wrote: > > First, the customization interface for the custom sections (not the > > predefined ones) is very confusing. I was not able to use it at all. > > Instead I hacked the source code (see below) to add my section to the > > notmuch-hello-sections list. > > What specifically did you find confusing? Sorry for not being clear - I wrote the mail late at night, looking forward to some sleep :) > The first customization-option is intended for sections like the all > tags section which various configuration options which should be > explained by the docstrings next to the options. The second item > allows saved-searches-like sections. I'm talking about notmuch-hello-section. It seems it is not possible easily specify there your own function. You can either select a predefined section or "Customized section", which is in fact "Customized all-tags section". So I propose to renaming this entry like this and adding another one called "Custom function", where you could enter the name of your function. > By the way: Instead of editing the source directly, you can also put all > customizations like that in your configuration files and set > notmuch-hello-sections from there. Nevertheless your inbox-display > functions look useful in general so they should indeed be included. I know that, but I wanted to do the experiment quickly and this was the easiest way for me. Regarding setting of notmuch-hello-sections from .emacs, I prefer customizing it through the customization interface because when somebody else introduces a new section it is more convenient for me to try it by choosing from predefined values than modifying that in .emacs. > > And last but not least, you allow quite wild modifications of tag > > searches (e.g. in notmuch-hello-generate-tag-alist), but is might be > > also useful to use such modifications for other searches. For example, I > > want to modify the saved searches in a similar way. > > Are you referring to things like specifying a filter for each tag or > count in saved-searches? My thinking was that since you specify each > item manually anyway, you can append any filter you might want to use > there. But I guess you're right that it's a bit more convenient to be > able to specify those once and not having to repeat it for each item. It seems that I still do not understand what the "Customized section" was intended to provide. I have to say I'm not able to fully decode the definition of notmuch-hello-customized-section, but it seems to me that it is a way how to customize a call to notmuch-hello-insert-all-tags. I wanted to say that it might be useful to have the similar options for saved searches. For example, what I do in my notmuch-hello-insert-inbox is to take the saved searches and filter them by tag:inbox. I guess that if there is a possibility to do that without coding in elisp, some people may find it useful. What do you think? -Michal ^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: [PATCH 0/2] emacs: User-defined sections in notmuch-hello 2011-07-05 14:09 ` Michal Sojka @ 2011-07-05 14:55 ` Daniel Schoepe 2011-07-05 16:43 ` Daniel Schoepe 1 sibling, 0 replies; 20+ messages in thread From: Daniel Schoepe @ 2011-07-05 14:55 UTC (permalink / raw) To: Michal Sojka, notmuch [-- Attachment #1: Type: text/plain, Size: 1591 bytes --] On Tue, 05 Jul 2011 16:09:14 +0200, Michal Sojka <sojkam1@fel.cvut.cz> wrote: > I'm talking about notmuch-hello-section. It seems it is not possible > easily specify there your own function. You can either select a > predefined section or "Customized section", which is in fact "Customized > all-tags section". So I propose to renaming this entry like this and > adding another one called "Custom function", where you could enter the > name of your function. Ah, I see why it confused me: You are referring to the first version of the patch. This particular bit is already clarified in the second iteration of the patch, starting at id:"1309613471-23465-1-git-send-email-daniel.schoepe@googlemail.com" In that case I also fully understand why you found the customize-interface confusing. :) > It seems that I still do not understand what the "Customized section" > was intended to provide. I have to say I'm not able to fully decode the > definition of notmuch-hello-customized-section, but it seems to me that > it is a way how to customize a call to notmuch-hello-insert-all-tags. Yes, that was the intention, in v2 I used a cleaner way to do that. > I wanted to say that it might be useful to have the similar options for > saved searches. For example, what I do in my notmuch-hello-insert-inbox > is to take the saved searches and filter them by tag:inbox. I guess that > if there is a possibility to do that without coding in elisp, some > people may find it useful. What do you think? Ah okay, then I understood you correctly. I agree that this would make sense. Cheers, Daniel [-- Attachment #2: Type: application/pgp-signature, Size: 835 bytes --] ^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: [PATCH 0/2] emacs: User-defined sections in notmuch-hello 2011-07-05 14:09 ` Michal Sojka 2011-07-05 14:55 ` Daniel Schoepe @ 2011-07-05 16:43 ` Daniel Schoepe 1 sibling, 0 replies; 20+ messages in thread From: Daniel Schoepe @ 2011-07-05 16:43 UTC (permalink / raw) To: Michal Sojka, notmuch [-- Attachment #1: Type: text/plain, Size: 727 bytes --] On Tue, 05 Jul 2011 16:09:14 +0200, Michal Sojka <sojkam1@fel.cvut.cz> wrote: > I wanted to say that it might be useful to have the similar options for > saved searches. For example, what I do in my notmuch-hello-insert-inbox > is to take the saved searches and filter them by tag:inbox. I guess that > if there is a possibility to do that without coding in elisp, some > people may find it useful. What do you think? I sent a new version of the patch that incorporates your suggestions with one exception: I removed notmuch-hello-insert-inbox from the default value for notmuch-hello-sections, since personally I find it a bit confusing to have all tags with inbox-tagged messages mixed with the saved searches in one place. [-- Attachment #2: Type: application/pgp-signature, Size: 835 bytes --] ^ permalink raw reply [flat|nested] 20+ messages in thread
* [PATCH v3 0/2] emacs: User-defined sections in notmuch-hello 2011-06-29 20:26 [PATCH 0/2] emacs: User-defined sections in notmuch-hello Daniel Schoepe ` (3 preceding siblings ...) 2011-07-05 0:00 ` [PATCH 0/2] emacs: User-defined sections in notmuch-hello Michal Sojka @ 2011-07-05 16:23 ` Daniel Schoepe 2011-07-05 16:23 ` [PATCH v3 1/2] " Daniel Schoepe 2011-07-05 16:23 ` [PATCH v3 2/2] emacs: Tests for user-defined sections Daniel Schoepe 4 siblings, 2 replies; 20+ messages in thread From: Daniel Schoepe @ 2011-07-05 16:23 UTC (permalink / raw) To: notmuch This version incorporates the changes suggested by Michal Sojka; thanks for your help with this, Michal. ^ permalink raw reply [flat|nested] 20+ messages in thread
* [PATCH v3 1/2] emacs: User-defined sections in notmuch-hello 2011-07-05 16:23 ` [PATCH v3 " Daniel Schoepe @ 2011-07-05 16:23 ` Daniel Schoepe 2011-07-06 11:34 ` Michal Sojka 2011-07-05 16:23 ` [PATCH v3 2/2] emacs: Tests for user-defined sections Daniel Schoepe 1 sibling, 1 reply; 20+ messages in thread From: Daniel Schoepe @ 2011-07-05 16:23 UTC (permalink / raw) To: notmuch This patch makes the notmuch-hello screen fully customizable by allowing the user to add and remove arbitrary sections. It also provides some convenience functions for constructing sections, e.g. showing the unread message count for each tag. This is done by specifying a list of functions that will be run when notmuch-hello is invoked. --- emacs/notmuch-hello.el | 609 +++++++++++++++++++++++++++++++----------------- 1 files changed, 393 insertions(+), 216 deletions(-) diff --git a/emacs/notmuch-hello.el b/emacs/notmuch-hello.el index 65fde75..9c18caa 100644 --- a/emacs/notmuch-hello.el +++ b/emacs/notmuch-hello.el @@ -55,26 +55,6 @@ :type 'boolean :group 'notmuch) -(defcustom notmuch-hello-tag-list-make-query nil - "Function or string to generate queries for the all tags list. - -This variable controls which query results are shown for each tag -in the \"all tags\" list. If nil, it will use all messages with -that tag. If this is set to a string, it is used as a filter for -messages having that tag (equivalent to \"tag:TAG and (THIS-VARIABLE)\"). -Finally this can be a function that will be called for each tag and -should return a filter for that tag, or nil to hide the tag." - :type '(choice (const :tag "All messages" nil) - (const :tag "Unread messages" "tag:unread") - (const :tag "Custom filter" string) - (const :tag "Custom filter function" function)) - :group 'notmuch) - -(defcustom notmuch-hello-hide-tags nil - "List of tags to be hidden in the \"all tags\"-section." - :type '(repeat string) - :group 'notmuch) - (defface notmuch-hello-logo-background '((((class color) (background dark)) @@ -123,6 +103,75 @@ Typically \",\" in the US and UK and \".\" in Europe." (defvar notmuch-hello-recent-searches nil) +(define-widget 'notmuch-hello-tags-section 'lazy + "Customize-type for notmuch-hello tag-list sections." + :tag "Customized tag-list" + :type + (let ((opts + '((:title (string :tag "Title for this section")) + (:make-query (string :tag "Filter for each tag")) + (:make-count (string :tag "Different query to generate counts")) + (:hide-tags (repeat :tag "Tags that will be hidden" string)) + (:initially-hidden (boolean :tag "Hide this on startup?")) + (:hide-empty-tags (boolean :tag "Hide tags with no matching messages")) + (:hide-if-empty (boolean :tag "Hide if empty"))))) + `(list (const :tag "" notmuch-hello-insert-tags-section) + (plist :inline t :options ,opts)))) + +(define-widget 'notmuch-hello-query-section 'lazy + "Customize-type for custom saved-search-like sections" + :tag "Customized queries section" + :type + '(list (const :tag "" notmuch-hello-insert-query-list) + (string :tag "Title for this section") + (repeat :tag "Queries" + (cons (string :tag "Name") (string :tag "Query"))) + (plist :inline t + :options + ((:initially-hidden (boolean :tag "Hide this on startup?")) + (:hide-empty-tags + (boolean :tag "Hide tags with no matching messages")) + (:hide-if-empty (boolean :tag "Hide if empty")))))) + +(defcustom notmuch-hello-sections + (list #'notmuch-hello-insert-header + #'notmuch-hello-insert-saved-searches + #'notmuch-hello-insert-search + #'notmuch-hello-insert-recent-searches + #'notmuch-hello-insert-alltags + #'notmuch-hello-insert-footer) + "Sections for notmuch-hello. + +Each entry of this list should be a function of no arguments that +should return if notmuch-hello-target is produced as part of its +output and nil otherwise. For convenience an element can also be +a list of the form (FUNC ARG1 ARG2 .. ARGN) in which case FUNC +will be applied to the rest of the list. + +The functions will be run to construct the content of the +notmuch-hello buffer in the order they appear in this list." + :group 'notmuch + :type + '(repeat + (choice (function-item notmuch-hello-insert-header) + (function-item notmuch-hello-insert-saved-searches) + (function-item notmuch-hello-insert-search) + (function-item notmuch-hello-insert-recent-searches) + (function-item notmuch-hello-insert-alltags) + (function-item notmuch-hello-insert-footer) + (function-item notmuch-hello-insert-inbox) + notmuch-hello-tags-section + notmuch-hello-query-section + (function :tag "Custom function")))) + +;; only defined to avoid compilation warnings about free variables +(defvar notmuch-hello-target nil) + +(defvar notmuch-hello-hidden-sections nil + "List of query sections whose contents are hidden") + +(defvar notmuch-hello-first-run t) + (defun notmuch-hello-remember-search (search) (if (not (member search notmuch-hello-recent-searches)) (push search notmuch-hello-recent-searches)) @@ -173,8 +222,8 @@ Typically \",\" in the US and UK and \".\" in Europe." (message "Saved '%s' as '%s'." search name) (notmuch-hello-update))) -(defun notmuch-hello-longest-label (tag-alist) - (or (loop for elem in tag-alist +(defun notmuch-hello-longest-label (searches-alist) + (or (loop for elem in searches-alist maximize (length (car elem))) 0)) @@ -238,12 +287,71 @@ should be. Returns a cons cell `(tags-per-line width)'." (* tags-per-line (+ 9 1)))) tags-per-line)))) -(defun notmuch-hello-insert-tags (tag-alist widest target) - (let* ((tags-and-width (notmuch-hello-tags-per-line widest)) +(defun notmuch-hello-filtered-query (query filter) + "Constructs a query to search all messages matching QUERY and FILTER. + +If FILTER is a string, it is directly used in the returned query. + +If FILTER is a function, it is called with QUERY as a parameter and +the string it returns is used as the filter. + +Otherwise, FILTER is ignored. +" + (cond + ((functionp filter) + (let ((result (funcall filter query))) + (and result (concat query " and (" result ")")))) + ((stringp filter) + (concat query " and (" filter ")")) + (t (concat query)))) + +(defun notmuch-hello-query-counts (query-alist &rest options) + "Compute list of counts of matched messages from QUERY-ALIST. + +QUERY-ALIST must be a list containing elements of the form (NAME . QUERY) +or (NAME QUERY COUNT-QUERY). If the latter form is used, +COUNT-QUERY specifies an alternate query to be used to generate +the count for the associated query. + +The result is the list of elements of the form (NAME QUERY COUNT). + +The values :hide-empty-searches, :make-query and :make-count from +options will be handled as specified for +`notmuch-hello-insert-searches'." + (notmuch-remove-if-not + #'identity + (mapcar + (lambda (elem) + (let* ((name (car elem)) + (query-and-count (if (consp (cdr elem)) + ;; do we have a different query for the message count? + (cons (second elem) (third elem)) + (cons (cdr elem) (cdr elem)))) + (message-count + (string-to-number + (notmuch-saved-search-count + (notmuch-hello-filtered-query (cdr query-and-count) + (or (plist-get options :make-count) + (plist-get options :make-query))))))) + (and (or (not (plist-get options :hide-empty-searches)) (> message-count 0)) + (list name (notmuch-hello-filtered-query + (car query-and-count) (plist-get options :make-query)) + message-count)))) + query-alist))) + +(defun notmuch-hello-insert-buttons (searches) + "Insert buttons for SEARCHES. + +SEARCHES must be a list containing lists of the form (NAME QUERY COUNT), where +QUERY is the query to start when the button for the corresponding entry is +activated. COUNT should be the number of messages matching the query. +Such a list can be computed with `notmuch-hello-query-counts'." + (let* ((widest (notmuch-hello-longest-label searches)) + (tags-and-width (notmuch-hello-tags-per-line widest)) (tags-per-line (car tags-and-width)) (widest (cdr tags-and-width)) (count 0) - (reordered-list (notmuch-hello-reflect tag-alist tags-per-line)) + (reordered-list (notmuch-hello-reflect searches tags-per-line)) ;; Hack the display of the buttons used. (widget-push-button-prefix "") (widget-push-button-suffix "") @@ -253,13 +361,13 @@ should be. Returns a cons cell `(tags-per-line width)'." (mapc (lambda (elem) ;; (not elem) indicates an empty slot in the matrix. (when elem - (let* ((name (car elem)) - (query (cdr elem)) + (let* ((name (first elem)) + (query (second elem)) + (count (third elem)) (formatted-name (format "%s " name))) (widget-insert (format "%8s " - (notmuch-hello-nice-number - (string-to-number (notmuch-saved-search-count query))))) - (if (string= formatted-name target) + (notmuch-hello-nice-number count))) + (if (string= formatted-name notmuch-hello-target) (setq found-target-pos (point-marker))) (widget-create 'push-button :notify #'notmuch-hello-widget-search @@ -277,7 +385,7 @@ should be. Returns a cons cell `(tags-per-line width)'." (setq count (1+ count)) (if (eq (% count tags-per-line) 0) (widget-insert "\n"))) - reordered-list) + searches) ;; If the last line was not full (and hence did not include a ;; carriage return), insert one now. @@ -325,59 +433,268 @@ should be. Returns a cons cell `(tags-per-line width)'." (fset 'notmuch-hello-mode-map notmuch-hello-mode-map) (defun notmuch-hello-mode () - "Major mode for convenient notmuch navigation. This is your entry portal into notmuch. + "Major mode for convenient notmuch navigation. This is your entry portal into notmuch. Complete list of currently available key bindings: \\{notmuch-hello-mode-map}" - (interactive) - (kill-all-local-variables) - (use-local-map notmuch-hello-mode-map) - (setq major-mode 'notmuch-hello-mode - mode-name "notmuch-hello") - ;;(setq buffer-read-only t) -) - -(defun notmuch-hello-generate-tag-alist () + (interactive) + (kill-all-local-variables) + (use-local-map notmuch-hello-mode-map) + (setq major-mode 'notmuch-hello-mode + mode-name "notmuch-hello") + ;;(setq buffer-read-only t) + ) + +(defun notmuch-hello-generate-tag-alist (&optional hide-tags filter-query filter-count) "Return an alist from tags to queries to display in the all-tags section." (notmuch-remove-if-not - #'cdr + #'identity (mapcar (lambda (tag) - (cons tag - (cond - ((functionp notmuch-hello-tag-list-make-query) - (concat "tag:" tag " and (" - (funcall notmuch-hello-tag-list-make-query tag) ")")) - ((stringp notmuch-hello-tag-list-make-query) - (concat "tag:" tag " and (" - notmuch-hello-tag-list-make-query ")")) - (t (concat "tag:" tag))))) + (let ((query (notmuch-hello-filtered-query (concat "tag:" tag) + filter-query))) + (when query + (if filter-count + (list tag (notmuch-hello-filtered-query tag filter-query) + (notmuch-hello-filtered-query (concat "tag:" tag) + filter-count)) + (cons tag (notmuch-hello-filtered-query + (concat "tag:" tag) filter-query)))))) (notmuch-remove-if-not (lambda (tag) - (not (member tag notmuch-hello-hide-tags))) + (not (member tag hide-tags))) (process-lines notmuch-command "search-tags"))))) +(defun notmuch-hello-insert-header () + "Insert the default notmuch-hello header." + (when notmuch-show-logo + (let ((image notmuch-hello-logo)) + ;; The notmuch logo uses transparency. That can display poorly + ;; when inserting the image into an emacs buffer (black logo on + ;; a black background), so force the background colour of the + ;; image. We use a face to represent the colour so that + ;; `defface' can be used to declare the different possible + ;; colours, which depend on whether the frame has a light or + ;; dark background. + (setq image (cons 'image + (append (cdr image) + (list :background (face-background 'notmuch-hello-logo-background))))) + (insert-image image)) + (widget-insert " ")) + + (widget-insert "Welcome to ") + ;; Hack the display of the links used. + (let ((widget-link-prefix "") + (widget-link-suffix "")) + (widget-create 'link + :notify (lambda (&rest ignore) + (browse-url notmuch-hello-url)) + :help-echo "Visit the notmuch website." + "notmuch") + (widget-insert ". ") + (widget-insert "You have ") + (widget-create 'link + :notify (lambda (&rest ignore) + (notmuch-hello-update)) + :help-echo "Refresh" + (notmuch-hello-nice-number + (string-to-number (car (process-lines notmuch-command "count"))))) + (widget-insert " messages."))) + + +(defun notmuch-hello-insert-saved-searches () + "Insert the saved-searches section." + (let ((searches (notmuch-hello-query-counts + notmuch-saved-searches + :hide-empty-searches (not notmuch-show-empty-saved-searches))) + found-target-pos final-target-pos) + (when searches + (widget-insert "\nSaved searches: ") + (widget-create 'push-button + :notify (lambda (&rest ignore) + (customize-variable 'notmuch-saved-searches)) + "edit") + (widget-insert "\n\n") + (setq final-target-pos (point-marker)) + (let ((start (point))) + (setq found-target-pos + (notmuch-hello-insert-buttons searches)) + (if found-target-pos + (setq final-target-pos found-target-pos)) + (indent-rigidly start (point) notmuch-hello-indent) + final-target-pos)))) + +(defun notmuch-hello-insert-search () + "Insert a search widget." + (widget-insert "Search: ") + (setq notmuch-hello-search-bar-marker (point-marker)) + (widget-create 'editable-field + ;; Leave some space at the start and end of the + ;; search boxes. + :size (max 8 (- (window-width) notmuch-hello-indent + (length "Search: "))) + :action (lambda (widget &rest ignore) + (notmuch-hello-search (widget-value widget))))) + +(defun notmuch-hello-insert-recent-searches () + "Insert recent searches." + (when notmuch-hello-recent-searches + (widget-insert "\nRecent searches: ") + (widget-create 'push-button + :notify (lambda (&rest ignore) + (setq notmuch-hello-recent-searches nil) + (notmuch-hello-update)) + "clear") + (widget-insert "\n\n") + (let ((start (point)) + (nth 0)) + (mapc '(lambda (search) + (let ((widget-symbol (intern (format "notmuch-hello-search-%d" nth)))) + (set widget-symbol + (widget-create 'editable-field + ;; Don't let the search boxes be + ;; less than 8 characters wide. + :size (max 8 + (- (window-width) + ;; Leave some space + ;; at the start and + ;; end of the + ;; boxes. + (* 2 notmuch-hello-indent) + ;; 1 for the space + ;; before the + ;; `[save]' button. 6 + ;; for the `[save]' + ;; button. + 1 6)) + :action (lambda (widget &rest ignore) + (notmuch-hello-search (widget-value widget))) + search)) + (widget-insert " ") + (widget-create 'push-button + :notify (lambda (widget &rest ignore) + (notmuch-hello-add-saved-search widget)) + :notmuch-saved-search-widget widget-symbol + "save")) + (widget-insert "\n") + (setq nth (1+ nth))) + notmuch-hello-recent-searches) + (indent-rigidly start (point) notmuch-hello-indent)))) + +(defun notmuch-hello-insert-searches (title query-alist &rest options) + "Insert a section with TITLE showing a list of buttons made from QUERY-ALIST. + +QUERY-ALIST must be a list containing elements of the form (NAME . QUERY) +or (NAME QUERY COUNT-QUERY). If the latter form is used, +COUNT-QUERY specifies an alternate query to be used to generate +the count for the associated item. + +Supports the following entries in OPTIONS as a plist: +:initially-hidden - if non-nil, section will be hidden on startup +:hide-empty-searches - hide buttons with no matching messages +:hide-if-empty - hide if no buttons would be shown + (only makes sense with :hide-empty-searches) +:make-query - This can be a function that takes a tag as its argument and + returns a filter to be used for the query for that tag or nil to hide the + tag. This can also be a string that is used as a filter for messages with that tag. +:make-count - Seperate query to generate the count that should be displayed for each + tag. Accepts the same values as :make-query" + (widget-insert title) + (if (and notmuch-hello-first-run (plist-get options :initially-hidden)) + (add-to-list 'notmuch-hello-hidden-sections title)) + (let ((is-hidden (member title notmuch-hello-hidden-sections)) + (start (point))) + (if is-hidden + (widget-create 'push-button + :notify `(lambda (widget &rest ignore) + (setq notmuch-hello-hidden-sections + (delete ,title notmuch-hello-hidden-sections)) + (notmuch-hello-update)) + "show") + (widget-create 'push-button + :notify `(lambda (widget &rest ignore) + (add-to-list 'notmuch-hello-hidden-sections + ,title) + (notmuch-hello-update)) + "hide")) + (widget-insert "\n") + (let (target-pos + (searches (apply 'notmuch-hello-query-counts query-alist options))) + (when (and (not is-hidden) + (or (not (plist-get options :hide-if-empty)) + searches)) + (widget-insert "\n") + (setq target-pos + (notmuch-hello-insert-buttons searches)) + (indent-rigidly start (point) notmuch-hello-indent) + target-pos)))) + +(defun notmuch-hello-insert-tags-section (&optional title &rest options) + "Insert a section displaying all tags and message counts for each. + +TITLE defaults to \"All tags: \". +Allowed options are those accepted by `notmuch-hello-insert-searches' and the +following: + +:hide-tags - List of tags that should be excluded." + (apply 'notmuch-hello-insert-searches + (or title "All tags: ") + (notmuch-hello-generate-tag-alist + (plist-get options :hide-tags) + (plist-get options :make-query) + (plist-get options :make-count)) + options)) + +(defun notmuch-hello-insert-inbox () + "Show an entry for each saved search and inboxed messages for each tag" + (notmuch-hello-insert-searches "What's in your inbox: " + (append + (notmuch-saved-searches) + (notmuch-hello-generate-tag-alist)) + :make-query "tag:inbox" + :hide-empty-searches t)) + +(defun notmuch-hello-insert-alltags () + "Insert a section displaying all tags and associated message counts" + (notmuch-hello-insert-tags-section nil :initially-hidden + (not notmuch-show-all-tags-list))) + +(defun notmuch-hello-insert-footer () + "Insert the notmuch-hello footer." + (let ((start (point))) + (widget-insert "Type a search query and hit RET to view matching threads.\n") + (when notmuch-hello-recent-searches + (widget-insert "Hit RET to re-submit a previous search. Edit it first if you like.\n") + (widget-insert "Save recent searches with the `save' button.\n")) + (when notmuch-saved-searches + (widget-insert "Edit saved searches with the `edit' button.\n")) + (widget-insert "Hit RET or click on a saved search or tag name to view matching threads.\n") + (widget-insert "`=' refreshes this screen. `s' jumps to the search box. `q' to quit.\n") + (let ((fill-column (- (window-width) notmuch-hello-indent))) + (center-region start (point))))) + + ;;;###autoload (defun notmuch-hello (&optional no-display) "Run notmuch and display saved searches, known tags, etc." (interactive) - ; Jump through a hoop to get this value from the deprecated variable - ; name (`notmuch-folders') or from the default value. + ; Jump through a hoop to get this value from the deprecated variable + ; name (`notmuch-folders') or from the default value. (if (not notmuch-saved-searches) - (setq notmuch-saved-searches (notmuch-saved-searches))) + (setq notmuch-saved-searches (notmuch-saved-searches))) (if no-display (set-buffer "*notmuch-hello*") (switch-to-buffer "*notmuch-hello*")) - (let ((target (if (widget-at) - (widget-value (widget-at)) - (condition-case nil - (progn - (widget-forward 1) - (widget-value (widget-at))) - (error nil))))) + (let ((notmuch-hello-target (if (widget-at) + (widget-value (widget-at)) + (condition-case nil + (progn + (widget-forward 1) + (widget-value (widget-at))) + (error nil))))) (kill-all-local-variables) (let ((inhibit-read-only t)) @@ -391,167 +708,27 @@ Complete list of currently available key bindings: (mapc 'delete-overlay (car all)) (mapc 'delete-overlay (cdr all))) - (when notmuch-show-logo - (let ((image notmuch-hello-logo)) - ;; The notmuch logo uses transparency. That can display poorly - ;; when inserting the image into an emacs buffer (black logo on - ;; a black background), so force the background colour of the - ;; image. We use a face to represent the colour so that - ;; `defface' can be used to declare the different possible - ;; colours, which depend on whether the frame has a light or - ;; dark background. - (setq image (cons 'image - (append (cdr image) - (list :background (face-background 'notmuch-hello-logo-background))))) - (insert-image image)) - (widget-insert " ")) - - (widget-insert "Welcome to ") - ;; Hack the display of the links used. - (let ((widget-link-prefix "") - (widget-link-suffix "")) - (widget-create 'link - :notify (lambda (&rest ignore) - (browse-url notmuch-hello-url)) - :help-echo "Visit the notmuch website." - "notmuch") - (widget-insert ". ") - (widget-insert "You have ") - (widget-create 'link - :notify (lambda (&rest ignore) - (notmuch-hello-update)) - :help-echo "Refresh" - (notmuch-hello-nice-number - (string-to-number (car (process-lines notmuch-command "count"))))) - (widget-insert " messages.\n")) - - (let ((found-target-pos nil) - (final-target-pos nil)) - (let* ((saved-alist - ;; Filter out empty saved searches if required. - (if notmuch-show-empty-saved-searches - notmuch-saved-searches - (loop for elem in notmuch-saved-searches - if (> (string-to-number (notmuch-saved-search-count (cdr elem))) 0) - collect elem))) - (saved-widest (notmuch-hello-longest-label saved-alist)) - (alltags-alist (if notmuch-show-all-tags-list (notmuch-hello-generate-tag-alist))) - (alltags-widest (notmuch-hello-longest-label alltags-alist)) - (widest (max saved-widest alltags-widest))) - - (when saved-alist - (widget-insert "\nSaved searches: ") - (widget-create 'push-button - :notify (lambda (&rest ignore) - (customize-variable 'notmuch-saved-searches)) - "edit") - (widget-insert "\n\n") - (setq final-target-pos (point-marker)) - (let ((start (point))) - (setq found-target-pos (notmuch-hello-insert-tags saved-alist widest target)) - (if found-target-pos - (setq final-target-pos found-target-pos)) - (indent-rigidly start (point) notmuch-hello-indent))) - - (widget-insert "\nSearch: ") - (setq notmuch-hello-search-bar-marker (point-marker)) - (widget-create 'editable-field - ;; Leave some space at the start and end of the - ;; search boxes. - :size (max 8 (- (window-width) notmuch-hello-indent - (length "Search: "))) - :action (lambda (widget &rest ignore) - (notmuch-hello-search (widget-value widget)))) - (widget-insert "\n") - - (when notmuch-hello-recent-searches - (widget-insert "\nRecent searches: ") - (widget-create 'push-button - :notify (lambda (&rest ignore) - (setq notmuch-hello-recent-searches nil) - (notmuch-hello-update)) - "clear") - (widget-insert "\n\n") - (let ((start (point)) - (nth 0)) - (mapc '(lambda (search) - (let ((widget-symbol (intern (format "notmuch-hello-search-%d" nth)))) - (set widget-symbol - (widget-create 'editable-field - ;; Don't let the search boxes be - ;; less than 8 characters wide. - :size (max 8 - (- (window-width) - ;; Leave some space - ;; at the start and - ;; end of the - ;; boxes. - (* 2 notmuch-hello-indent) - ;; 1 for the space - ;; before the - ;; `[save]' button. 6 - ;; for the `[save]' - ;; button. - 1 6)) - :action (lambda (widget &rest ignore) - (notmuch-hello-search (widget-value widget))) - search)) - (widget-insert " ") - (widget-create 'push-button - :notify (lambda (widget &rest ignore) - (notmuch-hello-add-saved-search widget)) - :notmuch-saved-search-widget widget-symbol - "save")) - (widget-insert "\n") - (setq nth (1+ nth))) - notmuch-hello-recent-searches) - (indent-rigidly start (point) notmuch-hello-indent))) - - (when alltags-alist - (widget-insert "\nAll tags: ") - (widget-create 'push-button - :notify (lambda (widget &rest ignore) - (setq notmuch-show-all-tags-list nil) - (notmuch-hello-update)) - "hide") - (widget-insert "\n\n") - (let ((start (point))) - (setq found-target-pos (notmuch-hello-insert-tags alltags-alist widest target)) - (if (not final-target-pos) - (setq final-target-pos found-target-pos)) - (indent-rigidly start (point) notmuch-hello-indent))) - - (widget-insert "\n") - - (if (not notmuch-show-all-tags-list) - (widget-create 'push-button - :notify (lambda (widget &rest ignore) - (setq notmuch-show-all-tags-list t) - (notmuch-hello-update)) - "Show all tags"))) - - (let ((start (point))) - (widget-insert "\n\n") - (widget-insert "Type a search query and hit RET to view matching threads.\n") - (when notmuch-hello-recent-searches - (widget-insert "Hit RET to re-submit a previous search. Edit it first if you like.\n") - (widget-insert "Save recent searches with the `save' button.\n")) - (when notmuch-saved-searches - (widget-insert "Edit saved searches with the `edit' button.\n")) - (widget-insert "Hit RET or click on a saved search or tag name to view matching threads.\n") - (widget-insert "`=' refreshes this screen. `s' jumps to the search box. `q' to quit.\n") - (let ((fill-column (- (window-width) notmuch-hello-indent))) - (center-region start (point)))) + (let (final-target-pos) + (mapc + (lambda (section) + (let ((result (if (functionp section) + (funcall section) + (apply (car section) (cdr section))))) + (if (and (not final-target-pos) (integer-or-marker-p result)) + (setq final-target-pos result)) + (widget-insert "\n"))) + notmuch-hello-sections) (widget-setup) - + (when final-target-pos (goto-char final-target-pos) (unless (widget-at) (widget-forward 1))) - + (unless (widget-at) - (notmuch-hello-goto-search))))) + (notmuch-hello-goto-search))) + (setq notmuch-hello-first-run nil))) (defun notmuch-folder () "Deprecated function for invoking notmuch---calling `notmuch' is preferred now." -- 1.7.5.4 ^ permalink raw reply related [flat|nested] 20+ messages in thread
* Re: [PATCH v3 1/2] emacs: User-defined sections in notmuch-hello 2011-07-05 16:23 ` [PATCH v3 1/2] " Daniel Schoepe @ 2011-07-06 11:34 ` Michal Sojka 2011-07-06 12:41 ` Daniel Schoepe 0 siblings, 1 reply; 20+ messages in thread From: Michal Sojka @ 2011-07-06 11:34 UTC (permalink / raw) To: Daniel Schoepe, notmuch Hi, this is definitely better than the first version I was using! Thanks. See other comments below. On Tue, 05 Jul 2011, Daniel Schoepe wrote: > This patch makes the notmuch-hello screen fully customizable > by allowing the user to add and remove arbitrary sections. It > also provides some convenience functions for constructing sections, > e.g. showing the unread message count for each tag. > > This is done by specifying a list of functions that will be run > when notmuch-hello is invoked. > --- > emacs/notmuch-hello.el | 609 +++++++++++++++++++++++++++++++----------------- > 1 files changed, 393 insertions(+), 216 deletions(-) > > diff --git a/emacs/notmuch-hello.el b/emacs/notmuch-hello.el > index 65fde75..9c18caa 100644 > --- a/emacs/notmuch-hello.el > +++ b/emacs/notmuch-hello.el > @@ -55,26 +55,6 @@ > :type 'boolean > :group 'notmuch) > > -(defcustom notmuch-hello-tag-list-make-query nil > - "Function or string to generate queries for the all tags list. I'm not sure it is a good thing to remove customizations that are included in a released version. Some users may configure it and their configuration disappear when they install a new version. Is there a way to migrate this settings to a new, section-based one? > -This variable controls which query results are shown for each tag > -in the \"all tags\" list. If nil, it will use all messages with > -that tag. If this is set to a string, it is used as a filter for > -messages having that tag (equivalent to \"tag:TAG and (THIS-VARIABLE)\"). > -Finally this can be a function that will be called for each tag and > -should return a filter for that tag, or nil to hide the tag." > - :type '(choice (const :tag "All messages" nil) > - (const :tag "Unread messages" "tag:unread") > - (const :tag "Custom filter" string) > - (const :tag "Custom filter function" function)) > - :group 'notmuch) > - > -(defcustom notmuch-hello-hide-tags nil > - "List of tags to be hidden in the \"all tags\"-section." > - :type '(repeat string) > - :group 'notmuch) > - > (defface notmuch-hello-logo-background > '((((class color) > (background dark)) > @@ -123,6 +103,75 @@ Typically \",\" in the US and UK and \".\" in Europe." > > (defvar notmuch-hello-recent-searches nil) > > +(define-widget 'notmuch-hello-tags-section 'lazy > + "Customize-type for notmuch-hello tag-list sections." > + :tag "Customized tag-list" This is IMHO still hard to understand only from looking at the customization widget. Ideally, I'd like to see somewhere a paragraph like this: "This displays a list of all tags, optionally hiding some of them. It is also possible to filter the list of messages matching each tag by an additional filter query. Similarly, the count of messages displayed next to the buttons can be generated by applying another filter to the tag query." Actually, when reading the above paragraph, it still seems a way too complex. What about the following: I do not think it has sense to list custom tags with zero counts - I think everybody would use :hide-empty-tags all the time. Then, the result of listing all tags with 'notmuch search-tags', filtering them with 'filter' and throwing away empty tags, would be the same as running 'notmuch search-tags filter' and then finding the counts by 'notmuch count tag:TAG and filter'. One of the advantage of this approach is that it would probably be faster to generate the section, because you won't query the counts of all tags if your filter matches only a few of them. Additionally, as I didn't read carefully your previous discussions about the additional filter for counts, I do not see much use for it. If you only want to see which tags has unread messages, you can simply add a new section with (:make-query "tag:unread") and you would get what you want. You can also disable all-tags sections. So my proposal is to forget about different queries for filters and counts and having here only the following options: filter, hide-tags and hide-if-empty. Then, the documentation would be simple: "This section displays buttons for all tags of messages matching a FILTER. Optionally, some of these tags may be hidden." Is there a use case, which is not covered by this? > + :type > + (let ((opts > + '((:title (string :tag "Title for this section")) > + (:make-query (string :tag "Filter for each tag")) > + (:make-count (string :tag "Different query to generate counts")) > + (:hide-tags (repeat :tag "Tags that will be hidden" string)) I can imagine, that :hide-tags could also be a (list of) regexp(es). > + (:initially-hidden (boolean :tag "Hide this on startup?")) This is IMHO not needed here, as you always has to enable this section manually. > + (:hide-empty-tags (boolean :tag "Hide tags with no matching messages")) > + (:hide-if-empty (boolean :tag "Hide if empty"))))) > + `(list (const :tag "" notmuch-hello-insert-tags-section) > + (plist :inline t :options ,opts)))) > + > +(define-widget 'notmuch-hello-query-section 'lazy > + "Customize-type for custom saved-search-like sections" > + :tag "Customized queries section" > + :type > + '(list (const :tag "" notmuch-hello-insert-query-list) > + (string :tag "Title for this section") > + (repeat :tag "Queries" > + (cons (string :tag "Name") (string :tag "Query"))) > + (plist :inline t > + :options > + ((:initially-hidden (boolean :tag "Hide this on startup?")) Not needed (see above). > + (:hide-empty-tags Rename to :hide-empty-queries. > + (boolean :tag "Hide tags with no matching messages")) Hide queries... > + (:hide-if-empty (boolean :tag "Hide if empty")))))) > + > +(defcustom notmuch-hello-sections > + (list #'notmuch-hello-insert-header > + #'notmuch-hello-insert-saved-searches > + #'notmuch-hello-insert-search > + #'notmuch-hello-insert-recent-searches > + #'notmuch-hello-insert-alltags > + #'notmuch-hello-insert-footer) > + "Sections for notmuch-hello. > + > +Each entry of this list should be a function of no arguments that > +should return if notmuch-hello-target is produced as part of its What is notmuch-hello-target? I guess I know what it is from reading the code, but if I didn't read the code, this mentioning it here would be of little value for me. Perhaps make the notmuch-hello-target clickable and document it below. > +output and nil otherwise. For convenience an element can also be > +a list of the form (FUNC ARG1 ARG2 .. ARGN) in which case FUNC > +will be applied to the rest of the list. > + > +The functions will be run to construct the content of the > +notmuch-hello buffer in the order they appear in this list." > + :group 'notmuch > + :type > + '(repeat > + (choice (function-item notmuch-hello-insert-header) > + (function-item notmuch-hello-insert-saved-searches) > + (function-item notmuch-hello-insert-search) > + (function-item notmuch-hello-insert-recent-searches) > + (function-item notmuch-hello-insert-alltags) > + (function-item notmuch-hello-insert-footer) > + (function-item notmuch-hello-insert-inbox) > + notmuch-hello-tags-section > + notmuch-hello-query-section > + (function :tag "Custom function")))) > + > +;; only defined to avoid compilation warnings about free variables > +(defvar notmuch-hello-target nil) Add the documentation as discussed above. Additionally, it seems that having only the label in this variable is not enough, since the same label can appear in multiple sections. Perhaps, we need both the title of the section and the label here. > +(defvar notmuch-hello-hidden-sections nil > + "List of query sections whose contents are hidden") "List of section titles whose... > + > +(defvar notmuch-hello-first-run t) > + > (defun notmuch-hello-remember-search (search) > (if (not (member search notmuch-hello-recent-searches)) > (push search notmuch-hello-recent-searches)) > @@ -173,8 +222,8 @@ Typically \",\" in the US and UK and \".\" in Europe." > (message "Saved '%s' as '%s'." search name) > (notmuch-hello-update))) > > -(defun notmuch-hello-longest-label (tag-alist) > - (or (loop for elem in tag-alist > +(defun notmuch-hello-longest-label (searches-alist) > + (or (loop for elem in searches-alist > maximize (length (car elem))) > 0)) > > @@ -238,12 +287,71 @@ should be. Returns a cons cell `(tags-per-line width)'." > (* tags-per-line (+ 9 1)))) > tags-per-line)))) > > -(defun notmuch-hello-insert-tags (tag-alist widest target) > - (let* ((tags-and-width (notmuch-hello-tags-per-line widest)) > +(defun notmuch-hello-filtered-query (query filter) > + "Constructs a query to search all messages matching QUERY and FILTER. > + > +If FILTER is a string, it is directly used in the returned query. > + > +If FILTER is a function, it is called with QUERY as a parameter and > +the string it returns is used as the filter. > + > +Otherwise, FILTER is ignored. > +" > + (cond > + ((functionp filter) > + (let ((result (funcall filter query))) > + (and result (concat query " and (" result ")")))) > + ((stringp filter) > + (concat query " and (" filter ")")) > + (t (concat query)))) > + > +(defun notmuch-hello-query-counts (query-alist &rest options) > + "Compute list of counts of matched messages from QUERY-ALIST. > + > +QUERY-ALIST must be a list containing elements of the form (NAME . QUERY) > +or (NAME QUERY COUNT-QUERY). If the latter form is used, > +COUNT-QUERY specifies an alternate query to be used to generate > +the count for the associated query. > + > +The result is the list of elements of the form (NAME QUERY COUNT). > + > +The values :hide-empty-searches, :make-query and :make-count from > +options will be handled as specified for > +`notmuch-hello-insert-searches'." > + (notmuch-remove-if-not > + #'identity > + (mapcar > + (lambda (elem) > + (let* ((name (car elem)) > + (query-and-count (if (consp (cdr elem)) > + ;; do we have a different query for the message count? > + (cons (second elem) (third elem)) > + (cons (cdr elem) (cdr elem)))) > + (message-count > + (string-to-number > + (notmuch-saved-search-count > + (notmuch-hello-filtered-query (cdr query-and-count) > + (or (plist-get options :make-count) > + (plist-get options :make-query))))))) > + (and (or (not (plist-get options :hide-empty-searches)) (> message-count 0)) > + (list name (notmuch-hello-filtered-query > + (car query-and-count) (plist-get options :make-query)) > + message-count)))) > + query-alist))) > + > +(defun notmuch-hello-insert-buttons (searches) > + "Insert buttons for SEARCHES. > + > +SEARCHES must be a list containing lists of the form (NAME QUERY COUNT), where > +QUERY is the query to start when the button for the corresponding entry is > +activated. COUNT should be the number of messages matching the query. > +Such a list can be computed with `notmuch-hello-query-counts'." > + (let* ((widest (notmuch-hello-longest-label searches)) > + (tags-and-width (notmuch-hello-tags-per-line widest)) > (tags-per-line (car tags-and-width)) > (widest (cdr tags-and-width)) > (count 0) > - (reordered-list (notmuch-hello-reflect tag-alist tags-per-line)) > + (reordered-list (notmuch-hello-reflect searches tags-per-line)) > ;; Hack the display of the buttons used. > (widget-push-button-prefix "") > (widget-push-button-suffix "") > @@ -253,13 +361,13 @@ should be. Returns a cons cell `(tags-per-line width)'." > (mapc (lambda (elem) > ;; (not elem) indicates an empty slot in the matrix. > (when elem > - (let* ((name (car elem)) > - (query (cdr elem)) > + (let* ((name (first elem)) > + (query (second elem)) > + (count (third elem)) > (formatted-name (format "%s " name))) > (widget-insert (format "%8s " > - (notmuch-hello-nice-number > - (string-to-number (notmuch-saved-search-count query))))) > - (if (string= formatted-name target) > + (notmuch-hello-nice-number count))) > + (if (string= formatted-name notmuch-hello-target) > (setq found-target-pos (point-marker))) > (widget-create 'push-button > :notify #'notmuch-hello-widget-search > @@ -277,7 +385,7 @@ should be. Returns a cons cell `(tags-per-line width)'." > (setq count (1+ count)) > (if (eq (% count tags-per-line) 0) > (widget-insert "\n"))) > - reordered-list) > + searches) > > ;; If the last line was not full (and hence did not include a > ;; carriage return), insert one now. > @@ -325,59 +433,268 @@ should be. Returns a cons cell `(tags-per-line width)'." > (fset 'notmuch-hello-mode-map notmuch-hello-mode-map) > > (defun notmuch-hello-mode () > - "Major mode for convenient notmuch navigation. This is your entry portal into notmuch. > + "Major mode for convenient notmuch navigation. This is your entry portal into notmuch. > > Complete list of currently available key bindings: > > \\{notmuch-hello-mode-map}" > - (interactive) > - (kill-all-local-variables) > - (use-local-map notmuch-hello-mode-map) > - (setq major-mode 'notmuch-hello-mode > - mode-name "notmuch-hello") > - ;;(setq buffer-read-only t) > -) > - > -(defun notmuch-hello-generate-tag-alist () > + (interactive) > + (kill-all-local-variables) > + (use-local-map notmuch-hello-mode-map) > + (setq major-mode 'notmuch-hello-mode > + mode-name "notmuch-hello") > + ;;(setq buffer-read-only t) Don't we want to get rid of this line? > + ) > + > +(defun notmuch-hello-generate-tag-alist (&optional hide-tags filter-query filter-count) > "Return an alist from tags to queries to display in the all-tags section." > (notmuch-remove-if-not > - #'cdr > + #'identity > (mapcar (lambda (tag) > - (cons tag > - (cond > - ((functionp notmuch-hello-tag-list-make-query) > - (concat "tag:" tag " and (" > - (funcall notmuch-hello-tag-list-make-query tag) ")")) > - ((stringp notmuch-hello-tag-list-make-query) > - (concat "tag:" tag " and (" > - notmuch-hello-tag-list-make-query ")")) > - (t (concat "tag:" tag))))) > + (let ((query (notmuch-hello-filtered-query (concat "tag:" tag) > + filter-query))) > + (when query > + (if filter-count > + (list tag (notmuch-hello-filtered-query tag filter-query) > + (notmuch-hello-filtered-query (concat "tag:" tag) > + filter-count)) > + (cons tag (notmuch-hello-filtered-query > + (concat "tag:" tag) filter-query)))))) > (notmuch-remove-if-not > (lambda (tag) > - (not (member tag notmuch-hello-hide-tags))) > + (not (member tag hide-tags))) > (process-lines notmuch-command "search-tags"))))) > > +(defun notmuch-hello-insert-header () > + "Insert the default notmuch-hello header." > + (when notmuch-show-logo > + (let ((image notmuch-hello-logo)) > + ;; The notmuch logo uses transparency. That can display poorly > + ;; when inserting the image into an emacs buffer (black logo on > + ;; a black background), so force the background colour of the > + ;; image. We use a face to represent the colour so that > + ;; `defface' can be used to declare the different possible > + ;; colours, which depend on whether the frame has a light or > + ;; dark background. > + (setq image (cons 'image > + (append (cdr image) > + (list :background (face-background 'notmuch-hello-logo-background))))) > + (insert-image image)) > + (widget-insert " ")) > + > + (widget-insert "Welcome to ") > + ;; Hack the display of the links used. > + (let ((widget-link-prefix "") > + (widget-link-suffix "")) > + (widget-create 'link > + :notify (lambda (&rest ignore) > + (browse-url notmuch-hello-url)) > + :help-echo "Visit the notmuch website." > + "notmuch") > + (widget-insert ". ") > + (widget-insert "You have ") > + (widget-create 'link > + :notify (lambda (&rest ignore) > + (notmuch-hello-update)) > + :help-echo "Refresh" > + (notmuch-hello-nice-number > + (string-to-number (car (process-lines notmuch-command "count"))))) > + (widget-insert " messages."))) Perhaps you want to end this (and also all other) section with an empty line so that the order of sections doesn't matter. I use sections in this order: header, inbox, saved-searches and there is no space between header and inbox and two spaces between inbox and saved-searches. > +(defun notmuch-hello-insert-saved-searches () > + "Insert the saved-searches section." > + (let ((searches (notmuch-hello-query-counts > + notmuch-saved-searches > + :hide-empty-searches (not notmuch-show-empty-saved-searches))) > + found-target-pos final-target-pos) > + (when searches > + (widget-insert "\nSaved searches: ") And here you want to remove the "\n" from the beginning. > + (widget-create 'push-button > + :notify (lambda (&rest ignore) > + (customize-variable 'notmuch-saved-searches)) > + "edit") > + (widget-insert "\n\n") > + (setq final-target-pos (point-marker)) > + (let ((start (point))) > + (setq found-target-pos > + (notmuch-hello-insert-buttons searches)) > + (if found-target-pos > + (setq final-target-pos found-target-pos)) > + (indent-rigidly start (point) notmuch-hello-indent) > + final-target-pos)))) > + > +(defun notmuch-hello-insert-search () > + "Insert a search widget." > + (widget-insert "Search: ") > + (setq notmuch-hello-search-bar-marker (point-marker)) > + (widget-create 'editable-field > + ;; Leave some space at the start and end of the > + ;; search boxes. > + :size (max 8 (- (window-width) notmuch-hello-indent > + (length "Search: "))) > + :action (lambda (widget &rest ignore) > + (notmuch-hello-search (widget-value widget))))) > + > +(defun notmuch-hello-insert-recent-searches () > + "Insert recent searches." > + (when notmuch-hello-recent-searches > + (widget-insert "\nRecent searches: ") > + (widget-create 'push-button > + :notify (lambda (&rest ignore) > + (setq notmuch-hello-recent-searches nil) > + (notmuch-hello-update)) > + "clear") > + (widget-insert "\n\n") > + (let ((start (point)) > + (nth 0)) > + (mapc '(lambda (search) > + (let ((widget-symbol (intern (format "notmuch-hello-search-%d" nth)))) > + (set widget-symbol > + (widget-create 'editable-field > + ;; Don't let the search boxes be > + ;; less than 8 characters wide. > + :size (max 8 > + (- (window-width) > + ;; Leave some space > + ;; at the start and > + ;; end of the > + ;; boxes. > + (* 2 notmuch-hello-indent) > + ;; 1 for the space > + ;; before the > + ;; `[save]' button. 6 > + ;; for the `[save]' > + ;; button. > + 1 6)) > + :action (lambda (widget &rest ignore) > + (notmuch-hello-search (widget-value widget))) > + search)) > + (widget-insert " ") > + (widget-create 'push-button > + :notify (lambda (widget &rest ignore) > + (notmuch-hello-add-saved-search widget)) > + :notmuch-saved-search-widget widget-symbol > + "save")) > + (widget-insert "\n") > + (setq nth (1+ nth))) > + notmuch-hello-recent-searches) > + (indent-rigidly start (point) notmuch-hello-indent)))) > + > +(defun notmuch-hello-insert-searches (title query-alist &rest options) > + "Insert a section with TITLE showing a list of buttons made from QUERY-ALIST. > + > +QUERY-ALIST must be a list containing elements of the form (NAME . QUERY) > +or (NAME QUERY COUNT-QUERY). If the latter form is used, > +COUNT-QUERY specifies an alternate query to be used to generate > +the count for the associated item. > + > +Supports the following entries in OPTIONS as a plist: > +:initially-hidden - if non-nil, section will be hidden on startup > +:hide-empty-searches - hide buttons with no matching messages > +:hide-if-empty - hide if no buttons would be shown > + (only makes sense with :hide-empty-searches) > +:make-query - This can be a function that takes a tag as its argument and > + returns a filter to be used for the query for that tag or nil to hide the > + tag. This can also be a string that is used as a filter for messages with that tag. > +:make-count - Seperate query to generate the count that should be displayed for each > + tag. Accepts the same values as :make-query" Again, I'm not sure that the distinction between the normal query and count query is needed now. > + (widget-insert title) > + (if (and notmuch-hello-first-run (plist-get options :initially-hidden)) > + (add-to-list 'notmuch-hello-hidden-sections title)) > + (let ((is-hidden (member title notmuch-hello-hidden-sections)) > + (start (point))) > + (if is-hidden > + (widget-create 'push-button > + :notify `(lambda (widget &rest ignore) > + (setq notmuch-hello-hidden-sections > + (delete ,title notmuch-hello-hidden-sections)) > + (notmuch-hello-update)) > + "show") > + (widget-create 'push-button > + :notify `(lambda (widget &rest ignore) > + (add-to-list 'notmuch-hello-hidden-sections > + ,title) > + (notmuch-hello-update)) > + "hide")) > + (widget-insert "\n") > + (let (target-pos > + (searches (apply 'notmuch-hello-query-counts query-alist options))) > + (when (and (not is-hidden) > + (or (not (plist-get options :hide-if-empty)) > + searches)) > + (widget-insert "\n") > + (setq target-pos > + (notmuch-hello-insert-buttons searches)) > + (indent-rigidly start (point) notmuch-hello-indent) > + target-pos)))) > + > +(defun notmuch-hello-insert-tags-section (&optional title &rest options) > + "Insert a section displaying all tags and message counts for each. > + > +TITLE defaults to \"All tags: \". > +Allowed options are those accepted by `notmuch-hello-insert-searches' and the > +following: > + > +:hide-tags - List of tags that should be excluded." > + (apply 'notmuch-hello-insert-searches > + (or title "All tags: ") > + (notmuch-hello-generate-tag-alist > + (plist-get options :hide-tags) > + (plist-get options :make-query) > + (plist-get options :make-count)) > + options)) > + > +(defun notmuch-hello-insert-inbox () > + "Show an entry for each saved search and inboxed messages for each tag" > + (notmuch-hello-insert-searches "What's in your inbox: " > + (append > + (notmuch-saved-searches) > + (notmuch-hello-generate-tag-alist)) > + :make-query "tag:inbox" > + :hide-empty-searches t)) > + > +(defun notmuch-hello-insert-alltags () > + "Insert a section displaying all tags and associated message counts" > + (notmuch-hello-insert-tags-section nil :initially-hidden > + (not notmuch-show-all-tags-list))) > + > +(defun notmuch-hello-insert-footer () > + "Insert the notmuch-hello footer." > + (let ((start (point))) > + (widget-insert "Type a search query and hit RET to view matching threads.\n") > + (when notmuch-hello-recent-searches > + (widget-insert "Hit RET to re-submit a previous search. Edit it first if you like.\n") > + (widget-insert "Save recent searches with the `save' button.\n")) > + (when notmuch-saved-searches > + (widget-insert "Edit saved searches with the `edit' button.\n")) > + (widget-insert "Hit RET or click on a saved search or tag name to view matching threads.\n") > + (widget-insert "`=' refreshes this screen. `s' jumps to the search box. `q' to quit.\n") > + (let ((fill-column (- (window-width) notmuch-hello-indent))) > + (center-region start (point))))) I might be useful to include here a link to the customization of this page. Maybe, it would be useful to have notmuch-hello subgroup in customization interface and link to this group. But creation of the subgroup should be definitely in a separate patch. Thanks again for such a great functionality. -Michal ^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: [PATCH v3 1/2] emacs: User-defined sections in notmuch-hello 2011-07-06 11:34 ` Michal Sojka @ 2011-07-06 12:41 ` Daniel Schoepe 2011-07-07 15:23 ` Michal Sojka 0 siblings, 1 reply; 20+ messages in thread From: Daniel Schoepe @ 2011-07-06 12:41 UTC (permalink / raw) To: Michal Sojka, notmuch [-- Attachment #1: Type: text/plain, Size: 10380 bytes --] On Wed, 06 Jul 2011 13:34:13 +0200, Michal Sojka <sojkam1@fel.cvut.cz> wrote: > > -(defcustom notmuch-hello-tag-list-make-query nil > > - "Function or string to generate queries for the all tags list. > > I'm not sure it is a good thing to remove customizations that are > included in a released version. Some users may configure it and their > configuration disappear when they install a new version. Is there a way > to migrate this settings to a new, section-based one? Agreed, I think I only removed it because at the time I first implemented user-defined sections, my patch, which introduced notmuch-hello-tag-list-make-query, hadn't been applied yet. > This is IMHO still hard to understand only from looking at the > customization widget. Ideally, I'd like to see somewhere a paragraph > like this: "This displays a list of all tags, optionally hiding some of > them. It is also possible to filter the list of messages matching each > tag by an additional filter query. Similarly, the count of messages > displayed next to the buttons can be generated by applying another > filter to the tag query." > > Actually, when reading the above paragraph, it still seems a way too > complex. What about the following: I do not think it has sense to list > custom tags with zero counts - I think everybody would use > :hide-empty-tags all the time. Then, the result of listing all tags with > 'notmuch search-tags', filtering them with 'filter' and throwing away > empty tags, would be the same as running 'notmuch search-tags filter' > and then finding the counts by 'notmuch count tag:TAG and filter'. One > of the advantage of this approach is that it would probably be faster to > generate the section, because you won't query the counts of all tags if > your filter matches only a few of them. > > Additionally, as I didn't read carefully your previous discussions about > the additional filter for counts, I do not see much use for it. If you > only want to see which tags has unread messages, you can simply add a > new section with (:make-query "tag:unread") and you would get what you > want. You can also disable all-tags sections. > > So my proposal is to forget about different queries for filters and > counts and having here only the following options: filter, hide-tags and > hide-if-empty. > > Then, the documentation would be simple: "This section displays buttons > for all tags of messages matching a FILTER. Optionally, some of these > tags may be hidden." > > Is there a use case, which is not covered by this? I'm not sure, I can imagine someone wanting to have an overview of all his tags, whether there are, e.g., unread messages or not. If we decide to keep this functionality, it should be inverted though, i.e. one has to explicitly specify :show-empty-searches to get them. About the counts: I introduced this because Austin Clements says he finds it useful in his comment here: id:"BANLkTi=729DWai4q57iBSfz1wDhBXsmndQ@mail.gmail.com" Also, I think/hope that we can just improve the documentation sufficiently without sacrificing flexibility. > > > + :type > > + (let ((opts > > + '((:title (string :tag "Title for this section")) > > + (:make-query (string :tag "Filter for each tag")) > > + (:make-count (string :tag "Different query to generate counts")) > > + (:hide-tags (repeat :tag "Tags that will be hidden" string)) > > I can imagine, that :hide-tags could also be a (list of) regexp(es). > > > + (:initially-hidden (boolean :tag "Hide this on startup?")) > > This is IMHO not needed here, as you always has to enable this section > manually. A user might still want to have the section collapsed when starting the notmuch UI and only have it shown when he needs it. (I use that for a section that displays unread counts for each tag). > > > + (:hide-empty-tags > > Rename to :hide-empty-queries. > > > + (boolean :tag "Hide tags with no matching messages")) > > Hide queries... Right, thanks. > > > + (:hide-if-empty (boolean :tag "Hide if empty")))))) > > + > > +(defcustom notmuch-hello-sections > > + (list #'notmuch-hello-insert-header > > + #'notmuch-hello-insert-saved-searches > > + #'notmuch-hello-insert-search > > + #'notmuch-hello-insert-recent-searches > > + #'notmuch-hello-insert-alltags > > + #'notmuch-hello-insert-footer) > > + "Sections for notmuch-hello. > > + > > +Each entry of this list should be a function of no arguments that > > +should return if notmuch-hello-target is produced as part of its > > What is notmuch-hello-target? I guess I know what it is from reading the > code, but if I didn't read the code, this mentioning it here would be of > little value for me. Perhaps make the notmuch-hello-target clickable and > document it below. Yes, you're right, this needs more documentation. > > > +output and nil otherwise. For convenience an element can also be > > +a list of the form (FUNC ARG1 ARG2 .. ARGN) in which case FUNC > > +will be applied to the rest of the list. > > + > > +The functions will be run to construct the content of the > > +notmuch-hello buffer in the order they appear in this list." > > + :group 'notmuch > > + :type > > + '(repeat > > + (choice (function-item notmuch-hello-insert-header) > > + (function-item notmuch-hello-insert-saved-searches) > > + (function-item notmuch-hello-insert-search) > > + (function-item notmuch-hello-insert-recent-searches) > > + (function-item notmuch-hello-insert-alltags) > > + (function-item notmuch-hello-insert-footer) > > + (function-item notmuch-hello-insert-inbox) > > + notmuch-hello-tags-section > > + notmuch-hello-query-section > > + (function :tag "Custom function")))) > > + > > +;; only defined to avoid compilation warnings about free variables > > +(defvar notmuch-hello-target nil) > > Add the documentation as discussed above. Additionally, it seems that > having only the label in this variable is not enough, since the same > label can appear in multiple sections. Perhaps, we need both the title > of the section and the label here. What do you mean by label? "Custom function"? If yes, that element will have the actual function name in the input element next to it anyway. > > + ;;(setq buffer-read-only t) > > Don't we want to get rid of this line? I guess so, it was there before my patch though. > > > + ) > > + > > +(defun notmuch-hello-generate-tag-alist (&optional hide-tags filter-query filter-count) > > "Return an alist from tags to queries to display in the all-tags section." > > (notmuch-remove-if-not > > - #'cdr > > + #'identity > > (mapcar (lambda (tag) > > - (cons tag > > - (cond > > - ((functionp notmuch-hello-tag-list-make-query) > > - (concat "tag:" tag " and (" > > - (funcall notmuch-hello-tag-list-make-query tag) ")")) > > - ((stringp notmuch-hello-tag-list-make-query) > > - (concat "tag:" tag " and (" > > - notmuch-hello-tag-list-make-query ")")) > > - (t (concat "tag:" tag))))) > > + (let ((query (notmuch-hello-filtered-query (concat "tag:" tag) > > + filter-query))) > > + (when query > > + (if filter-count > > + (list tag (notmuch-hello-filtered-query tag filter-query) > > + (notmuch-hello-filtered-query (concat "tag:" tag) > > + filter-count)) > > + (cons tag (notmuch-hello-filtered-query > > + (concat "tag:" tag) filter-query)))))) > > (notmuch-remove-if-not > > (lambda (tag) > > - (not (member tag notmuch-hello-hide-tags))) > > + (not (member tag hide-tags))) > > (process-lines notmuch-command "search-tags"))))) > > > > +(defun notmuch-hello-insert-header () > > + "Insert the default notmuch-hello header." > > + (when notmuch-show-logo > > + (let ((image notmuch-hello-logo)) > > + ;; The notmuch logo uses transparency. That can display poorly > > + ;; when inserting the image into an emacs buffer (black logo on > > + ;; a black background), so force the background colour of the > > + ;; image. We use a face to represent the colour so that > > + ;; `defface' can be used to declare the different possible > > + ;; colours, which depend on whether the frame has a light or > > + ;; dark background. > > + (setq image (cons 'image > > + (append (cdr image) > > + (list :background (face-background 'notmuch-hello-logo-background))))) > > + (insert-image image)) > > + (widget-insert " ")) > > + > > + (widget-insert "Welcome to ") > > + ;; Hack the display of the links used. > > + (let ((widget-link-prefix "") > > + (widget-link-suffix "")) > > + (widget-create 'link > > + :notify (lambda (&rest ignore) > > + (browse-url notmuch-hello-url)) > > + :help-echo "Visit the notmuch website." > > + "notmuch") > > + (widget-insert ". ") > > + (widget-insert "You have ") > > + (widget-create 'link > > + :notify (lambda (&rest ignore) > > + (notmuch-hello-update)) > > + :help-echo "Refresh" > > + (notmuch-hello-nice-number > > + (string-to-number (car (process-lines notmuch-command "count"))))) > > + (widget-insert " messages."))) > > Perhaps you want to end this (and also all other) section with an empty > line so that the order of sections doesn't matter. I use sections in > this order: header, inbox, saved-searches and there is no space between > header and inbox and two spaces between inbox and saved-searches. My thinking was to have no section end with a newline and insert a newline between each section when building the notmuch-hello buffer, to prevent forgotten trailing newlines when defining a new section. > I might be useful to include here a link to the customization of this > page. Maybe, it would be useful to have notmuch-hello subgroup in > customization interface and link to this group. But creation of the > subgroup should be definitely in a separate patch. Yes definitely. Pieter Praet recently sent a patch reorganizing the customization options into subgroups, so I'll change it once that patch has been applied. Cheers, Daniel [-- Attachment #2: Type: application/pgp-signature, Size: 835 bytes --] ^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: [PATCH v3 1/2] emacs: User-defined sections in notmuch-hello 2011-07-06 12:41 ` Daniel Schoepe @ 2011-07-07 15:23 ` Michal Sojka 2011-07-07 18:25 ` Daniel Schoepe 0 siblings, 1 reply; 20+ messages in thread From: Michal Sojka @ 2011-07-07 15:23 UTC (permalink / raw) To: Daniel Schoepe, notmuch On Wed, 06 Jul 2011, Daniel Schoepe wrote: Non-text part: multipart/signed > On Wed, 06 Jul 2011 13:34:13 +0200, Michal Sojka <sojkam1@fel.cvut.cz> wrote: > > So my proposal is to forget about different queries for filters and > > counts and having here only the following options: filter, hide-tags and > > hide-if-empty. > > > > Then, the documentation would be simple: "This section displays buttons > > for all tags of messages matching a FILTER. Optionally, some of these > > tags may be hidden." > > > > Is there a use case, which is not covered by this? > > I'm not sure, I can imagine someone wanting to have an overview of all > his tags, whether there are, e.g., unread messages or not. This wouldn't work for me. My all-tags section covers almost entire screen and finding non-zero entries there is not very convenient. I find much more useful to have a section saying: "Hey, you have unread messages only for these three tags". Moreover, it wouldn't help me to see non-zero number of unread messages and when I click the button I would see all the messages, not only the unread ones. It simply seems very confusing to me. > If we decide to keep this functionality, it should be inverted though, > i.e. one has to explicitly specify :show-empty-searches to get them. > > About the counts: I introduced this because Austin Clements says he > finds it useful in his comment here: > > id:"BANLkTi=729DWai4q57iBSfz1wDhBXsmndQ@mail.gmail.com" I agree that it is useful to see unread counts, but it is not useful to see all messages when I click the button. > > > + :type > > > + (let ((opts > > > + '((:title (string :tag "Title for this section")) > > > + (:make-query (string :tag "Filter for each tag")) > > > + (:make-count (string :tag "Different query to generate counts")) > > > + (:hide-tags (repeat :tag "Tags that will be hidden" string)) > > > > I can imagine, that :hide-tags could also be a (list of) regexp(es). > > > > > + (:initially-hidden (boolean :tag "Hide this on startup?")) > > > > This is IMHO not needed here, as you always has to enable this section > > manually. > > A user might still want to have the section collapsed when starting the > notmuch UI and only have it shown when he needs it. (I use that for a > section that displays unread counts for each tag). You are right. I use emacs --daemon so I actually initialize notmuch UI only when emacs crashes or when I run out of battery power ;-) > > > +;; only defined to avoid compilation warnings about free variables > > > +(defvar notmuch-hello-target nil) > > > > Add the documentation as discussed above. Additionally, it seems that > > having only the label in this variable is not enough, since the same > > label can appear in multiple sections. Perhaps, we need both the title > > of the section and the label here. > > What do you mean by label? "Custom function"? If yes, that element will > have the actual function name in the input element next to it anyway. If I understand this variable correctly, it stores the label (text) of the button you have your point at. This allows you to stay at the same button after reloading of notmuch-hello even if the layout changes, right? Then having the same named button in multiple sections results in moving the first (or last) occurrence of this button when notmuch-hello is reloaded. > > > Perhaps you want to end this (and also all other) section with an empty > > line so that the order of sections doesn't matter. I use sections in > > this order: header, inbox, saved-searches and there is no space between > > header and inbox and two spaces between inbox and saved-searches. > > My thinking was to have no section end with a newline and insert a > newline between each section when building the notmuch-hello buffer, to > prevent forgotten trailing newlines when defining a new section. This sounds reasonable. > > I might be useful to include here a link to the customization of this > > page. Maybe, it would be useful to have notmuch-hello subgroup in > > customization interface and link to this group. But creation of the > > subgroup should be definitely in a separate patch. > > Yes definitely. Pieter Praet recently sent a patch reorganizing the > customization options into subgroups, so I'll change it once that patch > has been applied. Good. -Michal ^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: [PATCH v3 1/2] emacs: User-defined sections in notmuch-hello 2011-07-07 15:23 ` Michal Sojka @ 2011-07-07 18:25 ` Daniel Schoepe 2011-07-08 21:23 ` Michal Sojka 0 siblings, 1 reply; 20+ messages in thread From: Daniel Schoepe @ 2011-07-07 18:25 UTC (permalink / raw) To: Michal Sojka, notmuch [-- Attachment #1: Type: text/plain, Size: 3166 bytes --] On Thu, 07 Jul 2011 17:23:13 +0200, Michal Sojka <sojkam1@fel.cvut.cz> wrote: > This wouldn't work for me. My all-tags section covers almost entire > screen and finding non-zero entries there is not very convenient. I find > much more useful to have a section saying: "Hey, you have unread > messages only for these three tags". Moreover, it wouldn't help me to see > non-zero number of unread messages and when I click the button I would > see all the messages, not only the unread ones. It simply seems very > confusing to me. I agree with you, personally, but I don't think this particular bit (:hide-empty-searches) increases code complexity that much (and all of it is localized to the functions generating the sections, which possibly should be moved to a separate file). I also find it plausible that a user might want behavior like this (as, e.g. an overview, if he doesn't want an all tags section, but still see what tags he has set). Hence I don't see why we should not provide this option. > > If we decide to keep this functionality, it should be inverted though, > > i.e. one has to explicitly specify :show-empty-searches to get them. > > > > > About the counts: I introduced this because Austin Clements says he > > finds it useful in his comment here: > > > > id:"BANLkTi=729DWai4q57iBSfz1wDhBXsmndQ@mail.gmail.com" > > I agree that it is useful to see unread counts, but it is not useful to > see all messages when I click the button. As I said, this is not my preference either, but personal taste is not necessarily the best argument for _not including_ something, especially for the UI of a mail client. And in this case there even is someone saying that behavior would be useful to him. > > A user might still want to have the section collapsed when starting the > > notmuch UI and only have it shown when he needs it. (I use that for a > > section that displays unread counts for each tag). > > You are right. I use emacs --daemon so I actually initialize notmuch UI > only when emacs crashes or when I run out of battery power ;-) Yes, me too, but I like seeing only sections that I care about every time I open notmuch while still having more information available in a convenient way. But I'm not too attached to this feature, so if the consensus is that that bit should be removed, I'll bow to public opinion. :) > If I understand this variable correctly, it stores the label (text) of > the button you have your point at. This allows you to stay at the same > button after reloading of notmuch-hello even if the layout changes, > right? Then having the same named button in multiple sections results in > moving the first (or last) occurrence of this button when notmuch-hello > is reloaded. Ah, I misunderstood what you were referring to earlier. That was already case before my patch though, except that now it's more likely to have buttons with the same name. Anyway, I think this is just a minor issue and can be addressed in a different patch. In any event, this definitely needs better documentation. Thanks for all your input on this! Cheers, Daniel [-- Attachment #2: Type: application/pgp-signature, Size: 835 bytes --] ^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: [PATCH v3 1/2] emacs: User-defined sections in notmuch-hello 2011-07-07 18:25 ` Daniel Schoepe @ 2011-07-08 21:23 ` Michal Sojka 2011-07-08 22:03 ` Daniel Schoepe 0 siblings, 1 reply; 20+ messages in thread From: Michal Sojka @ 2011-07-08 21:23 UTC (permalink / raw) To: Daniel Schoepe, notmuch On Thu, 07 Jul 2011, Daniel Schoepe wrote: Non-text part: multipart/signed > On Thu, 07 Jul 2011 17:23:13 +0200, Michal Sojka <sojkam1@fel.cvut.cz> wrote: > > This wouldn't work for me. My all-tags section covers almost entire > > screen and finding non-zero entries there is not very convenient. I find > > much more useful to have a section saying: "Hey, you have unread > > messages only for these three tags". Moreover, it wouldn't help me to see > > non-zero number of unread messages and when I click the button I would > > see all the messages, not only the unread ones. It simply seems very > > confusing to me. > > I agree with you, personally, but I don't think this particular bit > (:hide-empty-searches) increases code complexity that much (and all of > it is localized to the functions generating the sections, which possibly > should be moved to a separate file). I also find it plausible that a > user might want behavior like this (as, e.g. an overview, if he doesn't > want an all tags section, but still see what tags he has set). > > Hence I don't see why we should not provide this option. OK. I changed my mind. I thought that the approach with 'notmuch search-tags QEURY' followed by 'notmuch count tag:XXX and QUERY' for returned tags would be faster then 'notmuch search-tags' followed by 'notmuch count tag:XXX and QUERY' for all tags, but it turned out not to be the case. I played with the customization interface a bit and found the following problems: - It is annoying to insert section titles that includes ": " at the end in order to be nicely "rendered". I think this could be appended automatically. - :hide-empty-tags should be renamed in :hide-empty-searches to be effective (see the patch below). - The title of custom tags section was not passed correctly to the functions. This is also fixed in the patch below. -Michal diff --git a/emacs/notmuch-hello.el b/emacs/notmuch-hello.el index 9c18caa..7e81076 100644 --- a/emacs/notmuch-hello.el +++ b/emacs/notmuch-hello.el @@ -113,7 +113,7 @@ Typically \",\" in the US and UK and \".\" in Europe." (:make-count (string :tag "Different query to generate counts")) (:hide-tags (repeat :tag "Tags that will be hidden" string)) (:initially-hidden (boolean :tag "Hide this on startup?")) - (:hide-empty-tags (boolean :tag "Hide tags with no matching messages")) + (:hide-empty-searches (boolean :tag "Hide tags with no matching messages")) (:hide-if-empty (boolean :tag "Hide if empty"))))) `(list (const :tag "" notmuch-hello-insert-tags-section) (plist :inline t :options ,opts)))) @@ -129,7 +129,7 @@ Typically \",\" in the US and UK and \".\" in Europe." (plist :inline t :options ((:initially-hidden (boolean :tag "Hide this on startup?")) - (:hide-empty-tags + (:hide-empty-searches (boolean :tag "Hide tags with no matching messages")) (:hide-if-empty (boolean :tag "Hide if empty")))))) @@ -629,7 +629,7 @@ Supports the following entries in OPTIONS as a plist: (indent-rigidly start (point) notmuch-hello-indent) target-pos)))) -(defun notmuch-hello-insert-tags-section (&optional title &rest options) +(defun notmuch-hello-insert-tags-section (&rest options) "Insert a section displaying all tags and message counts for each. TITLE defaults to \"All tags: \". @@ -638,7 +638,7 @@ following: :hide-tags - List of tags that should be excluded." (apply 'notmuch-hello-insert-searches - (or title "All tags: ") + (plist-get options :title) (notmuch-hello-generate-tag-alist (plist-get options :hide-tags) (plist-get options :make-query) @@ -656,8 +656,8 @@ following: (defun notmuch-hello-insert-alltags () "Insert a section displaying all tags and associated message counts" - (notmuch-hello-insert-tags-section nil :initially-hidden - (not notmuch-show-all-tags-list))) + (notmuch-hello-insert-tags-section :title "All tags: " + :initially-hidden (not notmuch-show-all-tags-list))) (defun notmuch-hello-insert-footer () "Insert the notmuch-hello footer." ^ permalink raw reply related [flat|nested] 20+ messages in thread
* Re: [PATCH v3 1/2] emacs: User-defined sections in notmuch-hello 2011-07-08 21:23 ` Michal Sojka @ 2011-07-08 22:03 ` Daniel Schoepe 0 siblings, 0 replies; 20+ messages in thread From: Daniel Schoepe @ 2011-07-08 22:03 UTC (permalink / raw) To: Michal Sojka, notmuch [-- Attachment #1: Type: text/plain, Size: 1454 bytes --] On Fri, 08 Jul 2011 23:23:40 +0200, Michal Sojka <sojkam1@fel.cvut.cz> wrote: > OK. I changed my mind. I thought that the approach with 'notmuch > search-tags QEURY' followed by 'notmuch count tag:XXX and QUERY' for > returned tags would be faster then 'notmuch search-tags' followed by > 'notmuch count tag:XXX and QUERY' for all tags, but it turned out not to > be the case. For me, the stronger argument was that the code would have been simpler, because notmuch-hello refreshes don't happen that often that performance shouldn't matter much. > > I played with the customization interface a bit and found the following > problems: > > - It is annoying to insert section titles that includes ": " at the end > in order to be nicely "rendered". I think this could be appended > automatically. Agreed. > > - :hide-empty-tags should be renamed in :hide-empty-searches to be > effective (see the patch below). This has been done in v4 of the patch, for which I screwed up the In-Reply-To header and hence is listed as a separate thread: id:"1310079227-19120-1-git-send-email-daniel.schoepe@googlemail.com" > > - The title of custom tags section was not passed correctly to the > functions. This is also fixed in the patch below. I changed title to a mandatory argument for consistency with notmuch-insert-searches and because a title-less section wouldn't make much sense anyway. Cheers, Daniel [-- Attachment #2: Type: application/pgp-signature, Size: 835 bytes --] ^ permalink raw reply [flat|nested] 20+ messages in thread
* [PATCH v3 2/2] emacs: Tests for user-defined sections 2011-07-05 16:23 ` [PATCH v3 " Daniel Schoepe 2011-07-05 16:23 ` [PATCH v3 1/2] " Daniel Schoepe @ 2011-07-05 16:23 ` Daniel Schoepe 1 sibling, 0 replies; 20+ messages in thread From: Daniel Schoepe @ 2011-07-05 16:23 UTC (permalink / raw) To: notmuch --- test/emacs | 37 ++++++++++++++++++++ test/emacs.expected-output/notmuch-hello | 3 +- .../notmuch-hello-new-section | 4 ++ .../notmuch-hello-no-saved-searches | 3 +- .../notmuch-hello-section-before | 18 +++++++++ .../notmuch-hello-section-counts | 5 +++ | 4 ++ .../notmuch-hello-section-with-empty | 4 ++ .../emacs.expected-output/notmuch-hello-with-empty | 3 +- 9 files changed, 78 insertions(+), 3 deletions(-) create mode 100644 test/emacs.expected-output/notmuch-hello-new-section create mode 100644 test/emacs.expected-output/notmuch-hello-section-before create mode 100644 test/emacs.expected-output/notmuch-hello-section-counts create mode 100644 test/emacs.expected-output/notmuch-hello-section-hidden-tag create mode 100644 test/emacs.expected-output/notmuch-hello-section-with-empty diff --git a/test/emacs b/test/emacs index 53f455a..40e2563 100755 --- a/test/emacs +++ b/test/emacs @@ -34,6 +34,43 @@ test_emacs '(let ((notmuch-saved-searches (test-output))' test_expect_equal_file OUTPUT $EXPECTED/notmuch-hello-no-saved-searches +test_begin_subtest "User defined section with inbox tag" +test_emacs "(let ((notmuch-hello-sections + (list (lambda () (notmuch-hello-insert-searches + \"Test: \" '((\"inbox\" . \"tag:inbox\"))))))) + (notmuch-hello) + (test-output))" +test_expect_equal_file OUTPUT $EXPECTED/notmuch-hello-new-section + +test_begin_subtest "User defined section with empty, hidden entry" +test_emacs "(let ((notmuch-hello-sections + (list (lambda () (notmuch-hello-insert-searches + \"Test-with-empty:\" + '((\"inbox\" . \"tag:inbox\") + (\"doesnotexist\" . \"tag:doesnotexist\")) + :hide-empty-searches t))))) + (notmuch-hello) + (test-output))" +test_expect_equal_file OUTPUT $EXPECTED/notmuch-hello-section-with-empty + +test_begin_subtest "User defined section, unread tag filtered out" +test_emacs "(let ((notmuch-hello-sections + (list (lambda () (notmuch-hello-insert-tags-section + \"Test-with-filtered: \" + :hide-tags '(\"unread\")))))) + (notmuch-hello) + (test-output))" +test_expect_equal_file OUTPUT $EXPECTED/notmuch-hello-section-hidden-tag + +test_begin_subtest "User defined section, different query for counts" +test_emacs "(let ((notmuch-hello-sections + (list (lambda () (notmuch-hello-insert-tags-section + \"Test-with-counts: \" + :make-count \"tag:signed\"))))) + (notmuch-hello) + (test-output))" +test_expect_equal_file OUTPUT $EXPECTED/notmuch-hello-section-counts + test_begin_subtest "Basic notmuch-search view in emacs" test_emacs '(notmuch-search "tag:inbox") (notmuch-test-wait) diff --git a/test/emacs.expected-output/notmuch-hello b/test/emacs.expected-output/notmuch-hello index 64b7e42..9666327 100644 --- a/test/emacs.expected-output/notmuch-hello +++ b/test/emacs.expected-output/notmuch-hello @@ -6,9 +6,10 @@ Saved searches: [edit] Search: -[Show all tags] +All tags: [show] Type a search query and hit RET to view matching threads. Edit saved searches with the `edit' button. Hit RET or click on a saved search or tag name to view matching threads. `=' refreshes this screen. `s' jumps to the search box. `q' to quit. + diff --git a/test/emacs.expected-output/notmuch-hello-new-section b/test/emacs.expected-output/notmuch-hello-new-section new file mode 100644 index 0000000..be7b26a --- /dev/null +++ b/test/emacs.expected-output/notmuch-hello-new-section @@ -0,0 +1,4 @@ +Test: [hide] + + 50 inbox + diff --git a/test/emacs.expected-output/notmuch-hello-no-saved-searches b/test/emacs.expected-output/notmuch-hello-no-saved-searches index 7f8206a..744a8f1 100644 --- a/test/emacs.expected-output/notmuch-hello-no-saved-searches +++ b/test/emacs.expected-output/notmuch-hello-no-saved-searches @@ -2,9 +2,10 @@ Search: -[Show all tags] +All tags: [show] Type a search query and hit RET to view matching threads. Edit saved searches with the `edit' button. Hit RET or click on a saved search or tag name to view matching threads. `=' refreshes this screen. `s' jumps to the search box. `q' to quit. + diff --git a/test/emacs.expected-output/notmuch-hello-section-before b/test/emacs.expected-output/notmuch-hello-section-before new file mode 100644 index 0000000..a5781ce --- /dev/null +++ b/test/emacs.expected-output/notmuch-hello-section-before @@ -0,0 +1,18 @@ + Welcome to notmuch. You have 50 messages. + +Saved searches: [edit] + + 50 inbox 50 unread + +Test-before [hide] + + 4 attachment 7 signed + 50 inbox 50 unread + +Search: + + + Type a search query and hit RET to view matching threads. + Edit saved searches with the `edit' button. + Hit RET or click on a saved search or tag name to view matching threads. + `=' refreshes this screen. `s' jumps to the search box. `q' to quit. diff --git a/test/emacs.expected-output/notmuch-hello-section-counts b/test/emacs.expected-output/notmuch-hello-section-counts new file mode 100644 index 0000000..12d19ed --- /dev/null +++ b/test/emacs.expected-output/notmuch-hello-section-counts @@ -0,0 +1,5 @@ +Test-with-counts: [hide] + + 2 attachment 7 inbox 7 signed + 7 unread + eb21c07 --- /dev/null +++ b/test/emacs.expected-output/notmuch-hello-section-hidden-tag --git a/test/emacs.expected-output/notmuch-hello-section-hidden-tag b/test/emacs.expected-output/notmuch-hello-section-hidden-tag new file mode 100644 index 0000000..@@ -0,0 +1,4 @@ +Test-with-filtered: [hide] + + 4 attachment 50 inbox 7 signed + diff --git a/test/emacs.expected-output/notmuch-hello-section-with-empty b/test/emacs.expected-output/notmuch-hello-section-with-empty new file mode 100644 index 0000000..c5b6623 --- /dev/null +++ b/test/emacs.expected-output/notmuch-hello-section-with-empty @@ -0,0 +1,4 @@ +Test-with-empty:[hide] + + 50 inbox + diff --git a/test/emacs.expected-output/notmuch-hello-with-empty b/test/emacs.expected-output/notmuch-hello-with-empty index a9ed630..e9c8eb9 100644 --- a/test/emacs.expected-output/notmuch-hello-with-empty +++ b/test/emacs.expected-output/notmuch-hello-with-empty @@ -6,9 +6,10 @@ Saved searches: [edit] Search: -[Show all tags] +All tags: [show] Type a search query and hit RET to view matching threads. Edit saved searches with the `edit' button. Hit RET or click on a saved search or tag name to view matching threads. `=' refreshes this screen. `s' jumps to the search box. `q' to quit. + -- 1.7.5.4 ^ permalink raw reply related [flat|nested] 20+ messages in thread
end of thread, other threads:[~2011-07-08 22:03 UTC | newest] Thread overview: 20+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2011-06-29 20:26 [PATCH 0/2] emacs: User-defined sections in notmuch-hello Daniel Schoepe 2011-06-29 20:27 ` [PATCH 1/2] " Daniel Schoepe 2011-06-29 20:27 ` [PATCH 2/2] emacs: Tests for user-defined sections Daniel Schoepe 2011-07-02 13:31 ` [PATCH v2 0/2] emacs: User-defined sections in notmuch-hello Daniel Schoepe 2011-07-02 13:31 ` [PATCH 1/2] " Daniel Schoepe 2011-07-02 13:31 ` [PATCH 2/2] emacs: Tests for user-defined sections Daniel Schoepe 2011-07-05 0:00 ` [PATCH 0/2] emacs: User-defined sections in notmuch-hello Michal Sojka 2011-07-05 8:24 ` Daniel Schoepe 2011-07-05 14:09 ` Michal Sojka 2011-07-05 14:55 ` Daniel Schoepe 2011-07-05 16:43 ` Daniel Schoepe 2011-07-05 16:23 ` [PATCH v3 " Daniel Schoepe 2011-07-05 16:23 ` [PATCH v3 1/2] " Daniel Schoepe 2011-07-06 11:34 ` Michal Sojka 2011-07-06 12:41 ` Daniel Schoepe 2011-07-07 15:23 ` Michal Sojka 2011-07-07 18:25 ` Daniel Schoepe 2011-07-08 21:23 ` Michal Sojka 2011-07-08 22:03 ` Daniel Schoepe 2011-07-05 16:23 ` [PATCH v3 2/2] emacs: Tests for user-defined sections Daniel Schoepe
Code repositories for project(s) associated with this public inbox https://yhetil.org/notmuch.git/ This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox; as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).