* mu4e: cleanup header sorting (and fix the sort-by-human-date case)

This commit is contained in:
djcb 2012-11-11 19:27:55 +02:00
parent 1c1f974807
commit f957a9ca89
3 changed files with 77 additions and 51 deletions

View File

@ -149,16 +149,6 @@ match.
* PARAM-FUNC is function that is evaluated once, and its value is then passed to * PARAM-FUNC is function that is evaluated once, and its value is then passed to
PREDICATE-FUNC as PARAM. This is useful for getting user-input.") PREDICATE-FUNC as PARAM. This is useful for getting user-input.")
(defvar mu4e-headers-sortfield :date
"Field to sort the headers by.
Field must be a symbol, one of: :date, :subject, :size, :prio,
:from, :to.")
(defvar mu4e-headers-sort-revert t
"Whether to revert the sort-order.
i.e. Z>A instead of A>Z. When sorting by date, it's useful to go
from biggest to smallest, so newest messages come first.")
(defvar mu4e-headers-show-threads t (defvar mu4e-headers-show-threads t
"Whether to show threads in the headers list.") "Whether to show threads in the headers list.")
@ -181,7 +171,7 @@ followed by the docid, followed by `mu4e~headers-docid-post'.")
(defvar mu4e~headers-view-win nil (defvar mu4e~headers-view-win nil
"The view window connected to this headers view.") "The view window connected to this headers view.")
(defvar mu4e~headers-sortfield-choices (defvar mu4e~headers-sort-field-choices
'( ("date" . :date) '( ("date" . :date)
("from" . :from) ("from" . :from)
("prio" . :prio) ("prio" . :prio)
@ -353,6 +343,8 @@ date. The formats used for date and time are
(format-time-string mu4e-headers-time-format date) (format-time-string mu4e-headers-time-format date)
(format-time-string mu4e-headers-date-format date)))) (format-time-string mu4e-headers-date-format date))))
;; note: this function is very performance-sensitive ;; note: this function is very performance-sensitive
(defun mu4e~headers-header-handler (msg &optional point) (defun mu4e~headers-header-handler (msg &optional point)
"Create a one line description of MSG in this buffer, at POINT, "Create a one line description of MSG in this buffer, at POINT,
@ -599,9 +591,9 @@ after the end of the search results."
(define-key menumap [previous] '("Previous" . mu4e-headers-prev)) (define-key menumap [previous] '("Previous" . mu4e-headers-prev))
(define-key menumap [sepa4] '("--"))) (define-key menumap [sepa4] '("--")))
map))) map)))
(fset 'mu4e-headers-mode-map mu4e-headers-mode-map) (fset 'mu4e-headers-mode-map mu4e-headers-mode-map)
(defun mu4e~header-line-format () (defun mu4e~header-line-format ()
"Get the format for the header line." "Get the format for the header line."
(cons (cons
@ -617,8 +609,8 @@ after the end of the search results."
(downarrow (if mu4e-use-fancy-chars "" " V")) (downarrow (if mu4e-use-fancy-chars "" " V"))
;; triangle to mark the sorted-by column ;; triangle to mark the sorted-by column
(arrow (arrow
(when (and sortable (eq (car item) mu4e-headers-sortfield)) (when (and sortable (eq (car item) mu4e~headers-sort-field))
(if mu4e-headers-sort-revert downarrow uparrow))) (if (eq mu4e~headers-sort-direction 'descending) downarrow uparrow)))
(name (concat (plist-get info :shortname) arrow)) (name (concat (plist-get info :shortname) arrow))
(map (make-sparse-keymap))) (map (make-sparse-keymap)))
(when sortable (when sortable
@ -629,10 +621,9 @@ after the end of the search results."
(let* ((obj (posn-object (event-start e))) (let* ((obj (posn-object (event-start e)))
(field (field
(and obj (get-text-property 0 'field (car obj))))) (and obj (get-text-property 0 'field (car obj)))))
(if (eq field mu4e-headers-sortfield) ;; "t": if we're already sorted by field, the sort-order is
(setq mu4e-headers-sort-revert (not mu4e-headers-sort-revert)) ;; changed
(setq mu4e-headers-sortfield field))) (mu4e-headers-change-sorting field t)))))
(mu4e-headers-rerun-search))))
(concat (concat
(propertize (propertize
(if width (if width
@ -812,6 +803,10 @@ docid is not found."
(delete-region (line-beginning-position) (line-beginning-position 2))) (delete-region (line-beginning-position) (line-beginning-position 2)))
(unless ignore-missing (unless ignore-missing
(mu4e-error "Cannot find message with docid %S" docid))))) (mu4e-error "Cannot find message with docid %S" docid)))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defun mu4e~headers-search-execute (expr ignore-history) (defun mu4e~headers-search-execute (expr ignore-history)
"Search in the mu database for EXPR, and switch to the output "Search in the mu database for EXPR, and switch to the output
@ -838,8 +833,8 @@ the query history stack."
(mu4e~proc-find (mu4e~proc-find
expr expr
mu4e-headers-show-threads mu4e-headers-show-threads
mu4e-headers-sortfield mu4e~headers-sort-field
mu4e-headers-sort-revert mu4e~headers-sort-direction
(unless mu4e-headers-full-search mu4e-search-results-limit)))) (unless mu4e-headers-full-search mu4e-search-results-limit))))
(defun mu4e~headers-redraw-get-view-window () (defun mu4e~headers-redraw-get-view-window ()
@ -1088,26 +1083,50 @@ the last search expression."
(format "(%s) AND %s" mu4e~headers-last-query filter))) (format "(%s) AND %s" mu4e~headers-last-query filter)))
(defun mu4e-headers-change-sorting (&optional dont-refresh) (defvar mu4e~headers-sort-field :date
"Interactively change the sorting/threading parameters. "Field to sort the headers by.
With prefix-argument, do _not_ refresh the last search with the Field must be a symbol, one of: :date, :subject, :size, :prio,
new parameters." :from, :to.")
(interactive "P")
(let* ((sortfield (defvar mu4e~headers-sort-direction 'descending
(mu4e-read-option "Sortfield: " mu4e~headers-sortfield-choices)) "Direction to sort by; a symbol either `descending' (sorting
(revert Z->A) or `ascending' (sorting A->Z).")
(mu4e-read-option "Direction: "
'(("ascending" . nil) ("descending" . t))))) (defun mu4e-headers-change-sorting (&optional field dir)
"Change the sorting/threading parameters.
FIELD is the field to sort by; DIR is a symbol: either 'ascending,
'descending, 't (meaning: if FIELD is the same as the current
sortfield, change the sort-order) or nil (ask the user)."
(interactive)
(let* ((field
(or field
(mu4e-read-option "Sortfield: " mu4e~headers-sort-field-choices)))
;; note: 'sortable' is either a boolean (meaning: if non-nil, this is
;; sortable field), _or_ another field (meaning: sort by this other field).
(sortable (plist-get (cdr (assoc field mu4e-header-info)) :sortable))
;; error check
(sortable
(if sortable
sortable
(mu4e-error "Not a sortable field")))
(sortfield (if (booleanp sortable) field sortable))
(dir
(case dir
((ascending descending) dir)
;; change the sort order if field = curfield
(t
(if (eq sortfield mu4e~headers-sort-field)
(if (eq mu4e~headers-sort-direction 'ascending)
'descending 'ascending)))
(mu4e-read-option "Direction: "
'(("ascending" . 'ascending) ("descending" . 'descending))))))
(setq (setq
mu4e-headers-sortfield sortfield mu4e~headers-sort-field sortfield
mu4e-headers-sort-revert revert) ;; "descending" means "revert" mu4e~headers-sort-direction dir)
(mu4e-message "Sorting by %s (%s)%s" (mu4e-message "Sorting by %s (%s)"
(symbol-name sortfield) (symbol-name sortfield)
(if revert "descending" "ascending") (symbol-name mu4e~headers-sort-direction))
(if dont-refresh (mu4e-headers-rerun-search)))
" (press 'g' to refresh)" ""))
(unless dont-refresh
(mu4e-headers-rerun-search))))
(defun mu4e-headers-toggle-threading (&optional dont-refresh) (defun mu4e-headers-toggle-threading (&optional dont-refresh)
"Toggle threading on/off for the search results. "Toggle threading on/off for the search results.

View File

@ -317,23 +317,24 @@ In particular, backslashes and double-quotes."
(let ((esc (replace-regexp-in-string "\\\\" "\\\\\\\\" query))) (let ((esc (replace-regexp-in-string "\\\\" "\\\\\\\\" query)))
(replace-regexp-in-string "\"" "\\\\\"" esc))) (replace-regexp-in-string "\"" "\\\\\"" esc)))
(defun mu4e~proc-find (query threads sortfield revert maxnum) (defun mu4e~proc-find (query threads sortfield sortdir maxnum)
"Start a database query for QUERY. "Start a database query for QUERY.
If THREADS is non-nil, show results in threaded fasion, SORTFIELD If THREADS is non-nil, show results in threaded fasion, SORTFIELD
is a symbol describing the field to sort by (or nil); see is a symbol describing the field to sort by (or nil); see
`mu4e~headers-sortfield-choices'. If REVERT is non-nil, sort Z->A `mu4e~headers-sortfield-choices'. If SORT is `descending', sort
instead of A->Z. MAXNUM determines the maximum number of results Z->A, if it's `ascending', sort A->Z. MAXNUM determines the maximum
to return, or nil for 'unlimited'. For each result found, a number of results to return, or nil for 'unlimited'. For each
function is called, depending on the kind of result. The result found, a function is called, depending on the kind of
variables `mu4e-error-func' contain the function that will be result. The variables `mu4e-error-func' contain the function that
called for, resp., a message (header row) or an error." will be called for, resp., a message (header row) or an error."
(mu4e~proc-send-command (mu4e~proc-send-command
"find query:\"%s\" threads:%s sortfield:%s reverse:%s maxnum:%d" "find query:\"%s\" threads:%s sortfield:%s reverse:%s maxnum:%d"
(mu4e~proc-escape-query query) (mu4e~proc-escape-query query)
(if threads "true" "false") (if threads "true" "false")
;; sortfield is e.g. ':subject'; this removes the ':' ;; sortfield is e.g. ':subject'; this removes the ':'
(if (null sortfield) "nil" (substring (symbol-name sortfield) 1)) (if (null sortfield) "nil" (substring (symbol-name sortfield) 1))
(if revert "true" "false") ;; TODO: use ascending/descending in backend too (it's clearer than 'reverse'
(if (eq sortdir 'descending) "true" "false")
(if maxnum maxnum -1))) (if maxnum maxnum -1)))
(defun mu4e~proc-move (docid-or-msgid &optional maildir flags) (defun mu4e~proc-move (docid-or-msgid &optional maildir flags)

View File

@ -492,7 +492,7 @@ I.e. a message with the draft flag set."
( :name "Date" ( :name "Date"
:shortname "Date" :shortname "Date"
:help "Date/time when the message was written." :help "Date/time when the message was written."
:sortable t)) :sortable :date))
(:flags . (:flags .
( :name "Flags" ( :name "Flags"
:shortname "Flgs" :shortname "Flgs"
@ -544,13 +544,19 @@ I.e. a message with the draft flag set."
:help "Recipient of the message" :help "Recipient of the message"
:sortable t))) :sortable t)))
"An alist of all possible header fields and information about them. "An alist of all possible header fields and information about them.
This is used in the UI (the column headers in the header list, This is used in the user-interface (the column headers in the header list, and
and the fields the message view). the fields the message view).
Most fields should be self-explanatory. A special one is Most fields should be self-explanatory. A special one is
`:from-or-to', which is equal to `:from' unless `:from' matches `:from-or-to', which is equal to `:from' unless `:from' matches
`mu4e-user-mail-address-regexp', in which case it will be equal `mu4e-user-mail-address-regexp', in which case it will be equal to
to `:to'.") `:to'.
Furthermore, the property `:sortable' determines whether we can
sort by this field. This can be either a boolean (nil or t), or a
symbol for /another/ field. For example, the `:human-date' field
uses `:date' for that.
")
(defvar mu4e-custom-header-info nil (defvar mu4e-custom-header-info nil
"A list like `mu4e-custom-header-info', but for custom headers. "A list like `mu4e-custom-header-info', but for custom headers.