mirror of https://github.com/djcb/mu.git
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:
parent
7d17b324ad
commit
3cd127d8ae
|
@ -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.
|
||||
|
|
|
@ -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"))))
|
||||
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue