mu4e-search: split off search functionality in minor-mode

Split off the search functionality from mu4e-headers.el into a new
mu4e-search.el.

Clean up things a bit and create a minor mode in which to add the keybindings.

Enable this in main/headers/view.
This commit is contained in:
Dirk-Jan C. Binnema 2021-08-28 22:21:00 +03:00
parent 7d17b324ad
commit 3cd127d8ae
4 changed files with 498 additions and 320 deletions

View File

@ -39,6 +39,7 @@
(require 'mu4e-vars)
(require 'mu4e-mark)
(require 'mu4e-context)
(require 'mu4e-search)
(require 'mu4e-compose)
(require 'mu4e-actions)
(require 'mu4e-message)
@ -121,27 +122,6 @@ indexing operation showed changes."
:type 'boolean
:group 'mu4e-headers)
(defcustom mu4e-headers-results-limit 500
"Maximum number of results to show; this affects performance
quite a bit, especially when `mu4e-headers-include-related' is
non-nil. Set to -1 for no limits, and you temporarily (for one
query) ignore the limit by pressing a C-u before invoking the
search.
Note that there are a few complications when
`mu4e-headers-include-related' is enabled: mu performs *two*
queries; the first one with this limit set, and then a second
(unlimited) query for all messages that are related to the first
matches. We then limit this second result as well, favoring the
messages that were found in the first set (the \"leaders\").
"
:type '(choice (const :tag "Unlimited" -1)
(integer :tag "Limit"))
:group 'mu4e-headers)
(make-obsolete-variable 'mu4e-search-results-limit
'mu4e-headers-results-limit "0.9.9.5-dev6")
(defcustom mu4e-headers-advance-after-mark t
"With this option set to non-nil, automatically advance to the
next mail after marking a message in header view."
@ -187,33 +167,6 @@ query have been received and are displayed."
:type 'hook
:group 'mu4e-headers)
(defcustom mu4e-headers-search-bookmark-hook nil
"Hook run just after we invoke a bookmarked search. This
function receives the query as its parameter, before any
rewriting as per `mu4e-query-rewrite-function' has taken place.
The reason to use this instead of `mu4e-headers-search-hook' is
if you only want to execute a hook when a search is entered via a
bookmark, e.g. if you'd like to treat the bookmarks as a custom
folder and change the options for the search, e.g.
`mu4e-headers-show-threads', `mu4e-headers-include-related',
`mu4e-headers-skip-duplicates` or `mu4e-headers-results-limit'.
"
:type 'hook
:group 'mu4e-headers)
(defcustom mu4e-headers-search-hook nil
"Hook run just before executing a new search operation. This
function receives the query as its parameter, before any
rewriting as per `mu4e-query-rewrite-function' has taken place
This is a more general hook facility than the
`mu4e-headers-search-bookmark-hook'. It gets called on every
executed search, not just those that are invoked via bookmarks,
but also manually invoked searches."
:type 'hook
:group 'mu4e-headers)
;;; Public variables
(defvar mu4e-headers-sort-field :date
@ -309,14 +262,6 @@ and (optionally) PARAM, and should return non-nil when there's a
match.
* 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.")
(defvar mu4e-headers-show-threads t
"Whether to show threads in the headers list.")
(defvar mu4e-headers-full-search nil
"Whether to show all results.
If this is nil show results up to `mu4e-headers-results-limit')")
;;; Internal variables/constants
;; docid cookies
@ -795,6 +740,43 @@ if provided, or at the end of the buffer otherwise."
(mu4e~headers-add-header line (mu4e-message-field msg :docid)
point msg))))))
;;; Performing queries (internal)
(defun mu4e--search-execute (expr ignore-history)
"Search for query EXPR.
Switch to the output buffer for the results. If IGNORE-HISTORY is
true, do *not* update the query history stack."
(let* ((buf (get-buffer-create mu4e~headers-buffer-name))
(inhibit-read-only t)
(rewritten-expr (funcall mu4e-query-rewrite-function expr))
(maxnum (unless mu4e-search-full mu4e-search-results-limit)))
(with-current-buffer buf
(mu4e-headers-mode)
(unless ignore-history
;; save the old present query to the history list
(when mu4e--search-last-query
(mu4e--search-push-query mu4e--search-last-query 'past)))
(setq mu4e--search-last-query rewritten-expr)
(mu4e~headers-update-mode-line))
;; when the buffer is already visible, select it; otherwise,
;; switch to it.
(unless (get-buffer-window buf 0)
(switch-to-buffer buf))
(run-hook-with-args 'mu4e-search-hook expr)
(mu4e~headers-clear mu4e~search-message)
(mu4e~proc-find
rewritten-expr
mu4e-headers-show-threads
mu4e-headers-sort-field
mu4e-headers-sort-direction
maxnum
mu4e-headers-skip-duplicates
mu4e-headers-include-related)))
(defconst mu4e~search-message "Searching...")
(defconst mu4e~no-matches "No matching messages found")
(defconst mu4e~end-of-results "End of search results")
@ -888,20 +870,8 @@ after the end of the search results."
;; for terminal users
(define-key map (kbd "C-c C-u") 'mu4e-update-mail-and-index)
(define-key map "s" 'mu4e-headers-search)
(define-key map "S" 'mu4e-headers-search-edit)
(define-key map "/" 'mu4e-headers-search-narrow)
(define-key map "j" 'mu4e~headers-jump-to-maildir)
(define-key map (kbd "<M-left>") 'mu4e-headers-query-prev)
(define-key map (kbd "\\") 'mu4e-headers-query-prev)
(define-key map (kbd "<M-right>") 'mu4e-headers-query-next)
(define-key map "b" 'mu4e-headers-search-bookmark)
(define-key map "B" 'mu4e-headers-search-bookmark-edit)
(define-key map "O" 'mu4e-headers-change-sorting)
(define-key map "P" 'mu4e-headers-toggle-threading)
(define-key map "Q" 'mu4e-headers-toggle-full-search)
@ -1121,7 +1091,7 @@ no user-interaction ongoing."
;; otherwise we'd trigger a headers view from out of nowhere.
(when (and (buffer-live-p (mu4e-get-headers-buffer))
(window-live-p (get-buffer-window (mu4e-get-headers-buffer) t)))
(mu4e-headers-rerun-search))))
(mu4e-search-rerun))))
(define-derived-mode mu4e-headers-mode special-mode
"mu4e:headers"
@ -1145,6 +1115,7 @@ no user-interaction ongoing."
(mu4e~mark-initialize) ;; initialize the marking subsystem
(mu4e-context-minor-mode)
(mu4e-search-minor-mode)
(hl-line-mode 1))
(defun mu4e~headers-index-updated-hook-fn ()
@ -1256,7 +1227,7 @@ docid is not found."
(name "mu4e-headers"))
(setq mode-name name)
(setq mu4e~headers-mode-line-label (concat flagstr " " mu4e~headers-last-query))
(setq mu4e~headers-mode-line-label (concat flagstr " " mu4e--search-last-query))
(make-local-variable 'global-mode-string)
@ -1275,41 +1246,6 @@ docid is not found."
""))))))
(defun mu4e~headers-search-execute (expr ignore-history)
"Search in the mu database for EXPR, and switch to the output
buffer for the results. If IGNORE-HISTORY is true, do *not* update
the query history stack."
;; note: we don't want to update the history if this query comes from
;; `mu4e~headers-query-next' or `mu4e~headers-query-prev'.
;;(mu4e-hide-other-mu4e-buffers)
(let* ((buf (get-buffer-create mu4e~headers-buffer-name))
(inhibit-read-only t)
(rewritten-expr (funcall mu4e-query-rewrite-function expr))
(maxnum (unless mu4e-headers-full-search mu4e-headers-results-limit)))
(with-current-buffer buf
(mu4e-headers-mode)
(unless ignore-history
;; save the old present query to the history list
(when mu4e~headers-last-query
(mu4e~headers-push-query mu4e~headers-last-query 'past)))
(setq mu4e~headers-last-query rewritten-expr)
(mu4e~headers-update-mode-line))
;; when the buffer is already visible, select it; otherwise,
;; switch to it.
(unless (get-buffer-window buf 0)
(switch-to-buffer buf))
(run-hook-with-args 'mu4e-headers-search-hook expr)
(mu4e~headers-clear mu4e~search-message)
(mu4e~proc-find
rewritten-expr
mu4e-headers-show-threads
mu4e-headers-sort-field
mu4e-headers-sort-direction
maxnum
mu4e-headers-skip-duplicates
mu4e-headers-include-related)))
(defun mu4e~headers-redraw-get-view-window ()
"Close all windows, redraw the headers buffer based on the value
of `mu4e-split-view', and return a window for the message view."
@ -1503,166 +1439,8 @@ descendants."
(let ((current-prefix-arg t))
(call-interactively 'mu4e-headers-mark-thread))))
;;; The query past / present / future
(defvar mu4e~headers-query-past nil
"Stack of queries before the present one.")
(defvar mu4e~headers-query-future nil
"Stack of queries after the present one.")
(defvar mu4e~headers-query-stack-size 20
"Maximum size for the query stacks.")
(defun mu4e~headers-push-query (query where)
"Push QUERY to one of the query stacks.
WHERE is a symbol telling us where to push; it's a symbol, either
'future or 'past. Functional also removes duplicates, limits the
stack size."
(let ((stack
(cl-case where
(past mu4e~headers-query-past)
(future mu4e~headers-query-future))))
;; only add if not the same item
(unless (and stack (string= (car stack) query))
(push query stack)
;; limit the stack to `mu4e~headers-query-stack-size' elements
(when (> (length stack) mu4e~headers-query-stack-size)
(setq stack (cl-subseq stack 0 mu4e~headers-query-stack-size)))
;; remove all duplicates of the new element
(cl-remove-if (lambda (elm) (string= elm (car stack))) (cdr stack))
;; update the stacks
(cl-case where
(past (setq mu4e~headers-query-past stack))
(future (setq mu4e~headers-query-future stack))))))
(defun mu4e~headers-pop-query (whence)
"Pop a query from the stack.
WHENCE is a symbol telling us where to get it from, either `future'
or `past'."
(cl-case whence
(past
(unless mu4e~headers-query-past
(mu4e-warn "No more previous queries"))
(pop mu4e~headers-query-past))
(future
(unless mu4e~headers-query-future
(mu4e-warn "No more next queries"))
(pop mu4e~headers-query-future))))
;;; Reading queries with completion
(defvar mu4e-minibuffer-search-query-map
(let ((map (copy-keymap minibuffer-local-map)))
(define-key map (kbd "TAB") #'completion-at-point)
map)
"The keymap when reading a search query.")
(defun mu4e-read-query (prompt &optional initial-input)
"Read a search query with completion using PROMPT and INITIAL-INPUT."
(minibuffer-with-setup-hook
(lambda ()
(setq-local completion-at-point-functions
#'mu4e~search-query-competion-at-point)
(use-local-map mu4e-minibuffer-search-query-map))
(read-string prompt initial-input 'mu4e~headers-search-hist)))
(defvar mu4e~headers-search-hist nil
"History list of searches.")
(defconst mu4e~search-query-keywords
'("and" "or" "not"
"from:" "to:" "cc:" "bcc:" "contact:" "date:" "subject:" "body:"
"list:" "maildir:" "flag:" "mime:" "file:" "prio:" "tag:" "msgid:"
"size:" "embed:"))
(defun mu4e~search-query-competion-at-point ()
(cond
((not (looking-back "[:\"][^ \t]*" nil))
(let ((bounds (bounds-of-thing-at-point 'word)))
(list (or (car bounds) (point))
(or (cdr bounds) (point))
mu4e~search-query-keywords)))
((looking-back "flag:\\(\\w*\\)" nil)
(list (match-beginning 1)
(match-end 1)
'("attach" "draft" "flagged" "list" "new" "passed" "replied"
"seen" "trashed" "unread" "encrypted" "signed")))
((looking-back "maildir:\\([a-zA-Z0-9/.]*\\)" nil)
(list (match-beginning 1)
(match-end 1)
(mu4e-get-maildirs)))
((looking-back "prio:\\(\\w*\\)" nil)
(list (match-beginning 1)
(match-end 1)
(list "high" "normal" "low")))
((looking-back "mime:\\([a-zA-Z0-9/-]*\\)" nil)
(list (match-beginning 1)
(match-end 1)
(mailcap-mime-types)))))
;;; Interactive functions
(defun mu4e-headers-search (&optional expr prompt edit
ignore-history msgid show)
"Search in the mu database for EXPR, and switch to the output
buffer for the results. This is an interactive function which ask
user for EXPR. PROMPT, if non-nil, is the prompt used by this
function (default is \"Search for:\"). If EDIT is non-nil,
instead of executing the query for EXPR, let the user edit the
query before executing it. If IGNORE-HISTORY is true, do *not*
update the query history stack. If MSGID is non-nil, attempt to
move point to the first message with that message-id after
searching. If SHOW is non-nil, show the message with MSGID."
;; note: we don't want to update the history if this query comes from
;; `mu4e~headers-query-next' or `mu4e~headers-query-prev'."
(interactive)
(let* ((prompt (mu4e-format (or prompt "Search for: ")))
(expr
(if (or (null expr) edit)
(mu4e-read-query prompt expr)
expr)))
(mu4e-mark-handle-when-leaving)
(mu4e~headers-search-execute expr ignore-history)
(setq mu4e~headers-msgid-target msgid
mu4e~headers-view-target show)))
(defun mu4e-headers-search-edit ()
"Edit the last search expression."
(interactive)
(mu4e-headers-search mu4e~headers-last-query nil t))
(defun mu4e-headers-search-bookmark (&optional expr edit)
"Search using some bookmarked query EXPR.
If EDIT is non-nil, let the user edit the bookmark before starting
the search."
(interactive)
(let ((expr
(or expr
(mu4e-ask-bookmark (if edit "Select bookmark: " "Bookmark: ")))))
(run-hook-with-args 'mu4e-headers-search-bookmark-hook expr)
(mu4e-headers-search expr (when edit "Edit bookmark: ") edit)))
(defun mu4e-headers-search-bookmark-edit ()
"Edit an existing bookmark before executing it."
(interactive)
(mu4e-headers-search-bookmark nil t))
(defun mu4e-headers-search-narrow (filter )
"Narrow the last search by appending search expression FILTER to
the last search expression. Note that you can go back to previous
query (effectively, 'widen' it), with `mu4e-headers-query-prev'."
(interactive
(let ((filter
(read-string (mu4e-format "Narrow down to: ")
nil 'mu4e~headers-search-hist nil t)))
(list filter)))
(unless mu4e~headers-last-query
(mu4e-warn "There's nothing to filter"))
(mu4e-headers-search
(format "(%s) AND (%s)" mu4e~headers-last-query filter)))
(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,
@ -1698,7 +1476,7 @@ sortfield, change the sort-order) or nil (ask the user)."
(mu4e-message "Sorting by %s (%s)"
(symbol-name sortfield)
(symbol-name mu4e-headers-sort-direction))
(mu4e-headers-rerun-search)))
(mu4e-search-rerun)))
(defun mu4e~headers-toggle (name togglevar dont-refresh)
"Toggle variable TOGGLEVAR for feature NAME. Unless DONT-REFRESH is non-nil,
@ -1710,7 +1488,7 @@ re-run the last search."
(if dont-refresh
" (press 'g' to refresh)" ""))
(unless dont-refresh
(mu4e-headers-rerun-search)))
(mu4e-search-rerun)))
(defun mu4e-headers-toggle-threading (&optional dont-refresh)
"Toggle `mu4e-headers-show-threads'. With prefix-argument, do
@ -1792,42 +1570,6 @@ window . "
;; (mu4e~proc-view dowcid decrypt))
(mu4e~proc-view docid mark-as-read decrypt verify)))
(defun mu4e-headers-rerun-search ()
"Rerun the search for the last search expression."
(interactive)
;; if possible, try to return to the same message
(let* ((msg (mu4e-message-at-point t))
(msgid (and msg (mu4e-message-field msg :message-id))))
(mu4e-headers-search mu4e~headers-last-query nil nil t msgid)))
(defun mu4e~headers-query-navigate (whence)
"Execute the previous query from the query stacks.
WHENCE determines where the query is taken from and is a symbol,
either `future' or `past'."
(let ((query (mu4e~headers-pop-query whence))
(where (if (eq whence 'future) 'past 'future)))
(when query
(mu4e~headers-push-query mu4e~headers-last-query where)
(mu4e-headers-search query nil nil t))))
(defun mu4e-headers-query-next ()
"Execute the previous query from the query stacks."
(interactive)
(mu4e~headers-query-navigate 'future))
(defun mu4e-headers-query-prev ()
"Execute the previous query from the query stacks."
(interactive)
(mu4e~headers-query-navigate 'past))
;; forget the past so we don't repeat it :/
(defun mu4e-headers-forget-queries ()
"Forget all the complete query history."
(interactive)
(setq ;; note: don't forget the present one
mu4e~headers-query-past nil
mu4e~headers-query-future nil)
(mu4e-message "Query history cleared"))
(defun mu4e~headers-move (lines)
"Move point LINES lines forward (if LINES is positive) or
@ -1917,9 +1659,9 @@ given, offer to edit the search query before executing it."
(list maildir current-prefix-arg)))
(when maildir
(let* ((query (format "maildir:\"%s\"" maildir))
(query (if edit (mu4e-read-query "Refine query: " query) query)))
(query (if edit (mu4e-search-read-query "Refine query: " query) query)))
(mu4e-mark-handle-when-leaving)
(mu4e-headers-search query))))
(mu4e-search query))))
(defun mu4e-headers-split-view-grow (&optional n)
"In split-view, grow the headers window.

View File

@ -27,7 +27,9 @@
(require 'smtpmail) ;; the queueing stuff (silence elint)
(require 'mu4e-utils) ;; utility functions
(require 'mu4e-context) ;; the context
(require 'mu4e-search)
(require 'mu4e-vars) ;; mu-wide variables
(require 'cl-lib)
;;; Mode
@ -50,10 +52,6 @@ no unread messages.")
(defvar mu4e-main-mode-map
(let ((map (make-sparse-keymap)))
(define-key map "b" 'mu4e-headers-search-bookmark)
(define-key map "B" 'mu4e-headers-search-bookmark-edit)
(define-key map "s" 'mu4e-headers-search)
(define-key map "q" 'mu4e-quit)
(define-key map "j" 'mu4e~headers-jump-to-maildir)
(define-key map "C" 'mu4e-compose-new)
@ -86,6 +84,7 @@ no unread messages.")
(setq truncate-lines t
overwrite-mode 'overwrite-mode-binary)
(mu4e-context-minor-mode)
(mu4e-search-minor-mode)
(set (make-local-variable 'revert-buffer-function) #'mu4e~main-view-real))
@ -165,7 +164,7 @@ clicked."
"Return a string of maildirs with their counts."
(cl-loop with mds = (mu4e~maildirs-with-query)
with longest = (mu4e~longest-of-maildirs-and-bookmarks)
with queries = (plist-get mu4e~server-props :queries)
with queries = (plist-get mu4e--server-props :queries)
for m in mds
for key = (string (plist-get m :key))
for name = (plist-get m :name)
@ -276,7 +275,7 @@ When REFRESH is non nil refresh infos from server."
(mu4e~key-val "database-path" (mu4e-database-path))
(mu4e~key-val "maildir" (mu4e-root-maildir))
(mu4e~key-val "in store"
(format "%d" (plist-get mu4e~server-props :doccount)) "messages")
(format "%d" (plist-get mu4e--server-props :doccount)) "messages")
(if mu4e-main-hide-personal-addresses ""
(mu4e~key-val "personal addresses" (if addrs (mapconcat #'identity addrs ", " ) "none"))))

446
mu4e/mu4e-search.el Normal file
View File

@ -0,0 +1,446 @@
;;; mu4e-search.el -- part of mu4e -*- lexical-binding: t -*-
;; Copyright (C) 2021 Dirk-Jan C. Binnema
;; Author: Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
;; Maintainer: Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
;; This file is not part of GNU Emacs.
;; mu4e is free software: you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.
;; mu4e is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.
;; You should have received a copy of the GNU General Public License
;; along with mu4e. If not, see <http://www.gnu.org/licenses/>.
;;; Commentary:
;; Search-related functions and a minor-mode.
;;; Code:
(require 'seq)
(require 'cl-lib)
(require 'mu4e-helpers)
;;; Configuration
(defgroup mu4e-search nil
"Search-related settings."
:group 'mu4e)
(defcustom mu4e-search-results-limit 500
"Maximum number of results to show.
This affects performance, especially when
`mu4e-summary-include-related' is non-nil.
Set to -1 for no limits."
:type '(choice (const :tag "Unlimited" -1)
(integer :tag "Limit"))
:group 'mu4e-search)
(define-obsolete-variable-alias 'mu4e-headers-results-limit
'mu4e-search-results-limit "1.7.0")
(defvar mu4e-search-full nil
"Whether to search for all results.
If this is nil, search for up to `mu4e-search-results-limit')")
(define-obsolete-variable-alias 'mu4e-headers-full-search
'mu4e-search-full "1.7.0")
(defvar mu4e-search-threads t
"Whether to calculate threads for the search results.")
(define-obsolete-variable-alias 'mu4e-headers-show-threads
'mu4e-search-threads "1.7.0")
(defcustom mu4e-search-query-rewrite-function 'identity
"Function to rewrite a query.
It takes a search expression string, and returns a possibly
changed search expression string.
This function is applied on the search expression just before
searching, and allows users to modify the query.
For instance, we could change and of workmail into
\"maildir:/long-path-to-work-related-emails\", by setting the function
(setq mu4e-query-rewrite-function
(lambda(expr)
(replace-regexp-in-string \"workmail\"
\"maildir:/long-path-to-work-related-emails\" expr)))
It is good to remember that the replacement does not understand
anything about the query, it just does text replacement."
:type 'function
:group 'mu4e-search)
(define-obsolete-variable-alias 'mu4e-query-rewrite-function
'mu4e-search-query-rewrite-function "1.7.0")
(defcustom mu4e-search-bookmark-hook nil
"Hook run just after invoking a bookmarked search.
This function receives the query as its parameter, before any
rewriting as per `mu4e-query-rewrite-function' has taken place.
The reason to use this instead of `mu4e-headers-search-hook' is
if you only want to execute a hook when a search is entered via a
bookmark, e.g. if you'd like to treat the bookmarks as a custom
folder and change the options for the search."
:type 'hook
:group 'mu4e-search)
(define-obsolete-variable-alias
'mu4e-headers-search-bookmark-hook
'mu4e-search-bookmark-hook "1.7.0")
(defcustom mu4e-search-hook nil
"Hook run just before executing a new search operation.
This function receives the query as its parameter, before any
rewriting as per `mu4e-query-rewrite-function' has taken place
This is a more general hook facility than the
`mu4e-search-bookmark-hook'. It gets called on every
executed search, not just those that are invoked via bookmarks,
but also manually invoked searches."
:type 'hook
:group 'mu4e-search)
(define-obsolete-variable-alias 'mu4e-headers-search-hook
'mu4e-search-hook "1.7.0")
;;; Interactive functions
(defun mu4e-search (&optional expr prompt edit ignore-history msgid show)
"Search for query EXPR.
Switch to the output buffer for the results. This is an
interactive function which ask user for EXPR. PROMPT, if non-nil,
is the prompt used by this function (default is \"Search for:\").
If EDIT is non-nil, instead of executing the query for EXPR, let
the user edit the query before executing it.
If IGNORE-HISTORY is true, do *not* update the query history
stack. If MSGID is non-nil, attempt to move point to the first
message with that message-id after searching. If SHOW is non-nil,
show the message with MSGID."
(interactive)
(let* ((prompt (mu4e-format (or prompt "Search for: ")))
(expr
(if (or (null expr) edit)
(mu4e-read-query prompt expr)
expr)))
(mu4e-mark-handle-when-leaving)
(mu4e--search-execute expr ignore-history)
(setq mu4e~headers-msgid-target msgid
mu4e~headers-view-target show)))
(define-obsolete-function-alias 'mu4e-headers-search 'mu4e-search "1.7.0")
(defun mu4e-search-edit ()
"Edit the last search expression."
(interactive)
(mu4e-search mu4e--search-last-query nil t))
(define-obsolete-variable-alias 'mu4e-headers-search-edit
'mu4e-search-edit "1.7.0")
(defun mu4e-search-bookmark (&optional expr edit)
"Search using some bookmarked query EXPR.
If EDIT is non-nil, let the user edit the bookmark before starting
the search."
(interactive)
(let ((expr
(or expr
(mu4e-ask-bookmark (if edit "Select bookmark: " "Bookmark: ")))))
(run-hook-with-args 'mu4e-search-bookmark-hook expr)
(mu4e-search expr (when edit "Edit bookmark: ") edit)))
(define-obsolete-function-alias 'mu4e-headers-search-bookmark
'mu4e-search-bookmark "1.7.0")
(defun mu4e-search-bookmark-edit ()
"Edit an existing bookmark before executing it."
(interactive)
(mu4e-search-bookmark nil t))
(define-obsolete-function-alias 'mu4e-headers-search-bookmark-edit
'mu4e-search-bookmark-edit "1.7.0")
(defun mu4e-search-narrow(&optional filter)
"Narrow the last search.
Do so by appending search expression FILTER to the last search
expression. Note that you can go back to previous
query (effectively, 'widen' it), with `mu4e-search-prev'."
(interactive
(let ((filter
(read-string (mu4e-format "Narrow down to: ")
nil 'mu4e~headers-search-hist nil t)))
(list filter)))
(unless mu4e--search-last-query
(mu4e-warn "There's nothing to filter"))
(mu4e-headers-search
(format "(%s) AND (%s)" mu4e--search-last-query filter)))
(define-obsolete-function-alias 'mu4e-headers-search-narrow
'mu4e-search-narrow "1.7.0")
;; (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
;; (cl-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)
;; 'descending))
;; (mu4e-read-option "Direction: "
;; '(("ascending" . 'ascending) ("descending" . 'descending))))))
;; (setq
;; mu4e-headers-sort-field sortfield
;; mu4e-headers-sort-direction dir)
;; (mu4e-message "Sorting by %s (%s)"
;; (symbol-name sortfield)
;; (symbol-name mu4e-headers-sort-direction))
;; (mu4e-headers-rerun-search)))
;; (defun mu4e~headers-toggle (name togglevar dont-refresh)
;; "Toggle variable TOGGLEVAR for feature NAME. Unless DONT-REFRESH is non-nil,
;; re-run the last search."
;; (set togglevar (not (symbol-value togglevar)))
;; (mu4e-message "%s turned %s%s"
;; name
;; (if (symbol-value togglevar) "on" "off")
;; (if dont-refresh
;; " (press 'g' to refresh)" ""))
;; (unless dont-refresh
;; (mu4e-headers-rerun-search)))
;; (defun mu4e-headers-toggle-threading (&optional dont-refresh)
;; "Toggle `mu4e-headers-show-threads'. With prefix-argument, do
;; _not_ refresh the last search with the new setting for threading."
;; (interactive "P")
;; (mu4e~headers-toggle "Threading" 'mu4e-headers-show-threads dont-refresh))
;; (defun mu4e-headers-toggle-full-search (&optional dont-refresh)
;; "Toggle `mu4e-headers-full-search'. With prefix-argument, do
;; _not_ refresh the last search with the new setting for threading."
;; (interactive "P")
;; (mu4e~headers-toggle "Full-search"
;; 'mu4e-headers-full-search dont-refresh))
;; (defun mu4e-headers-toggle-include-related (&optional dont-refresh)
;; "Toggle `mu4e-headers-include-related'. With prefix-argument, do
;; _not_ refresh the last search with the new setting for threading."
;; (interactive "P")
;; (mu4e~headers-toggle "Include-related"
;; 'mu4e-headers-include-related dont-refresh))
;; (defun mu4e-headers-toggle-skip-duplicates (&optional dont-refresh)
;; "Toggle `mu4e-headers-skip-duplicates'. With prefix-argument, do
;; _not_ refresh the last search with the new setting for threading."
;; (interactive "P")
;; (mu4e~headers-toggle "Skip-duplicates"
;; 'mu4e-headers-skip-duplicates dont-refresh))
;;; History
(defvar mu4e--search-last-query nil
"The present (most recent) query.")
(defvar mu4e--search-query-past nil
"Stack of queries before the present one.")
(defvar mu4e--search-query-future nil
"Stack of queries after the present one.")
(defvar mu4e--search-query-stack-size 20
"Maximum size for the query stacks.")
(defun mu4e--search-push-query (query where)
"Push QUERY to one of the query stacks.
WHERE is a symbol telling us where to push; it's a symbol, either
'future or 'past. Functional also removes duplicates, limits the
stack size."
(let ((stack
(cl-case where
(past mu4e--search-query-past)
(future mu4e--search-query-future))))
;; only add if not the same item
(unless (and stack (string= (car stack) query))
(push query stack)
;; limit the stack to `mu4e--search-query-stack-size' elements
(when (> (length stack) mu4e--search-query-stack-size)
(setq stack (cl-subseq stack 0 mu4e--search-query-stack-size)))
;; remove all duplicates of the new element
(cl-remove-if (lambda (elm) (string= elm (car stack))) (cdr stack))
;; update the stacks
(cl-case where
(past (setq mu4e--search-query-past stack))
(future (setq mu4e--search-query-future stack))))))
(defun mu4e--search-pop-query (whence)
"Pop a query from the stack.
WHENCE is a symbol telling us where to get it from, either `future'
or `past'."
(cl-case whence
(past
(unless mu4e--search-query-past
(mu4e-warn "No more previous queries"))
(pop mu4e--search-query-past))
(future
(unless mu4e--search-query-future
(mu4e-warn "No more next queries"))
(pop mu4e--search-query-future))))
(defun mu4e-search-rerun ()
"Re-run the search for the last search expression."
(interactive)
;; if possible, try to return to the same message
(let* ((msg (mu4e-message-at-point t))
(msgid (and msg (mu4e-message-field msg :message-id))))
(mu4e-headers-search mu4e~headers-last-query nil nil t msgid)))
(define-obsolete-function-alias 'mu4e-headers-rerun-search
'mu4e-search-rerun "1.7.0")
(defun mu4e--search-query-navigate (whence)
"Execute the previous query from the query stacks.
WHENCE determines where the query is taken from and is a symbol,
either `future' or `past'."
(let ((query (mu4e--search-pop-query whence))
(where (if (eq whence 'future) 'past 'future)))
(when query
(mu4e--search-push-query mu4e--search-last-query where)
(mu4e-search query nil nil t))))
(defun mu4e-search-next ()
"Execute the next query from the query stack."
(interactive)
(mu4e--search-query-navigate 'future))
(define-obsolete-function-alias 'mu4e-headers-query-next
'mu4e-search-next "1.7.0")
(defun mu4e-search-prev ()
"Execute the previous query from the query stacks."
(interactive)
(mu4e--search-query-navigate 'past))
(define-obsolete-function-alias 'mu4e-headers-query-prev
'mu4e-search-prev "1.7.0")
;; forget the past so we don't repeat it :/
(defun mu4e-search-forget ()
"Forget the search history."
(interactive)
(setq mu4e--search-query-past nil
mu4e--search-query-future nil)
(mu4e-message "Query history cleared"))
(define-obsolete-function-alias 'mu4e-headers-forget-queries
'mu4e-search-forget "1.7.0")
(defun mu4e-last-query ()
"Get the most recent query or nil if there is none."
mu4e--search-last-query)
;;; Completion for queries
(defvar mu4e--search-hist nil "History list of searches.")
(defvar mu4e-minibuffer-search-query-map
(let ((map (copy-keymap minibuffer-local-map)))
(define-key map (kbd "TAB") #'completion-at-point)
map)
"The keymap for reading a search query.")
(defun mu4e-search-read-query (prompt &optional initial-input)
"Read a query with completion using PROMPT and INITIAL-INPUT."
(minibuffer-with-setup-hook
(lambda ()
(setq-local completion-at-point-functions
#'mu4e--search-query-competion-at-point)
(use-local-map mu4e-minibuffer-search-query-map))
(read-string prompt initial-input 'mu4e--search-hist)))
(define-obsolete-function-alias 'mu4e-read-query
'mu4e-search-read-query "1.7.0")
(defconst mu4e--search-query-keywords
'("and" "or" "not"
"from:" "to:" "cc:" "bcc:" "contact:" "date:" "subject:" "body:"
"list:" "maildir:" "flag:" "mime:" "file:" "prio:" "tag:" "msgid:"
"size:" "embed:"))
(defun mu4e--search-query-competion-at-point ()
"Provide completion when entering search expressions."
(cond
((not (looking-back "[:\"][^ \t]*" nil))
(let ((bounds (bounds-of-thing-at-point 'word)))
(list (or (car bounds) (point))
(or (cdr bounds) (point))
mu4e--search-query-keywords)))
((looking-back "flag:\\(\\w*\\)" nil)
(list (match-beginning 1)
(match-end 1)
'("attach" "draft" "flagged" "list" "new" "passed" "replied"
"seen" "trashed" "unread" "encrypted" "signed")))
;; ((looking-back "maildir:\\([a-zA-Z0-9/.]*\\)" nil)
;; (list (match-beginning 1)
;; (match-end 1)
;; (mu4e-get-maildirs)))
((looking-back "prio:\\(\\w*\\)" nil)
(list (match-beginning 1)
(match-end 1)
(list "high" "normal" "low")))
((looking-back "mime:\\([a-zA-Z0-9/-]*\\)" nil)
(list (match-beginning 1)
(match-end 1)
(mailcap-mime-types)))))
(define-minor-mode mu4e-search-minor-mode
"Mode for searching for messages."
:global nil
:init-value nil ;; disabled by default
:group 'mu4e
:lighter ""
:keymap
(let ((map (make-sparse-keymap)))
(define-key map "s" 'mu4e-search)
(define-key map "S" 'mu4e-search-edit)
(define-key map "/" 'mu4e-search-narrow)
;;(define-key map "j" 'mu4e~headers-jump-to-maildir)
(define-key map (kbd "<M-left>") 'mu4e-search-prev)
(define-key map (kbd "\\") 'mu4e-search-prev)
(define-key map (kbd "<M-right>") 'mu4e-search-next)
(define-key map "b" 'mu4e-search-bookmark)
(define-key map "B" 'mu4e-search-bookmark-edit)
map))
(provide 'mu4e-search)
;;; mu4e-search.el ends here

View File

@ -29,6 +29,7 @@
(require 'mu4e-view-common)
(require 'mu4e-context)
(require 'mu4e-search)
(require 'calendar)
(require 'gnus-art)
@ -237,16 +238,6 @@ This is useful for advising some Gnus-functionality that does not work in mu4e."
;; but that's not very useful in this case
(define-key map "z" 'ignore)
(define-key map "s" #'mu4e-headers-search)
(define-key map "S" #'mu4e-view-search-edit)
(define-key map "/" #'mu4e-view-search-narrow)
(define-key map (kbd "<M-left>") #'mu4e-headers-query-prev)
(define-key map (kbd "<M-right>") #'mu4e-headers-query-next)
(define-key map "b" #'mu4e-headers-search-bookmark)
(define-key map "B" #'mu4e-headers-search-bookmark-edit)
(define-key map "%" #'mu4e-view-mark-pattern)
(define-key map "t" #'mu4e-view-mark-subthread)
(define-key map "T" #'mu4e-view-mark-thread)
@ -320,7 +311,6 @@ This is useful for advising some Gnus-functionality that does not work in mu4e."
(define-key map (kbd "<insertchar>") #'mu4e-view-mark-for-something)
(define-key map (kbd "#") #'mu4e-mark-resolve-deferred-marks)
;; misc
(define-key map "M" #'mu4e-view-massage)
@ -421,6 +411,7 @@ Based on Gnus' article-mode."
"." (apply func args))))
(use-local-map mu4e-view-mode-map)
(mu4e-context-minor-mode)
(mu4e-search-minor-mode)
(setq buffer-undo-list t);; don't record undo info
;; autopair mode gives error when pressing RET
;; turn it off