mirror of https://github.com/djcb/mu.git
mu4e-helpers: rework mu4e-read-option
Rework the mu4e-read-option code to be a bit easier to (re)use.
This commit is contained in:
parent
d55cba7237
commit
0354fa4fac
|
@ -1,6 +1,6 @@
|
|||
;;; mu4e-helpers.el -- part of mu4e -*- lexical-binding: t -*-
|
||||
|
||||
;; Copyright (C) 2022 Dirk-Jan C. Binnema
|
||||
;; Copyright (C) 2022-2023 Dirk-Jan C. Binnema
|
||||
|
||||
;; Author: Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
|
||||
;; Maintainer: Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
|
||||
|
@ -139,40 +139,74 @@ Does a local-exit and does not return."
|
|||
nil
|
||||
(mu4e-error "Missing property %s in %s" prop lst))))
|
||||
|
||||
(defun mu4e--read-char-choice (prompt choices &optional key)
|
||||
(defun mu4e--matching-choice (choices kar)
|
||||
"Does KAR match any of the CHOICES?
|
||||
|
||||
KAR is a character and CHOICES is an alist as describe in
|
||||
`mu4e--read-choice-builting'.
|
||||
|
||||
First try an exact match, but if there isn't, try
|
||||
case-insensitive.
|
||||
|
||||
Return the cdr (value) of the matching cell, if any."
|
||||
(let* ((match) (match-ci))
|
||||
(catch 'found
|
||||
(seq-do
|
||||
(lambda (choice)
|
||||
;; first try an exact match
|
||||
(let ((case-fold-search nil))
|
||||
(if (char-equal kar (caadr choice))
|
||||
(progn
|
||||
(setq match choice)
|
||||
(throw 'found choice)) ;; found it - quit.
|
||||
;; perhaps case-insensitive?
|
||||
(let ((case-fold-search t))
|
||||
(when (and (not match-ci) (char-equal kar (caadr choice)))
|
||||
(setq match-ci choice))))))
|
||||
choices))
|
||||
(if match (cdadr match)
|
||||
(when match-ci (cdadr match-ci)))))
|
||||
|
||||
(defun mu4e--read-choice-builtin (prompt choices)
|
||||
"Read and return one of CHOICES, prompting for PROMPT.
|
||||
|
||||
PROMPT describes a multiple-choice question to the user. CHOICES
|
||||
is an alist of the fiorm
|
||||
( ( <display-string> ( <shortcut> . <value> ))
|
||||
... )
|
||||
Any input that is not one of CHOICES is ignored. This is mu4e's
|
||||
version of `read-char-choice' which becomes case-insentive after
|
||||
trying an exact match.
|
||||
version of `read-char-choice' which becomes case-insensitive
|
||||
after trying an exact match.
|
||||
|
||||
If optional KEY is provided, use that instead of asking user."
|
||||
(let ((choice) (chosen) (inhibit-quit nil))
|
||||
(while (not chosen)
|
||||
(message nil);; this seems needed...
|
||||
(setq choice (or key (read-char-exclusive prompt)))
|
||||
(if (eq choice 27) (keyboard-quit)) ;; quit if ESC is pressed
|
||||
(setq chosen (or (member choice choices)
|
||||
(member (downcase choice) choices)
|
||||
(member (upcase choice) choices))))
|
||||
(car chosen)))
|
||||
Return the matching choice value (cdr of the cell)."
|
||||
(let ((chosen) (inhibit-quit nil)
|
||||
(prompt (format "%s%s"
|
||||
(mu4e-format prompt)
|
||||
(mapconcat #'car choices ", "))))
|
||||
(while (not chosen)
|
||||
(message nil) ;; this seems needed...
|
||||
(when-let ((kar (read-char-exclusive prompt)))
|
||||
(setq chosen (mu4e--matching-choice choices kar))))
|
||||
chosen))
|
||||
|
||||
(defun mu4e-read-option (prompt options &optional key)
|
||||
(defun mu4e-read-option (prompt options)
|
||||
"Ask user for an option from a list on the input area.
|
||||
|
||||
PROMPT describes a multiple-choice question to the user. OPTIONS
|
||||
describe the options, and is a list of cells describing
|
||||
particular options. Cells have the following structure:
|
||||
|
||||
(OPTIONSTRING . RESULT)
|
||||
(OPTION . RESULT)
|
||||
|
||||
where OPTIONSTRING is a non-empty string describing the
|
||||
option. The first character of OPTIONSTRING is used as the
|
||||
shortcut, and obviously all shortcuts must be different, so you
|
||||
can prefix the string with an uniquifying character.
|
||||
where OPTIONS is a non-empty string describing the option. The
|
||||
first character of OPTION is used as the shortcut, and obviously
|
||||
all shortcuts must be different, so you can prefix the string
|
||||
with an uniquifying character.
|
||||
|
||||
The options are provided as a list for the user to choose from;
|
||||
user can then choose by typing CHAR. Example:
|
||||
(mu4e-read-option \"Choose an animal: \"
|
||||
\='((\"Monkey\" . monkey) (\"Gnu\" . gnu) (\"xMoose\" . moose)))
|
||||
\\='((\"Monkey\" . monkey) (\"Gnu\" . gnu) (\"xMoose\" . moose)))
|
||||
|
||||
User now will be presented with a list: \"Choose an animal:
|
||||
[M]onkey, [G]nu, [x]Moose\".
|
||||
|
@ -180,36 +214,22 @@ User now will be presented with a list: \"Choose an animal:
|
|||
If optional character KEY is provied, use that instead of asking
|
||||
the user.
|
||||
|
||||
Function returns the cdr of the list element."
|
||||
(let* ((prompt (mu4e-format "%s" prompt))
|
||||
(optionsstr
|
||||
(mapconcat
|
||||
Function returns the value (cdr) of the matching cell."
|
||||
(let* ((choices ;; ((<display> ( <key> . <value> ) ...)
|
||||
(seq-map
|
||||
(lambda (option)
|
||||
;; try to detect old-style options, and warn
|
||||
(when (characterp (car-safe (cdr-safe option)))
|
||||
(mu4e-error
|
||||
(concat "Please use the new format for options/actions; "
|
||||
"see the manual")))
|
||||
(let ((kar (substring (car option) 0 1)))
|
||||
(concat
|
||||
"[" (propertize kar 'face 'mu4e-highlight-face) "]"
|
||||
(substring (car option) 1))))
|
||||
options ", "))
|
||||
(response
|
||||
(mu4e--read-char-choice
|
||||
(concat prompt optionsstr
|
||||
" [" (propertize "C-g" 'face 'mu4e-highlight-face)
|
||||
" to cancel]")
|
||||
;; the allowable chars
|
||||
(seq-map (lambda(elm) (string-to-char (car elm))) options)
|
||||
key))
|
||||
(chosen
|
||||
(seq-find
|
||||
(lambda (option) (eq response (string-to-char (car option))))
|
||||
(list
|
||||
(concat ;; <display>
|
||||
"[" (propertize (substring (car option) 0 1)
|
||||
'face 'mu4e-highlight-face)
|
||||
"]"
|
||||
(substring (car option) 1))
|
||||
(cons
|
||||
(string-to-char (car option)) ;; <key>
|
||||
(cdr option)))) ;; <value>
|
||||
options)))
|
||||
(if chosen
|
||||
(cdr chosen)
|
||||
(mu4e-warn "Unknown shortcut '%c'" response))))
|
||||
(or (mu4e--read-choice-builtin prompt choices)
|
||||
(mu4e-warn "invalid input"))))
|
||||
|
||||
(defun mu4e-filter-single-key (lst)
|
||||
"Return a list consisting of LST items with a `characterp' :key prop."
|
||||
|
@ -480,7 +500,10 @@ Mu4e version of emacs 28's string-replace."
|
|||
(defun mu4e-key-description (cmd)
|
||||
"Get the textual form of current binding to interactive function CMD.
|
||||
If it is unbound, return nil. If there are multiple bindings,
|
||||
return the shortest."
|
||||
return the shortest.
|
||||
|
||||
Rougly does what `substitute-command-keys' does, but picks
|
||||
shorter keys in some cases where there are multiple bindings."
|
||||
;; not a perfect heuristic: e.g. '<up>' is longer that 'C-p'
|
||||
(car-safe
|
||||
(seq-sort (lambda (b1 b2)
|
||||
|
|
|
@ -442,11 +442,10 @@ user)."
|
|||
(symbol-name mu4e-search-sort-direction))
|
||||
(mu4e-search-rerun)))
|
||||
|
||||
(defun mu4e-search-toggle-property (&optional dont-refresh key)
|
||||
(defun mu4e-search-toggle-property (&optional dont-refresh)
|
||||
"Toggle some aspect of search.
|
||||
When prefix-argument DONT-REFRESH is non-nil, do not refresh the
|
||||
last search with the new setting.
|
||||
If KEY is provided, use it instead of asking user."
|
||||
last search with the new setting."
|
||||
(interactive "P")
|
||||
(let* ((toggles '(("fFull-search" . mu4e-search-full)
|
||||
("rInclude-related" . mu4e-headers-include-related)
|
||||
|
@ -460,7 +459,7 @@ If KEY is provided, use it instead of asking user."
|
|||
(format" (%s)"
|
||||
(if (symbol-value (cdr cell)) "on" "off")))
|
||||
(cdr cell))) toggles))
|
||||
(choice (mu4e-read-option "Toggle property " toggles key)))
|
||||
(choice (mu4e-read-option "Toggle property " toggles)))
|
||||
(when choice
|
||||
(set choice (not (symbol-value choice)))
|
||||
(mu4e-message "Set `%s' to %s" (symbol-name choice) (symbol-value choice))
|
||||
|
|
Loading…
Reference in New Issue