summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDirk-Jan C. Binnema <djcb@djcbsoftware.nl>2021-08-28 22:21:00 +0300
committerDirk-Jan C. Binnema <djcb@djcbsoftware.nl>2021-08-29 20:30:29 +0300
commit3cd127d8ae9214f20375ecdca6c2622b033f3eb8 (patch)
treeae37d922ef570773411d59b568447c1da1b6da73
parent7d17b324addc5f2dafd18a494834a39ecb1c33bb (diff)
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.
-rw-r--r--mu4e/mu4e-headers.el348
-rw-r--r--mu4e/mu4e-main.el11
-rw-r--r--mu4e/mu4e-search.el446
-rw-r--r--mu4e/mu4e-view-gnus.el13
4 files changed, 498 insertions, 320 deletions
diff --git a/mu4e/mu4e-headers.el b/mu4e/mu4e-headers.el
index fb5930d..503c613 100644
--- a/mu4e/mu4e-headers.el
+++ b/mu4e/mu4e-headers.el
@@ -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."
@@ -1504,165 +1440,7 @@ descendants."
(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.
diff --git a/mu4e/mu4e-main.el b/mu4e/mu4e-main.el
index 6e01ceb..5a1f630 100644
--- a/mu4e/mu4e-main.el
+++ b/mu4e/mu4e-main.el
@@ -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"))))
diff --git a/mu4e/mu4e-search.el b/mu4e/mu4e-search.el
new file mode 100644
index 0000000..5539312
--- /dev/null
+++ b/mu4e/mu4e-search.el
@@ -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
diff --git a/mu4e/mu4e-view-gnus.el b/mu4e/mu4e-view-gnus.el
index 2b73f64..ad5af17 100644
--- a/mu4e/mu4e-view-gnus.el
+++ b/mu4e/mu4e-view-gnus.el
@@ -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