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:
Dirk-Jan C. Binnema 2023-02-14 23:37:19 +02:00
parent d55cba7237
commit 0354fa4fac
2 changed files with 76 additions and 54 deletions

View File

@ -1,6 +1,6 @@
;;; mu4e-helpers.el -- part of mu4e -*- lexical-binding: t -*- ;;; 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> ;; Author: Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
;; Maintainer: 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 nil
(mu4e-error "Missing property %s in %s" prop lst)))) (mu4e-error "Missing property %s in %s" prop lst))))
(defun mu4e--read-char-choice (prompt choices &optional key) (defun mu4e--matching-choice (choices kar)
"Read and return one of CHOICES, prompting for PROMPT. "Does KAR match any of the CHOICES?
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.
If optional KEY is provided, use that instead of asking user." KAR is a character and CHOICES is an alist as describe in
(let ((choice) (chosen) (inhibit-quit nil)) `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-insensitive
after trying an exact match.
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) (while (not chosen)
(message nil) ;; this seems needed... (message nil) ;; this seems needed...
(setq choice (or key (read-char-exclusive prompt))) (when-let ((kar (read-char-exclusive prompt)))
(if (eq choice 27) (keyboard-quit)) ;; quit if ESC is pressed (setq chosen (mu4e--matching-choice choices kar))))
(setq chosen (or (member choice choices) chosen))
(member (downcase choice) choices)
(member (upcase choice) choices))))
(car 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. "Ask user for an option from a list on the input area.
PROMPT describes a multiple-choice question to the user. OPTIONS PROMPT describes a multiple-choice question to the user. OPTIONS
describe the options, and is a list of cells describing describe the options, and is a list of cells describing
particular options. Cells have the following structure: particular options. Cells have the following structure:
(OPTIONSTRING . RESULT) (OPTION . RESULT)
where OPTIONSTRING is a non-empty string describing the where OPTIONS is a non-empty string describing the option. The
option. The first character of OPTIONSTRING is used as the first character of OPTION is used as the shortcut, and obviously
shortcut, and obviously all shortcuts must be different, so you all shortcuts must be different, so you can prefix the string
can prefix the string with an uniquifying character. with an uniquifying character.
The options are provided as a list for the user to choose from; The options are provided as a list for the user to choose from;
user can then choose by typing CHAR. Example: user can then choose by typing CHAR. Example:
(mu4e-read-option \"Choose an animal: \" (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: User now will be presented with a list: \"Choose an animal:
[M]onkey, [G]nu, [x]Moose\". [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 If optional character KEY is provied, use that instead of asking
the user. the user.
Function returns the cdr of the list element." Function returns the value (cdr) of the matching cell."
(let* ((prompt (mu4e-format "%s" prompt)) (let* ((choices ;; ((<display> ( <key> . <value> ) ...)
(optionsstr (seq-map
(mapconcat
(lambda (option) (lambda (option)
;; try to detect old-style options, and warn (list
(when (characterp (car-safe (cdr-safe option))) (concat ;; <display>
(mu4e-error "[" (propertize (substring (car option) 0 1)
(concat "Please use the new format for options/actions; " 'face 'mu4e-highlight-face)
"see the manual"))) "]"
(let ((kar (substring (car option) 0 1))) (substring (car option) 1))
(concat (cons
"[" (propertize kar 'face 'mu4e-highlight-face) "]" (string-to-char (car option)) ;; <key>
(substring (car option) 1)))) (cdr option)))) ;; <value>
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))))
options))) options)))
(if chosen (or (mu4e--read-choice-builtin prompt choices)
(cdr chosen) (mu4e-warn "invalid input"))))
(mu4e-warn "Unknown shortcut '%c'" response))))
(defun mu4e-filter-single-key (lst) (defun mu4e-filter-single-key (lst)
"Return a list consisting of LST items with a `characterp' :key prop." "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) (defun mu4e-key-description (cmd)
"Get the textual form of current binding to interactive function CMD. "Get the textual form of current binding to interactive function CMD.
If it is unbound, return nil. If there are multiple bindings, 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' ;; not a perfect heuristic: e.g. '<up>' is longer that 'C-p'
(car-safe (car-safe
(seq-sort (lambda (b1 b2) (seq-sort (lambda (b1 b2)

View File

@ -442,11 +442,10 @@ user)."
(symbol-name mu4e-search-sort-direction)) (symbol-name mu4e-search-sort-direction))
(mu4e-search-rerun))) (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. "Toggle some aspect of search.
When prefix-argument DONT-REFRESH is non-nil, do not refresh the When prefix-argument DONT-REFRESH is non-nil, do not refresh the
last search with the new setting. last search with the new setting."
If KEY is provided, use it instead of asking user."
(interactive "P") (interactive "P")
(let* ((toggles '(("fFull-search" . mu4e-search-full) (let* ((toggles '(("fFull-search" . mu4e-search-full)
("rInclude-related" . mu4e-headers-include-related) ("rInclude-related" . mu4e-headers-include-related)
@ -460,7 +459,7 @@ If KEY is provided, use it instead of asking user."
(format" (%s)" (format" (%s)"
(if (symbol-value (cdr cell)) "on" "off"))) (if (symbol-value (cdr cell)) "on" "off")))
(cdr cell))) toggles)) (cdr cell))) toggles))
(choice (mu4e-read-option "Toggle property " toggles key))) (choice (mu4e-read-option "Toggle property " toggles)))
(when choice (when choice
(set choice (not (symbol-value choice))) (set choice (not (symbol-value choice)))
(mu4e-message "Set `%s' to %s" (symbol-name choice) (symbol-value choice)) (mu4e-message "Set `%s' to %s" (symbol-name choice) (symbol-value choice))