(require 'notmuch-lib)
(require 'notmuch-mua)
-(declare-function notmuch-search "notmuch" (query &optional oldest-first target-thread target-line continuation))
+(declare-function notmuch-search "notmuch" (&optional query oldest-first target-thread target-line continuation))
(declare-function notmuch-poll "notmuch" ())
(defcustom notmuch-hello-recent-searches-max 10
(defun notmuch-sort-saved-searches (alist)
"Generate an alphabetically sorted saved searches alist."
- (sort alist (lambda (a b) (string< (car a) (car b)))))
+ (sort (copy-sequence alist) (lambda (a b) (string< (car a) (car b)))))
(defcustom notmuch-saved-search-sort-function nil
"Function used to sort the saved searches for the notmuch-hello view.
(defvar notmuch-hello-url "http://notmuchmail.org"
"The `notmuch' web site.")
-(defvar notmuch-hello-search-pos nil
- "Position of search widget, if any.
-
-This should only be set by `notmuch-hello-insert-search'.")
-
(defvar notmuch-hello-custom-section-options
'((:filter (string :tag "Filter for each tag"))
(:filter-count (string :tag "Different filter to generate message counts"))
:tag "Customized queries section (see docstring for details)"
:type
`(list :tag ""
- (const :tag "" notmuch-hello-insert-query-list)
+ (const :tag "" notmuch-hello-insert-searches)
(string :tag "Title for this section")
(repeat :tag "Queries"
(cons (string :tag "Name") (string :tag "Query")))
buffer. A section should not end with an empty line, because a
newline will be inserted after each section by `notmuch-hello'.
-Each function should take no arguments. If the produced section
-includes `notmuch-hello-target' (i.e. cursor should be positioned
-inside this section), the function should return this element's
-position.
-Otherwise, it should return nil.
+Each function should take no arguments. The return value is
+ignored.
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
displayed next to the buttons can be generated by applying a
different filter to the tag query. These filters are also
supported for \"Customized queries section\" items."
- :group 'notmuch
+ :group 'notmuch-hello
:type
'(repeat
(choice (function-item notmuch-hello-insert-header)
notmuch-hello-query-section
(function :tag "Custom section"))))
-(defvar notmuch-hello-target nil
- "Button text at position of point before rebuilding the notmuch-buffer.
-
-This variable contains the text of the button, if any, the
-point was positioned at before the notmuch-hello buffer was
-rebuilt. This should never actually be global and is defined as a
-defvar only for documentation purposes and to avoid a compiler
-warning about it occurring as a free variable.")
-
(defvar notmuch-hello-hidden-sections nil
"List of sections titles whose contents are hidden")
The values :show-empty-searches, :filter and :filter-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 :filter-count)
- (plist-get options :filter)))))))
- (and (or (plist-get options :show-empty-searches) (> message-count 0))
- (list name (notmuch-hello-filtered-query
- (car query-and-count) (plist-get options :filter))
- message-count))))
- query-alist)))
+ (with-temp-buffer
+ (dolist (elem query-alist nil)
+ (let ((count-query (if (consp (cdr elem))
+ ;; do we have a different query for the message count?
+ (third elem)
+ (cdr elem))))
+ (insert
+ (notmuch-hello-filtered-query count-query
+ (or (plist-get options :filter-count)
+ (plist-get options :filter)))
+ "\n")))
+
+ (call-process-region (point-min) (point-max) notmuch-command
+ t t nil "count" "--batch")
+ (goto-char (point-min))
+
+ (notmuch-remove-if-not
+ #'identity
+ (mapcar
+ (lambda (elem)
+ (let ((name (car elem))
+ (search-query (if (consp (cdr elem))
+ ;; do we have a different query for the message count?
+ (second elem)
+ (cdr elem)))
+ (message-count (prog1 (read (current-buffer))
+ (forward-line 1))))
+ (and (or (plist-get options :show-empty-searches) (> message-count 0))
+ (list name (notmuch-hello-filtered-query
+ search-query (plist-get options :filter))
+ message-count))))
+ query-alist))))
(defun notmuch-hello-insert-buttons (searches)
"Insert buttons for SEARCHES.
(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))
+ (column-width (cdr tags-and-width))
+ (column-indent 0)
(count 0)
(reordered-list (notmuch-hello-reflect searches tags-per-line))
;; Hack the display of the buttons used.
(widget-push-button-prefix "")
- (widget-push-button-suffix "")
- (found-target-pos nil))
+ (widget-push-button-suffix ""))
;; dme: It feels as though there should be a better way to
;; implement this loop than using an incrementing counter.
(mapc (lambda (elem)
;; (not elem) indicates an empty slot in the matrix.
(when elem
+ (if (> column-indent 0)
+ (widget-insert (make-string column-indent ? )))
(let* ((name (first elem))
(query (second elem))
- (msg-count (third elem))
- (formatted-name (format "%s " name)))
+ (msg-count (third elem)))
(widget-insert (format "%8s "
(notmuch-hello-nice-number msg-count)))
- (if (string= formatted-name notmuch-hello-target)
- (setq found-target-pos (point-marker)))
(widget-create 'push-button
:notify #'notmuch-hello-widget-search
:notmuch-search-terms query
- formatted-name)
- (unless (eq (% count tags-per-line) (1- tags-per-line))
- ;; If this is not the last tag on the line, insert
- ;; enough space to consume the rest of the column.
- ;; Because the button for the name is `(1+ (length
- ;; name))' long (due to the trailing space) we can
- ;; just insert `(- widest (length name))' spaces - the
- ;; column separator is included in the button if
- ;; `(equal widest (length name)'.
- (widget-insert (make-string (max 1
- (- widest (length name)))
- ? )))))
+ name)
+ (setq column-indent
+ (1+ (max 0 (- column-width (length name)))))))
(setq count (1+ count))
- (if (eq (% count tags-per-line) 0)
- (widget-insert "\n")))
+ (when (eq (% count tags-per-line) 0)
+ (setq column-indent 0)
+ (widget-insert "\n")))
reordered-list)
;; If the last line was not full (and hence did not include a
;; carriage return), insert one now.
(unless (eq (% count tags-per-line) 0)
- (widget-insert "\n"))
- found-target-pos))
+ (widget-insert "\n"))))
(defimage notmuch-hello-logo ((:type png :file "notmuch-logo.png")))
(defun notmuch-hello-generate-tag-alist (&optional hide-tags)
"Return an alist from tags to queries to display in the all-tags section."
(mapcar (lambda (tag)
- (cons tag (format "tag:%s" tag)))
+ (cons tag (concat "tag:" (notmuch-escape-boolean-term tag))))
(notmuch-remove-if-not
(lambda (tag)
(not (member tag hide-tags)))
- (process-lines notmuch-command "search-tags"))))
+ (process-lines notmuch-command "search" "--output=tags" "*"))))
(defun notmuch-hello-insert-header ()
"Insert the default notmuch-hello header."
(funcall notmuch-saved-search-sort-function
notmuch-saved-searches)
notmuch-saved-searches)
- :show-empty-searches notmuch-show-empty-saved-searches))
- found-target-pos)
+ :show-empty-searches notmuch-show-empty-saved-searches)))
(when searches
(widget-insert "Saved searches: ")
(widget-create 'push-button
"edit")
(widget-insert "\n\n")
(let ((start (point)))
- (setq found-target-pos
- (notmuch-hello-insert-buttons searches))
- (indent-rigidly start (point) notmuch-hello-indent)
- found-target-pos))))
+ (notmuch-hello-insert-buttons searches)
+ (indent-rigidly start (point) notmuch-hello-indent)))))
(defun notmuch-hello-insert-search ()
"Insert a search widget."
(widget-insert "Search: ")
- (setq notmuch-hello-search-pos (point-marker))
(widget-create 'editable-field
;; Leave some space at the start and end of the
;; search boxes.
(widget-insert "Recent searches: ")
(widget-create 'push-button
:notify (lambda (&rest ignore)
- (setq notmuch-search-history nil)
- (notmuch-hello-update))
+ (when (y-or-n-p "Are you sure you want to clear the searches? ")
+ (setq notmuch-search-history nil)
+ (notmuch-hello-update)))
"clear")
(widget-insert "\n\n")
(let ((start (point)))
(notmuch-hello-update))
"hide"))
(widget-insert "\n")
- (let (target-pos)
- (when (not is-hidden)
- (let ((searches (apply 'notmuch-hello-query-counts query-alist options)))
- (when (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)))
+ (when (not is-hidden)
+ (let ((searches (apply 'notmuch-hello-query-counts query-alist options)))
+ (when (or (not (plist-get options :hide-if-empty))
+ searches)
+ (widget-insert "\n")
+ (notmuch-hello-insert-buttons searches)
+ (indent-rigidly start (point) notmuch-hello-indent))))))
(defun notmuch-hello-insert-tags-section (&optional title &rest options)
"Insert a section displaying all tags with message counts.
"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-saved-searches
(notmuch-hello-generate-tag-alist))
:filter "tag:inbox"))
"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.
- (unless notmuch-saved-searches
- (setq notmuch-saved-searches (notmuch-saved-searches)))
-
(if no-display
(set-buffer "*notmuch-hello*")
(switch-to-buffer "*notmuch-hello*"))
- (let ((notmuch-hello-target (if (widget-at)
- (widget-value (widget-at))
- (condition-case nil
- (progn
- (widget-forward 1)
- (widget-value (widget-at)))
- (error nil))))
+ (let ((target-line (line-number-at-pos))
+ (target-column (current-column))
(inhibit-read-only t))
;; Delete all editable widget fields. Editable widget fields are
(mapc 'delete-overlay (car all))
(mapc 'delete-overlay (cdr all)))
- (let (final-target-pos)
- (mapc
- (lambda (section)
- (let ((point-before (point))
- (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))
- ;; don't insert a newline when the previous section didn't show
- ;; anything.
- (unless (eq (point) point-before)
- (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)
- (when notmuch-hello-search-pos
- (goto-char notmuch-hello-search-pos)))))
+ (mapc
+ (lambda (section)
+ (let ((point-before (point)))
+ (if (functionp section)
+ (funcall section)
+ (apply (car section) (cdr section)))
+ ;; don't insert a newline when the previous section didn't
+ ;; show anything.
+ (unless (eq (point) point-before)
+ (widget-insert "\n"))))
+ notmuch-hello-sections)
+ (widget-setup)
+
+ ;; Move point back to where it was before refresh. Use line and
+ ;; column instead of point directly to be insensitive to additions
+ ;; and removals of text within earlier lines.
+ (goto-char (point-min))
+ (forward-line (1- target-line))
+ (move-to-column target-column))
(run-hooks 'notmuch-hello-refresh-hook)
(setq notmuch-hello-first-run nil))