* bug#43709: minibuffer keybinding cheatsheet and launcher [CODE SUBMISSION] @ 2020-09-29 16:59 Boruch Baum 2020-09-29 17:41 ` Drew Adams ` (2 more replies) 0 siblings, 3 replies; 17+ messages in thread From: Boruch Baum @ 2020-09-29 16:59 UTC (permalink / raw) To: 43709 [-- Attachment #1: Type: text/plain, Size: 691 bytes --] If you've ever used packages such as `ivy' or `magit', you've probably benefited from each's custom combination keybinding cheatsheet and launcher: `hydra' in the case of `ivy', and `transient' for `magit'. The attached package `key-assist' attempts to offer a generic and very simple alternative requiring only the `completing-read' function commonly used in core vanilla emacs. `key-assist' is trivial to implement "on-the-fly" interactively for any buffer, and programmatically much simpler to customize that either `hydra' or `transient'. And did I mention that it only requires `completing-read'? -- hkp://keys.gnupg.net CA45 09B5 5351 7C11 A9D1 7286 0036 9E45 1595 8BC0 [-- Attachment #2: key-assist.el --] [-- Type: text/plain, Size: 12593 bytes --] ;;; key-assist.el --- minibuffer keybinding cheatsheet and launcher -*- lexical-binding: t -*- ;; Copyright © 2020, Boruch Baum <boruch_baum@gmx.com> ;; Available for assignment to the Free Software Foundation, Inc. ;; Author: Boruch Baum <boruch_baum@gmx.com> ;; Maintainer: Boruch Baum <boruch_baum@gmx.com> ;; Keywords: minibuffer keybindings ;; Package: key-assist ;; This file is NOT part of GNU Emacs. ;; This 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. ;; This software 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 this software. If not, see <https://www.gnu.org/licenses/>. ;; ;;; Commentary: ;; If you've ever used packages such as `ivy' or `magit', you've ;; probably benefited from each's custom combination keybinding ;; cheatsheet and launcher: `hydra' in the case of `ivy', and ;; `transient' for `magit'. The current package `key-assist' ;; attempts to offer a generic and very simple alternative requiring ;; only the `completing-read' function commonly used in core vanilla ;; emacs. `key-assist' is trivial to implement "on-the-fly" ;; interactively for any buffer, and programmatically much simpler ;; to customize that either `hydra' or `transient'. And did I ;; mention that it only requires `completing-read'? ;; ;;; Dependencies: ;; `cl-seq' - For `cl-position', but `cl-lib' is long part of all ;; modern emcasen. ;; ;;; Installation: ;; 1) Evaluate or load this file. ;; ;;; Interactive operation: ;; Run M-x key-assist from the buffer of interest. Specify a ;; selection (or don't), press <TAB> to view the presentation, and ;; then either exit with your new-found knowledge of the command ;; keybindings, or use standard emacs tab completion to select an ;; option, and press <RETURN> to perform the action. ;; ;; If you choose not to respond to the initial prompt, a list of ;; keybindings and command descriptions will be generated based upon ;; the first word of the buffer's major mode. For, example, in a ;; `w3m' buffer, the list will be of all interactive functions ;; beginning `w3m-'. This works out to be great as a default, but ;; isn't always useful. For example, in an `emacs-lisp-mode' buffer, ;; what would you expect it to usefully produce? At the other ;; extreme might be a case of a buffer too many obscure keybindings ;; of little use. ;; You can also respond to the prompt with your own regexp of ;; commands to show, or with the name of a keymap of your choice. ;; For the purposes of `key-assist', a regexp can be just a ;; substring, without REQUIRING any leading or trailing globs. ;; In all cases, note that the package can only present keybindings ;; currently active in the current buffer, so if a sub-package ;; hasn't been loaded yet, that package's keybindings would not be ;; presented. Also note that the commands are presented sorted by ;; keybinding length, alphabetically. ;; ;;; Programmatic operation: ;; See the docstrings for functions `key-assist' and ;; `key-assist--get-cmds'. ;; ;;; Configuration: ;; Two variables are available to exclude items from the ;; presentation list: `key-assist-exclude-cmds' and ;; `key-assist-exclude-regexps'. See there for further information. ;; ;;; Compatability ;; Tested with emacs 26.1 in debian. ;; TODO: ;; ;; * In a vanilla emacs environment, the sorting appears wrong ;; seemingly partially because of how the completion buffer is ;; constructed, bottom-up. This isn't an issue when using one of ;; the supplemental minibuffer completion packages. \f ;; ;;; Code (require 'cl-seq) ;; cl-member, cl-position ;; ;;; Variables (defvar key-assist-exclude-cmds '(ignore self-insert-command digit-argument negative-argument describe-mode) "List of commands to always exclude from `key-assist' output.") (defvar key-assist-exclude-regexps '("-mouse-") "List of regexps of commands to exclude from `key-assist' output.") ;; TODO: Don't depend upon a mouse command having the word '-mouse-' in it. ;; ;;; Internal functions (defun key-assist--get-keybinding (cmd &optional key-map) "Return a string with CMD's shortest keybinding." (let (shortest) (dolist (key (mapcar 'key-description (where-is-internal cmd key-map nil t))) (when (or (not shortest) (> (length shortest) (length key))) (setq shortest key))) shortest)) (defun key-assist--get-description (cmd) "Return a string with CMD's description. CMD is a symbol of an interactive command." ;; TODO: Change hard-coded length to an ARG. (let ((doc (documentation cmd t))) (format "\t%s" (if (or (not doc) (not (string-match "\n" doc)) (zerop (match-beginning 0))) (concat (symbol-name cmd) " (not documented)") (substring doc 0 (match-beginning 0)))))) (defun key-assist--vet-cmd (cmd result-list) "Check whether CMD should be on a `key-assist' list. See `key-assist-exclude-cmds' and `key-assist-exclude-regexps'." (and (symbolp cmd) (commandp cmd) (not (cl-member cmd result-list :test (lambda (cmd l) (equal cmd (nth 1 l))))) ; (assq cmd result-list)) (not (memq cmd key-assist-exclude-cmds)) (let ((not-found t)) (mapc (lambda (x) (when (string-match x (format "%s" cmd)) (setq not-found nil))) key-assist-exclude-regexps) not-found))) (defun key-assist--parse-cmd (cmd result-list &optional key-map) "Extract commands and shortest keybindings from a keymap. This is an internal function used by `key-assist'. Returns a list whose elements are a keybinding string, a command symbol, and a description string." (when (key-assist--vet-cmd cmd result-list) (let* ((key-map (when (keymapp key-map) key-map)) (shortest (key-assist--get-keybinding cmd key-map))) (when shortest (list shortest cmd (concat shortest (key-assist--get-description cmd))))))) (defun key-assist--get-cmds (&optional spec nosort nofinish) "Return a list of commands, keybindings, and descriptions. Returns a list of CONS, whose CAR is the command, and whose CDR is a string of the form \"shortest-keybinding tab-character command-description\". Optional arg SPEC may be a regexp string of desired commands. If NIL, a regexp is generated based upon the first word of the buffer's major mode. SPEC may also be a keymap of desired commands. In both of these cases, the resulting list is sorted alphabetically by keybinding length. SPEC has additional options of being either a list of commands, or a list of CONS whose CAR is a command, and whose CDR is either a description-string or a function which returns a description string. A final programmatic option is for SPEC to be any combination of the above options. For that most complex case, the first list element of SPEC must be the symbol 'collection. For none of these additional options is sorting performed. Optional arg NOSORT can be a function to replace the default sort algorithm with the programmer's desired post-processing, or some other non-nil value for no sorting at all. If a function, it should accept a single list of elements (keybinding-string commandp description-string) and should return a list of elements (anything commandp description-string). Optional arg NOFINSH return a list in `key-assist--parse-cmd' format instead of the list of CONS described above. It is used internally for processing 'collection lists." (setq spec (cond ((or (not spec) (and (stringp spec) (zerop (length spec)))) (let ((str (symbol-name major-mode))) (substring str 0 (1+ (string-match "-" str))))) ((and (stringp spec) (boundp (intern spec)) (keymapp (symbol-value (intern spec)))) (symbol-value (intern spec))) (t spec))) (let (name result-elem (result-list '())) (cond ((keymapp spec) (let (cmd) (dolist (elem spec) (cond ((atom elem)) ;; eg. 'keymap ((listp (setq cmd (cdr elem)))) ;; TODO: possibly also embedded keymap? ((commandp cmd) ;; this excludes 'menubar (when (setq result-elem (key-assist--parse-cmd cmd result-list)) (push result-elem result-list))))))) ((stringp spec) (mapatoms (lambda (x) (and (commandp x) (string-match spec (setq name (symbol-name x))) (when (setq result-elem (key-assist--parse-cmd x result-list)) (push result-elem result-list)))))) ((listp spec) (cond ((eq (car spec) 'collection) (dolist (collection-element (cdr spec)) ;; Maybe it's more efficient to sort each collection element? (let ((temp-list (key-assist--get-cmds collection-element 'nosort 'nofinish))) (dolist (elem temp-list) (push elem result-list))))) ((commandp (car spec)) (dolist (cmd spec) (when (setq result-elem (key-assist--parse-cmd cmd result-list)) (push result-elem result-list)))) (t ; spec is a list of CONS (cmd . (or string function)) (dolist (elem spec) (when (key-assist--vet-cmd (car elem) result-list) (let ((shortest (key-assist--get-keybinding (car elem)))) (when shortest (push (list shortest (car elem) (if (stringp (cadr elem)) (cadr elem) (funcall (cadr elem)))) result-list)))))))) (t (error "Improper SPEC format."))) (when (not nosort) (setq result-list (if (functionp nosort) (funcall nosort result-list) (sort result-list (lambda (a b) (cond ((= (length (car a)) (length (car b))) (string< (car a) (car b))) ((< (length (car a)) (length (car b)))) (t nil))))))) (if nofinish result-list (mapcar (lambda (x) (cons (nth 1 x) (nth 2 x))) result-list)))) ;; ;;; Interactive functions (defun key-assist (&optional spec prompt nosort) "Prompt to eval a locally relevant function, with hints and keybindings. Press TAB to see the hints. Interactively, the optional arg SPEC is either a regexp string for candidate commands to match, or a keymap from which to prepare the hints. If NIL, a regexp is generated based upon the first word of the buffer's major mode. Results are presented sorted alphabetically by keybinding length. Programmatically, optional arg PROMPT can be used to customize the prompt. For the further programmatic options of SPEC and for a description of arg NOSORT, see function `key-assist--get-cmds'. See also variables `key-assist-exclude-regexps' and `key-assist-exclude-cmds'." (interactive "MOptional: Enter command regexp or keymap name: ") (let* ((prompt (or prompt "Select: ")) (commands (key-assist--get-cmds spec nosort)) (choices (mapcar 'cdr commands)) choice minibuffer-history) (if (not choices) (user-error "No choices found.") (while (not (setq choice (cl-position (substring-no-properties ; Is -no-properties necessary? (completing-read prompt choices nil t)) choices :test 'equal)))) (funcall (car (nth choice commands)))))) \f ;; ;;; Conclusion (provide 'key-assist) ;;; key-assist.el ends here ^ permalink raw reply [flat|nested] 17+ messages in thread
* bug#43709: minibuffer keybinding cheatsheet and launcher [CODE SUBMISSION] 2020-09-29 16:59 bug#43709: minibuffer keybinding cheatsheet and launcher [CODE SUBMISSION] Boruch Baum @ 2020-09-29 17:41 ` Drew Adams 2020-09-29 18:22 ` Boruch Baum 2020-09-29 18:26 ` Boruch Baum 2020-10-01 13:51 ` Stefan Monnier 2020-10-02 7:14 ` bug#43709: minibuffer keybinding cheatsheet and launcher [V2 CODE SUBMISSION] Boruch Baum 2 siblings, 2 replies; 17+ messages in thread From: Drew Adams @ 2020-09-29 17:41 UTC (permalink / raw) To: Boruch Baum, 43709 > If you've ever used packages such as `ivy' or `magit', you've probably > benefited from each's custom combination keybinding cheatsheet and > launcher: `hydra' in the case of `ivy', and `transient' for `magit'. The > attached package `key-assist' attempts to offer a generic and very > simple alternative requiring only the `completing-read' function > commonly used in core vanilla emacs. `key-assist' is trivial to > implement "on-the-fly" interactively for any buffer, and > programmatically much simpler to customize that either `hydra' or > `transient'. And did I mention that it only requires `completing-read'? Cf. `keysee.el' https://www.emacswiki.org/emacs/KeySee https://www.emacswiki.org/emacs/download/keysee.el ^ permalink raw reply [flat|nested] 17+ messages in thread
* bug#43709: minibuffer keybinding cheatsheet and launcher [CODE SUBMISSION] 2020-09-29 17:41 ` Drew Adams @ 2020-09-29 18:22 ` Boruch Baum 2020-09-29 19:05 ` Drew Adams 2020-09-29 18:26 ` Boruch Baum 1 sibling, 1 reply; 17+ messages in thread From: Boruch Baum @ 2020-09-29 18:22 UTC (permalink / raw) To: Drew Adams; +Cc: 43709 [-- Attachment #1: Type: text/plain, Size: 1656 bytes --] On 2020-09-29 10:41, Drew Adams wrote: > Cf. `keysee.el' > > https://www.emacswiki.org/emacs/KeySee > > https://www.emacswiki.org/emacs/download/keysee.el Oh. Had I known... Would I? / Wouldn't have I? From reading the emacswiki description, it seems that there are some differences in presentation. My motivation was specifically to write just a few lines for an element of another code submission I have pending, and then the idea got out of control. It didn't occur to me to check emacs-wiki. BACKGROUND: I have a collection of dired extensions that'll be ready for submission as soon as I complete the documentation. It includes a set of 'trash' management functions, eg. smart empty trash, restore trashed file(s), report trash status. But who can be expected to remember all the arcane keybindings of all the arcane modes? So I wrote it hard-wired for about four commands of diredc-trash, and then ... ATTACHED: I'm attaching that original function because someone may ask why include in `key-assist.el' a programmatic option to supply a list of elements (command . function). In the attached example, you can see that I'm presenting a description of the command `diredc-trash-toggle' based upon what it would do at any particular time. FOLLOW-UP: Another follow-up might be to supply some specific code for some notorious major modes. I'm thinking in particular of `calc-mode', which is such a beauty-in-the-rough, but suffers from a particularly bad case of keybinding hell (it also suffers from a consistent absence of docstrings). -- hkp://keys.gnupg.net CA45 09B5 5351 7C11 A9D1 7286 0036 9E45 1595 8BC0 [-- Attachment #2: diredc-trash-assistant.el --] [-- Type: text/plain, Size: 1580 bytes --] (defun diredc-trash-assistant () "Minibuffer cheatsheet and launcher for diredc-trash functions." ;; This was the inspiration for `key-assist.el' (interactive) (let ((zz (lambda (x) (let (shortest) (dolist (key (mapcar 'key-description (where-is-internal x diredc-mode-map nil t))) (when (or (not shortest) (> (length shortest) (length key))) (setq shortest key))) shortest))) (options (list (list (if delete-by-moving-to-trash "Switch to using deletion" "Switch to using trash") 'diredc-trash-toggle) '("Jump to trash files dir" diredc-trash-view) '("Report trash size" diredc-trash-info) '("Empty the trash" diredc-trash-empty) '("Restore file at point" diredc-trash-restore))) (prompt (concat (diredc-trash-info) "\nSelect: ")) choices choice) (setq choices (mapcar (lambda(x) (format "%-23s %s" (car x) (funcall zz (cadr x)))) options)) (while (not (setq choice (cl-position (completing-read prompt choices nil t nil 'choices ) choices :test 'equal)))) (funcall (cadr (nth choice options))))) ^ permalink raw reply [flat|nested] 17+ messages in thread
* bug#43709: minibuffer keybinding cheatsheet and launcher [CODE SUBMISSION] 2020-09-29 18:22 ` Boruch Baum @ 2020-09-29 19:05 ` Drew Adams 0 siblings, 0 replies; 17+ messages in thread From: Drew Adams @ 2020-09-29 19:05 UTC (permalink / raw) To: Boruch Baum; +Cc: 43709 > Oh. Had I known... Would I? / Wouldn't have I? Yes, you should. And thanks for doing so. Dunno whether filing a bug is the best way to provide it, but that will work, no doubt. Another possibility is to use emacs-devel@gnu.org. Many more people read that mailing list, including most who also pay attention to the bug list. > BACKGROUND: ... > ATTACHED: ... > FOLLOW-UP: ... That all sounds good. Keep it up. FYI: Another related library is `which-key.el', which you may already be aware of. https://github.com/justbur/emacs-which-key ^ permalink raw reply [flat|nested] 17+ messages in thread
* bug#43709: minibuffer keybinding cheatsheet and launcher [CODE SUBMISSION] 2020-09-29 17:41 ` Drew Adams 2020-09-29 18:22 ` Boruch Baum @ 2020-09-29 18:26 ` Boruch Baum 1 sibling, 0 replies; 17+ messages in thread From: Boruch Baum @ 2020-09-29 18:26 UTC (permalink / raw) To: Drew Adams; +Cc: 43709 Also, note how in the `diredc-trash-assistant' prototype, how I exploited the PROMPT arg to sneak in extra status information. -- hkp://keys.gnupg.net CA45 09B5 5351 7C11 A9D1 7286 0036 9E45 1595 8BC0 ^ permalink raw reply [flat|nested] 17+ messages in thread
* bug#43709: minibuffer keybinding cheatsheet and launcher [CODE SUBMISSION] 2020-09-29 16:59 bug#43709: minibuffer keybinding cheatsheet and launcher [CODE SUBMISSION] Boruch Baum 2020-09-29 17:41 ` Drew Adams @ 2020-10-01 13:51 ` Stefan Monnier 2020-10-01 15:31 ` Boruch Baum 2020-10-01 18:25 ` Boruch Baum 2020-10-02 7:14 ` bug#43709: minibuffer keybinding cheatsheet and launcher [V2 CODE SUBMISSION] Boruch Baum 2 siblings, 2 replies; 17+ messages in thread From: Stefan Monnier @ 2020-10-01 13:51 UTC (permalink / raw) To: Boruch Baum; +Cc: 43709 > If you've ever used packages such as `ivy' or `magit', you've probably > benefited from each's custom combination keybinding cheatsheet and > launcher: `hydra' in the case of `ivy', and `transient' for `magit'. The > attached package `key-assist' attempts to offer a generic and very > simple alternative requiring only the `completing-read' function > commonly used in core vanilla emacs. `key-assist' is trivial to > implement "on-the-fly" interactively for any buffer, and > programmatically much simpler to customize that either `hydra' or > `transient'. And did I mention that it only requires `completing-read'? I'm not completely sure what is the intended use scenario. I mean, I understand the package is not 100% ready, so I'm interested to know how you imagine it working *ideally* once the various kinks have been sorted out. [ To see why I say it's not quite ready, here's my experience with it: I just tried it in a fresh new Emacs session, and after `M-x key-assist` (which itself is a problem because most users won't know/bother to run this command way, so we'd need some more "obvious" way to run it) I get a prompt for something and without reading more the description I wouldn't know what the prompt wants (I think this prompt should only appear if requested explicitly) and then it said "no choices found". So I tried again in a dired buffer, where it instead showed me a minibuffer prompt "Select: " with no indication of what it is I should be selecting, after that I hit TAB (because I read that it uses `completing-read`, but for the un-initiated it might not be obvious) at which point it did show me a list of bindings and short descriptions and when I selected the first entry (i.e. `!`) it signal'd an error about wrong number of arguments. ] I do think it would be nice to have something like it, since it's an alternative to the menus which has the advantage of being auto-constructed, doesn't need the mouse, and focuses on the keybindings. It would also be nice to make it usable from minibuffers as well ;-) See some comments below based on a quick look at the code. Stefan > ;; If you've ever used packages such as `ivy' or `magit', you've For those who haven't, it would be good to describe what your package does. > ;;; Dependencies: > > ;; `cl-seq' - For `cl-position', but `cl-lib' is long part of all > ;; modern emcasen. There's no such thing as a `cl-seq` package. There's a `cl-seq.el` file, which is part of the `cl-lib` package. > (require 'cl-seq) ;; cl-member, cl-position Please require `cl-lib` rather than `cl-seq` since which function is implemented in which file of `cl-lib` is an internal detail (e.g. in Emacs-24.1 `cl-seq` does not provide `cl-position` but `cl-lib` still does). > (defun key-assist--get-keybinding (cmd &optional key-map) > "Return a string with CMD's shortest keybinding." > (let (shortest) > (dolist (key (mapcar 'key-description > (where-is-internal > cmd key-map nil t))) > (when (or (not shortest) > (> (length shortest) (length key))) > (setq shortest key))) > shortest)) `where-is-internal` is supposed to return the shortest binding already when asked to return the "firstonly". Your notion of length is slightly different, admittedly, but I wonder what were the concrete cases that motivated doing your own sorting (might be worth adding that as a comment in the code). > (let ((not-found t)) > (mapc (lambda (x) > (when (string-match x (format "%s" cmd)) > (setq not-found nil))) > key-assist-exclude-regexps) > not-found))) You should move the `(format "%s" cmd)` out of the loop (and use `dolist` instead of `mapc`). > ((keymapp spec) > (let (cmd) > (dolist (elem spec) Please use `map-keymap` rather than `dolist` so as to handle all the different keymap formats. > (while (not (setq choice > (cl-position > (substring-no-properties ; Is -no-properties necessary? > (completing-read prompt choices nil t)) > choices :test 'equal)))) Why do you have this loop? Doesn't the `require-match` arg of `completing-read` make it unnecessary? > (funcall (car (nth choice commands)))))) This should probably be `command-execute` rather than `funcall` to fix the wrong-number-of-arguments error I bumped into. ^ permalink raw reply [flat|nested] 17+ messages in thread
* bug#43709: minibuffer keybinding cheatsheet and launcher [CODE SUBMISSION] 2020-10-01 13:51 ` Stefan Monnier @ 2020-10-01 15:31 ` Boruch Baum 2020-10-01 16:04 ` Stefan Monnier 2020-10-01 18:25 ` Boruch Baum 1 sibling, 1 reply; 17+ messages in thread From: Boruch Baum @ 2020-10-01 15:31 UTC (permalink / raw) To: Stefan Monnier; +Cc: 43709 On 2020-10-01 09:51, Stefan Monnier wrote: > I'm not completely sure what is the intended use scenario. 1) Interactively... 1.1) A user finds himself in an unfamiliar mode buffer, and isn't sure what that mode's options are and how to use them. You mention further that you tried it in a dired-mode, so you saw its default operation in such a case. For dired-mode, the result-list is very long; for other modes, such as occur-mode or a packages buffer, you'll get a shorter list. The more obscure the mode, the more useful this use-case. 1.2) A user has a vague idea of a command name of interest, so enters a regex or substring of it to see a list of keybindings and descriptions of matches (as relevant for the current buffer). 2) Programmatically... 2.1) A developer wants to offer a cheat sheet / launcher for a package (similar in function to transient/hydra) but with less programming. This package offers wide latitude of how to compose a command list, what to display, how to sort, how to prompt. The args can be trivially simple to quite complex. (defun my-mode-low-budget-transient () (interactive) (when (eq major-mode my-mode) (key-assist ...))) (define-key my-mode-map "?" 'my-mode-low-budget-transient) BTW, On the emacs-bug thread/URL, I shared with Drew Adams the original inspiration for the package, which includes the motivation for what seems like an obscure arg option (cmd . function) > [ To see why I say it's not quite ready, here's my experience with it: > I just tried it in a fresh new Emacs session, and after `M-x > key-assist` (which itself is a problem because most users won't > know/bother to run this command way, so we'd need some more "obvious" > way to run it) Always a problem with a new feature. When you say 'obvious', you seem to mean more than the normal publicizing of a new feature? > I get a prompt for something You mean the prompt "Optional: Enter command regexp or keymap name:"? > and without reading more the description I wouldn't know what the > prompt wants True of very many emacs commands. Not a justification though. It could be a multi-line verbose explanatory prompt. > (I think this prompt should only appear if requested explicitly) Reasonable. Could be argued both ways. I have no preference, and just thought it friendlier this way to explicitly present to the user all the options, especially since this is supposed to be a 'help' function. Programmatically, this prompt shouldn't ever appear. > and then it said "no choices found". That's curious. I haven't seen that in my testing. What was your response to the first prompt? What was the value of 'major-mode' of the 'current-buffer' when you ran the command? > So I tried again in a dired buffer, where it instead showed me a > minibuffer prompt "Select: " with no indication of what it is I > should be selecting, I had difficulty deciding what text to use for this prompt because many users can be expected to be using some non-default minibuffer completion supplemental package (eg. ido, icicles, ivy), and those all have slightly different behavior; many present a result-list without needing to TAB. > after that I hit TAB (because I read that it uses `completing-read`, > but for the un-initiated it might not be obvious) Again, true of very many emacs commands. Not a justification though. I'm not a regular on this list, but even slashdot.org is reporting that the list has been discussing making emacs friendlier to new users, and I'm on board with that, so I'll take any suggestions for improvement here, but it might be better done as a patch to 'completing-read'. > at which point it did show me a list of bindings and short > descriptions and when I selected the first entry (i.e. `!`) it > signal'd an error about wrong number of arguments. ] Got it. Thanks much for your concrete suggestion further down on how to correct this bug. Many of the other options *DO* work though; this is a bug in the improper way the function calls some interactive functions. > I do think it would be nice to have something like it, since it's an > alternative to the menus which has the advantage of being > auto-constructed, doesn't need the mouse, and focuses on the keybindings. Yep. Also, for me it was important that it present command descriptions, even if in the end it came at the expense of omitting command names to make the strings shorter. > It would also be nice to make it usable from minibuffers as well ;-) Hmm, I wonder.. Maybe it already does programmatically on a per-case basis. Interactively, do you mean integrating into the minibuffer codebase? I've long wanted something like this for the minibuffer options to isearch- > See some comments below based on a quick look at the code. I appreciate the comments a lot. Thanks for the constructive review. > `where-is-internal` is supposed to return the shortest binding already > when asked to return the "firstonly". I wasn't getting my desired result with it.. > Your notion of length is slightly different, admittedly, but I wonder > what were the concrete cases that motivated doing your own sorting Oops. Forgetting just now. A secondary motivation for the sort was to offer multiple sort options. In the end, I left it as an option for programmers to pass their own sort function. For example, someone may want to treat prefixes / upper-case differently. > > (while (not (setq choice > > (cl-position > > (substring-no-properties ; Is -no-properties necessary? > > (completing-read prompt choices nil t)) > > choices :test 'equal)))) > > Why do you have this loop? Doesn't the `require-match` arg of > `completing-read` make it unnecessary? I remember thinking so, too, and then needing to add the loop because completing-read was accepting a RET. My recollection is that it was sending an empty string to cl-position. > .... But besides all that, it's great, right? -- hkp://keys.gnupg.net CA45 09B5 5351 7C11 A9D1 7286 0036 9E45 1595 8BC0 ^ permalink raw reply [flat|nested] 17+ messages in thread
* bug#43709: minibuffer keybinding cheatsheet and launcher [CODE SUBMISSION] 2020-10-01 15:31 ` Boruch Baum @ 2020-10-01 16:04 ` Stefan Monnier 2020-10-01 20:46 ` Boruch Baum 0 siblings, 1 reply; 17+ messages in thread From: Stefan Monnier @ 2020-10-01 16:04 UTC (permalink / raw) To: Boruch Baum; +Cc: 43709 >> [ To see why I say it's not quite ready, here's my experience with it: >> I just tried it in a fresh new Emacs session, and after `M-x >> key-assist` (which itself is a problem because most users won't >> know/bother to run this command way, so we'd need some more "obvious" >> way to run it) > Always a problem with a new feature. When you say 'obvious', you seem to > mean more than the normal publicizing of a new feature? I'm thinking that this would be useful for someone that not very familiar with Emacs, which also explains my further comments about the behavior. As it is, it seems instead geared toward users that are already familiar with Emacs and want to get to know some new mode or something like that. For a novice, we'd ideally want the user to be able to find and use this functionality "naturally" without even knowing beforehand how to look for it and how Emacs generally works ;-) >> I get a prompt for something > You mean the prompt "Optional: Enter command regexp or keymap name:"? Yes. >> and without reading more the description I wouldn't know what the >> prompt wants > True of very many emacs commands. Not a justification though. It could > be a multi-line verbose explanatory prompt. I think skipping the prompt is an easier way to avoid the confusion. Only prompt if the user asks for it (e.g. with a `C-u` prefix, an alternative entry-point, ...). > Reasonable. Could be argued both ways. I have no preference, and just > thought it friendlier this way to explicitly present to the user all the > options, especially since this is supposed to be a 'help' function. Or maybe we'd want to offer this prompt *afterwards*, as a special command to change&recompute the completion table based on a new criterion. >> and then it said "no choices found". > That's curious. I haven't seen that in my testing. What was your > response to the first prompt? RET > What was the value of 'major-mode' of the 'current-buffer' when you > ran the command? I was in *scratch*, so should have been `lisp-interaction-mode`. Maybe the "no choices found" message should mention the search string that was used (that's also a good way to teach the user what the first prompt means). >> So I tried again in a dired buffer, where it instead showed me a >> minibuffer prompt "Select: " with no indication of what it is I >> should be selecting, > > I had difficulty deciding what text to use for this prompt because many > users can be expected to be using some non-default minibuffer completion > supplemental package (eg. ido, icicles, ivy), and those all have > slightly different behavior; many present a result-list without needing > to TAB. Good point. For the plain old default, it would make a lot of sense to eagerly popup the *Completions*, but I'm not sure how we could do it without getting in the way for ivy and friends. > Again, true of very many emacs commands. Not a justification though. I'm > not a regular on this list, but even slashdot.org is reporting that the > list has been discussing making emacs friendlier to new users, and I'm > on board with that, so I'll take any suggestions for improvement here, > but it might be better done as a patch to 'completing-read'. Ah, now that makes more sense. I thought you'd sent your package in the context of that discussion of improving the experience for novice users. >> I do think it would be nice to have something like it, since it's an >> alternative to the menus which has the advantage of being >> auto-constructed, doesn't need the mouse, and focuses on the keybindings. > Yep. Also, for me it was important that it present command descriptions, Agreed. > even if in the end it came at the expense of omitting command names to > make the strings shorter. That's a good tradeoff. >> It would also be nice to make it usable from minibuffers as well ;-) > Hmm, I wonder.. Maybe it already does programmatically on a per-case > basis. Interactively, do you mean integrating into the minibuffer > codebase? I've long wanted something like this for the minibuffer > options to isearch- I'm not sure exactly how it should do it. But yes, `isearch` is an obvious use case as well (I say "as well", because isearch doesn't actually use the minibuffer). >> > (while (not (setq choice >> > (cl-position >> > (substring-no-properties ; Is -no-properties necessary? >> > (completing-read prompt choices nil t)) >> > choices :test 'equal)))) >> >> Why do you have this loop? Doesn't the `require-match` arg of >> `completing-read` make it unnecessary? > > I remember thinking so, too, and then needing to add the loop because > completing-read was accepting a RET. My recollection is that it was > sending an empty string to cl-position. Ah, OK, that makes sense. > But besides all that, it's great, right? Yes, I like it, Stefan ^ permalink raw reply [flat|nested] 17+ messages in thread
* bug#43709: minibuffer keybinding cheatsheet and launcher [CODE SUBMISSION] 2020-10-01 16:04 ` Stefan Monnier @ 2020-10-01 20:46 ` Boruch Baum 0 siblings, 0 replies; 17+ messages in thread From: Boruch Baum @ 2020-10-01 20:46 UTC (permalink / raw) To: Stefan Monnier; +Cc: 43709 On 2020-10-01 12:04, Stefan Monnier wrote: > I'm thinking that this would be useful for someone that not very > familiar with Emacs, It could be, yes. > it seems instead geared toward users that are already familiar with > Emacs and want to get to know some new mode or something like that. True. > For a novice, we'd ideally want the user to be able to find and use this > functionality "naturally" without even knowing beforehand how to look > for it and how Emacs generally works ;-) Oh, emacs. I'm the wrong person to ask because my reflexive response was to assume everyone should expect something like 'C-u C-h k'. BTW, a few months (years?) ago I had an email exchange with Eli Zaretskii in which he mentioned the help feature that he felt was most useful and important ... and it turned out not to even have a keybinding o even a mention in 'C-h ?' (and now I've forgotten even what it was the command was...) > I think skipping the prompt is an easier way to avoid the confusion. > Only prompt if the user asks for it (e.g. with a `C-u` prefix, an > alternative entry-point, ...). My initial inclination was to go for the prefix-arg option, but that wouldn't be friendly to novice users. And then I thought the same thing about an alternative entry-point, because it's asking someone to remember two separate commands. If the goal is to deal with the confusion of novices, the way to do it is with a single command and verbose interface. Even for non-novices, the goal here is to reduce the requirement to memorize, so having multiple ways of invoking the command is contradictory in that it asks for more memorization. A golden mean would be to come up with a short and clear prompt that can be ignored for most cases. > Or maybe we'd want to offer this prompt *afterwards*, as a special > command to change&recompute the completion table based on > a new criterion. Not difficult to program, but when I think about it, I see two obstacles: 1) The prompt ends up being at least as messy or confusing. What I end up with is a three-line prompt, something like (example being a dired buffer): Presenting list of 'dired-' commands. Select an item on the current list, or <key> to present a list using a different regexp 2) What <key> could you use to guarantee no conflict with any other entry on the list? Remember that we're inside 'completing-read' at this point, so we can't be using control characters, can we? Even if we can dynamically change the <key> based upon a uniqueness check of all characters in the list, that would just add to confusion, because each different invocation would generate a different <key>. In practice, it would be difficult to generate such a unique key because completing-read matches against all characters in list, which in our case includes all characters in the description, not just those of the keybindings. > >> and then it said "no choices found". > I was in *scratch*, so should have been `lisp-interaction-mode`. > Maybe the "no choices found" message should mention the search string > that was used (that's also a good way to teach the user what the first > prompt means). Agreed. I'll work on it. My preference would then be for a verbose minibuffer message, maybe as much as three long lines, amounting to a mini-tutorial, the goal being to reduce confusion for novices or people unfamiliar with the operation. > >> So I tried again in a dired buffer, where it instead showed me a > >> minibuffer prompt "Select: " with no indication of what it is I > >> should be selecting, > > > > I had difficulty deciding what text to use for this prompt because many > > users can be expected to be using some non-default minibuffer completion > > supplemental package (eg. ido, icicles, ivy), and those all have > > slightly different behavior; many present a result-list without needing > > to TAB. > > Good point. For the plain old default, it would make a lot of sense to > eagerly popup the *Completions*, but I'm not sure how we could do it > without getting in the way for ivy and friends. Is this again something to consider implementing as a patch inside completing-read, ie. change the message/prompt inside completing-read; would users of the packages that commandeer the mini-buffer interface ("ivy and friends") see the completing-read prompt at that point? At that point, they've already commandeered the interface. -- hkp://keys.gnupg.net CA45 09B5 5351 7C11 A9D1 7286 0036 9E45 1595 8BC0 ^ permalink raw reply [flat|nested] 17+ messages in thread
* bug#43709: minibuffer keybinding cheatsheet and launcher [CODE SUBMISSION] 2020-10-01 13:51 ` Stefan Monnier 2020-10-01 15:31 ` Boruch Baum @ 2020-10-01 18:25 ` Boruch Baum 1 sibling, 0 replies; 17+ messages in thread From: Boruch Baum @ 2020-10-01 18:25 UTC (permalink / raw) To: Stefan Monnier; +Cc: 43709 On 2020-10-01 09:51, Stefan Monnier wrote: > > ;; If you've ever used packages such as `ivy' or `magit', you've > For those who haven't, it would be good to describe what your package does. In progress... > > (require 'cl-seq) ;; cl-member, cl-position > Please require `cl-lib`grather than `cl-seq` Done. > > (let ((not-found t)) > > (mapc (lambda (x) > > (when (string-match x (format "%s" cmd)) > > (setq not-found nil))) > > key-assist-exclude-regexps) > > not-found))) > > You should move the `(format "%s" cmd)` out of the loop (and use > `dolist` instead of `mapc`). Done, but I went with symbol-name instead of format. > > ((keymapp spec) > > (let (cmd) > > (dolist (elem spec) > > Please use `map-keymap` rather than `dolist` so as to handle all the > different keymap formats. I had tried that originally, but map-keymap brings in data from parent keymaps, which is undesirable in this case. There is a function map-keymap-internal which might be a usable alternative, but it returns a single unusable cruft element (the parent keymap) when it exists, so the result doesn't seem much better than the current code, no? > > (funcall (car (nth choice commands)))))) > > This should probably be `command-execute` rather than `funcall` to fix > the wrong-number-of-arguments error I bumped into. Great catch. It solves the bug you found. Done. As for posting updates to the code submission, I can do any combination of the following, or something else, but want guidance before proceeding: 1) post to this list; 2) post to the pre-existing thread on emacs-bugs; 3) create a github repo, and post there (and gain potential wider audience, more features, but fragment communication over two platforms); 4) create a repo on some imagined savannah / gnu server; Finally: Tomorrow approaching sunset I go offline for about nine days[1], so what I can squeeze in before then, great; afterwards I welcome any continued feedback but please be patient and don't expect any response for ~10-12 days. But really finally this time: I've twice already made passing reference to another package about ready to submit, that adds a lot of new functionality to dired. I can post it now in its current state, and inconsiderately disappear offline for 9+ days, or I could just as inconsiderately not post it and deny you for 9+ days. I'm undecided, so unless I hear otherwise, I'll wait. -- [1] https://en.wikipedia.org/wiki/Sukkot -- hkp://keys.gnupg.net CA45 09B5 5351 7C11 A9D1 7286 0036 9E45 1595 8BC0 ^ permalink raw reply [flat|nested] 17+ messages in thread
* bug#43709: minibuffer keybinding cheatsheet and launcher [V2 CODE SUBMISSION] 2020-09-29 16:59 bug#43709: minibuffer keybinding cheatsheet and launcher [CODE SUBMISSION] Boruch Baum 2020-09-29 17:41 ` Drew Adams 2020-10-01 13:51 ` Stefan Monnier @ 2020-10-02 7:14 ` Boruch Baum 2020-10-02 18:24 ` Drew Adams 2 siblings, 1 reply; 17+ messages in thread From: Boruch Baum @ 2020-10-02 7:14 UTC (permalink / raw) To: 43709; +Cc: Stefan Monnier, Emacs-Devel List [-- Attachment #1: Type: text/plain, Size: 208 bytes --] Here's an update to key-assist.el: 1) Improved commentary documentation and more helpful prompts 2) Fixes a launcher bug -- hkp://keys.gnupg.net CA45 09B5 5351 7C11 A9D1 7286 0036 9E45 1595 8BC0 [-- Attachment #2: key-assist.el --] [-- Type: text/plain, Size: 14310 bytes --] ;;; key-assist.el --- minibuffer keybinding cheatsheet and launcher -*- lexical-binding: t -*- ;; Copyright © 2020, Boruch Baum <boruch_baum@gmx.com> ;; Available for assignment to the Free Software Foundation, Inc. ;; Author: Boruch Baum <boruch_baum@gmx.com> ;; Maintainer: Boruch Baum <boruch_baum@gmx.com> ;; Keywords: minibuffer keybindings ;; Package: key-assist ;; This file is NOT part of GNU Emacs. ;; This 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. ;; This software 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 this software. If not, see <https://www.gnu.org/licenses/>. ;; ;;; Commentary: ;; For emacs *users*: This package provides an interactive command ;; to easily produce a keybinding cheat-sheet "on-the-fly", and then ;; to launch any command on the cheat-sheet list. At its simplest, ;; it gives the user a list of keybindings for commands specific to ;; the current buffer's major-mode, but it's trivially simple to ask ;; it to build an alternative (see below). ;; Use this package to: learn keybindings; learn what commands are ;; available specifically for the current buffer; run a command ;; from a descriptive list; and afterwards return to work quickly. ;; For emacs *programmers*: This package provides a simple, flexible ;; way to produce custom keybinding cheat-sheets and command ;; launchers for sets of commands, with each command being ;; described, along with its direct keybinding for direct use ;; without the launcher (see below). ;; If you've ever used packages such as `ivy' or `magit', you've ;; probably benefited from each's custom combination keybinding ;; cheatsheet and launcher: `hydra' in the case of `ivy', and ;; `transient' for `magit'. The current package `key-assist' offers ;; a generic and very simple alternative requiring only the ;; `completing-read' function commonly used in core vanilla emacs. ;; `key-assist' is trivial to implement "on-the-fly" interactively ;; for any buffer, and programmatically much simpler to customize ;; that either `hydra' or `transient'. And did I mention that it ;; only requires `completing-read'? ;; ;;; Dependencies: ;; `cl-lib': For `cl-member',`cl-position' ;; ;;; Installation: ;; 1) Evaluate or load this file. ;; ;;; Interactive operation: ;; Run M-x key-assist from the buffer of interest. Specify a ;; selection (or don't), press <TAB> to view the presentation, and ;; then either exit with your new-found knowledge of the command ;; keybindings, or use standard emacs tab completion to select an ;; option, and press <RETURN> to perform the action. ;; ;; If you choose not to respond to the initial prompt, a list of ;; keybindings and command descriptions will be generated based upon ;; the first word of the buffer's major mode. For, example, in a ;; `w3m' buffer, the list will be of all interactive functions ;; beginning `w3m-'. This works out to be great as a default, but ;; isn't always useful. For example, in an `emacs-lisp-mode' buffer, ;; what would you expect it to usefully produce? At the other ;; extreme might be a case of a buffer too many obscure keybindings ;; of little use. ;; You can also respond to the prompt with your own regexp of ;; commands to show, or with the name of a keymap of your choice. ;; For the purposes of `key-assist', a regexp can be just a ;; substring, without REQUIRING any leading or trailing globs. ;; In all cases, note that the package can only present keybindings ;; currently active in the current buffer, so if a sub-package ;; hasn't been loaded yet, that package's keybindings would not be ;; presented. Also note that the commands are presented sorted by ;; keybinding length, alphabetically. ;; ;;; Programmatic operation: ;; See the docstrings for functions `key-assist' and ;; `key-assist--get-cmds'. ;; ;;; Configuration: ;; Two variables are available to exclude items from the ;; presentation list: `key-assist-exclude-cmds' and ;; `key-assist-exclude-regexps'. See there for further information. ;; ;;; Compatability ;; Tested with emacs 26.1 in debian. ;; TODO: ;; ;; * In a vanilla emacs environment, the sorting appears wrong ;; seemingly partially because of how the completion buffer is ;; constructed, bottom-up. This isn't an issue when using one of ;; the supplemental minibuffer completion packages. \f ;; ;;; Code (require 'cl-lib) ;; cl-member, cl-position ;; ;;; Variables (defvar key-assist-exclude-cmds '(ignore self-insert-command digit-argument negative-argument describe-mode) "List of commands to always exclude from `key-assist' output.") (defvar key-assist-exclude-regexps '("-mouse-") "List of regexps of commands to exclude from `key-assist' output.") ;; TODO: Don't depend upon a mouse command having the word '-mouse-' in it. ;; ;;; Internal functions (defun key-assist--get-keybinding (cmd &optional key-map) "Return a string with CMD's shortest keybinding." (let (shortest) (dolist (key (mapcar 'key-description (where-is-internal cmd key-map nil t))) (when (or (not shortest) (> (length shortest) (length key))) (setq shortest key))) shortest)) (defun key-assist--get-description (cmd) "Return a string with CMD's description. CMD is a symbol of an interactive command." ;; TODO: Change hard-coded length to an ARG. (let ((doc (documentation cmd t))) (format "\t%s" (if (or (not doc) (not (string-match "\n" doc)) (zerop (match-beginning 0))) (concat (symbol-name cmd) " (not documented)") (substring doc 0 (match-beginning 0)))))) (defun key-assist--vet-cmd (cmd result-list) "Check whether CMD should be on a `key-assist' list. See `key-assist-exclude-cmds' and `key-assist-exclude-regexps'." (and (symbolp cmd) (commandp cmd) (not (cl-member cmd result-list :test (lambda (cmd l) (equal cmd (nth 1 l))))) ; (assq cmd result-list)) (not (memq cmd key-assist-exclude-cmds)) (let ((not-found t) (cmd-string (symbol-name cmd))) (dolist (regexp key-assist-exclude-regexps) (when (string-match regexp cmd-string) (setq not-found nil))) not-found))) (defun key-assist--parse-cmd (cmd result-list &optional key-map) "Extract commands and shortest keybindings from a keymap. This is an internal function used by `key-assist'. Returns a list whose elements are a keybinding string, a command symbol, and a description string." (when (key-assist--vet-cmd cmd result-list) (let* ((key-map (when (keymapp key-map) key-map)) (shortest (key-assist--get-keybinding cmd key-map))) (when shortest (list shortest cmd (concat shortest (key-assist--get-description cmd))))))) (defun key-assist--get-cmds (spec &optional nosort nofinish) "Return a list of commands, keybindings, and descriptions. Returns a list of CONS, whose CAR is the command, and whose CDR is a string of the form \"shortest-keybinding tab-character command-description\". Optional arg SPEC may be a regexp string of desired commands. If NIL, a regexp is generated based upon the first word of the buffer's major mode. SPEC may also be a keymap of desired commands. In both of these cases, the resulting list is sorted alphabetically by keybinding length. SPEC has additional options of being either a list of commands, or a list of CONS whose CAR is a command, and whose CDR is either a description-string or a function which returns a description string. A final programmatic option is for SPEC to be any combination of the above options. For that most complex case, the first list element of SPEC must be the symbol 'collection. For none of these additional options is sorting performed. Optional arg NOSORT can be a function to replace the default sort algorithm with the programmer's desired post-processing, or some other non-nil value for no sorting at all. If a function, it should accept a single list of elements (keybinding-string commandp description-string) and should return a list of elements (anything commandp description-string). Optional arg NOFINSH return a list in `key-assist--parse-cmd' format instead of the list of CONS described above. It is used internally for processing 'collection lists." (when (and spec (not (and (stringp spec) (zerop (length spec))))) (when (and (stringp spec) (boundp (intern spec)) (keymapp (symbol-value (intern spec)))) (setq spec (symbol-value (intern spec)))) (let (name result-elem (result-list '())) (cond ((keymapp spec) (let (cmd) (dolist (elem spec) (cond ((atom elem)) ;; eg. 'keymap ((listp (setq cmd (cdr elem)))) ;; TODO: possibly also embedded keymap? ((commandp cmd) ;; this excludes 'menubar (when (setq result-elem (key-assist--parse-cmd cmd result-list)) (push result-elem result-list))))))) ((stringp spec) (mapatoms (lambda (x) (and (commandp x) (string-match spec (setq name (symbol-name x))) (when (setq result-elem (key-assist--parse-cmd x result-list)) (push result-elem result-list)))))) ((listp spec) (cond ((eq (car spec) 'collection) (dolist (collection-element (cdr spec)) ;; Maybe it's more efficient to sort each collection element? (let ((temp-list (key-assist--get-cmds collection-element 'nosort 'nofinish))) (dolist (elem temp-list) (push elem result-list))))) ((commandp (car spec)) (dolist (cmd spec) (when (setq result-elem (key-assist--parse-cmd cmd result-list)) (push result-elem result-list)))) (t ; spec is a list of CONS (cmd . (or string function)) (dolist (elem spec) (when (key-assist--vet-cmd (car elem) result-list) (let ((shortest (key-assist--get-keybinding (car elem)))) (when shortest (push (list shortest (car elem) (if (stringp (cadr elem)) (cadr elem) (funcall (cadr elem)))) result-list)))))))) (t (error "Improper SPEC format."))) (when (not nosort) (setq result-list (if (functionp nosort) (funcall nosort result-list) (sort result-list (lambda (a b) (cond ((= (length (car a)) (length (car b))) (string< (car a) (car b))) ((< (length (car a)) (length (car b)))) (t nil))))))) (if nofinish result-list (mapcar (lambda (x) (cons (nth 1 x) (nth 2 x))) result-list))))) ;; ;;; Interactive functions (defun key-assist (&optional spec prompt nosort) "Prompt to eval a locally relevant function, with hints and keybindings. Press TAB to see the hints. Interactively, the optional arg SPEC is either a regexp string for candidate commands to match, or a keymap from which to prepare the hints. If NIL, a regexp is generated based upon the first word of the buffer's major mode. Results are presented sorted alphabetically by keybinding length. Programmatically, optional arg PROMPT can be used to customize the prompt. For the further programmatic options of SPEC and for a description of arg NOSORT, see function `key-assist--get-cmds'. See also variables `key-assist-exclude-regexps' and `key-assist-exclude-cmds'." (interactive) (when (not spec) (setq spec (symbol-name major-mode) spec (substring spec 0 (1+ (string-match "-" spec))) spec (read-regexp (format "Press RET for keybinding cheatsheet/launcher for \"%s\" commands, Or enter a different command regexp or keymap name: " spec) spec))) (when (or (not spec) (and (stringp spec) (zerop (length spec)))) (user-error "Nothing to do!")) (let (commands choices choice minibuffer-history) (while (not choices) (setq commands (key-assist--get-cmds spec nosort)) (when (not (setq choices (mapcar 'cdr commands))) (setq spec (read-regexp (format "No choices found for \"%s\". Try a differernt command regexp or keymap name: " spec) spec)))) (while (not (setq choice (cl-position (completing-read (or prompt "You may need to press TAB to see the result list. Select an item on the list to launch it: ") choices nil t) choices :test 'equal)))) (command-execute (car (nth choice commands))))) \f ;; ;;; Conclusion ;; FIXME: This command can be very slow for very large regexp ;; collections. Try giving it ".*" and see how long it takes to ;; collect and present (for me, ~460 results). (provide 'key-assist) ;;; key-assist.el ends here ^ permalink raw reply [flat|nested] 17+ messages in thread
* bug#43709: minibuffer keybinding cheatsheet and launcher [V2 CODE SUBMISSION] 2020-10-02 7:14 ` bug#43709: minibuffer keybinding cheatsheet and launcher [V2 CODE SUBMISSION] Boruch Baum @ 2020-10-02 18:24 ` Drew Adams 2020-10-15 18:53 ` bug#43709: minibuffer keybinding cheatsheet and launcher [V3 " Boruch Baum 0 siblings, 1 reply; 17+ messages in thread From: Drew Adams @ 2020-10-02 18:24 UTC (permalink / raw) To: Boruch Baum, 43709; +Cc: Emacs-Devel List Please don't send messages to both the bug list and emacs-devel. Please pick one. Thx. ^ permalink raw reply [flat|nested] 17+ messages in thread
* bug#43709: minibuffer keybinding cheatsheet and launcher [V3 CODE SUBMISSION] 2020-10-02 18:24 ` Drew Adams @ 2020-10-15 18:53 ` Boruch Baum 2020-10-15 18:57 ` Drew Adams 2021-07-21 12:14 ` bug#43709: minibuffer keybinding cheatsheet and launcher [CODE SUBMISSION] Lars Ingebrigtsen 0 siblings, 2 replies; 17+ messages in thread From: Boruch Baum @ 2020-10-15 18:53 UTC (permalink / raw) To: 43709; +Cc: emacs-devel [-- Attachment #1: Type: text/plain, Size: 338 bytes --] It's been two weeks since I heard any feedback on this proposal. Can I nudge? What's the decision for future action or inaction on this? Attached is the same content with some minor documentation changes, mainly to comply with checkdoc and package-lint. -- hkp://keys.gnupg.net CA45 09B5 5351 7C11 A9D1 7286 0036 9E45 1595 8BC0 [-- Attachment #2: key-assist.el --] [-- Type: text/plain, Size: 14921 bytes --] ;;; key-assist.el --- Minibuffer keybinding cheatsheet and launcher -*- lexical-binding: t -*- ;; Copyright © 2020, Boruch Baum <boruch_baum@gmx.com> ;; Available for assignment to the Free Software Foundation, Inc. ;; Author: Boruch Baum <boruch_baum@gmx.com> ;; Maintainer: Boruch Baum <boruch_baum@gmx.com> ;; Homepage: --------------------- ;; Keywords: abbrev convenience docs help ;; Package: key-assist ;; Package-Version: 1.0 ;; Package-Requires: ((emacs "24.3")) ;; (emacs "24.3") for: lexical-binding, user-error, cl-lib ;; This file is NOT part of GNU Emacs. ;; This 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. ;; This software 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 this software. If not, see <https://www.gnu.org/licenses/>. ;; ;;; Commentary: ;; For Emacs *users*: This package provides an interactive command ;; to easily produce a keybinding cheat-sheet "on-the-fly", and then ;; to launch any command on the cheat-sheet list. At its simplest, ;; it gives the user a list of keybindings for commands specific to ;; the current buffer's major-mode, but it's trivially simple to ask ;; it to build an alternative (see below). ;; Use this package to: learn keybindings; learn what commands are ;; available specifically for the current buffer; run a command ;; from a descriptive list; and afterwards return to work quickly. ;; For Emacs *programmers*: This package provides a simple, flexible ;; way to produce custom keybinding cheat-sheets and command ;; launchers for sets of commands, with each command being ;; described, along with its direct keybinding for direct use ;; without the launcher (see below). ;; If you've ever used packages such as `ivy' or `magit', you've ;; probably benefited from each's custom combination keybinding ;; cheatsheet and launcher: `hydra' in the case of `ivy', and ;; `transient' for `magit'. The current package `key-assist' offers ;; a generic and very simple alternative requiring only the ;; `completing-read' function commonly used in core vanilla Emacs. ;; `key-assist' is trivial to implement "on-the-fly" interactively ;; for any buffer, and programmatically much simpler to customize ;; that either `hydra' or `transient'. And did I mention that it ;; only requires `completing-read'? ;; ;;; Dependencies: ;; `cl-lib': For `cl-member',`cl-position' ;; ;;; Installation: ;; 1) Evaluate or load this file. ;; ;;; Interactive operation: ;; Run M-x `key-assist' from the buffer of interest. Specify a ;; selection (or don't), press <TAB> to view the presentation, and ;; then either exit with your new-found knowledge of the command ;; keybindings, or use standard Emacs tab completion to select an ;; option, and press <RETURN> to perform the action. ;; ;; If you choose not to respond to the initial prompt, a list of ;; keybindings and command descriptions will be generated based upon ;; the first word of the buffer's major mode. For, example, in a ;; `w3m' buffer, the list will be of all interactive functions ;; beginning `w3m-'. This works out to be great as a default, but ;; isn't always useful. For example, in an `emacs-lisp-mode' buffer ;; or a `lisp-interaction-mode', what would you expect it to ;; usefully produce? At the other extreme might be a case of a ;; buffer with too many obscure keybindings of little use. ;; You can also respond to the prompt with your own regexp of ;; commands to show, or with the name of a keymap of your choice. ;; For the purposes of `key-assist', a regexp can be just a ;; substring, without REQUIRING any leading or trailing globs. ;; In all cases, note that the package can only present keybindings ;; currently active in the current buffer, so if a sub-package ;; hasn't been loaded yet, that package's keybindings would not be ;; presented. Also note that the commands are presented sorted by ;; keybinding length, alphabetically. ;; ;;; Programmating example: ;; Here's a most simple example that presents all of the keybindings ;; for 'my-mode: ;; ;; (defun my-mode-keybinding-cheatsheet-launcher () ;; (interactive) ;; (when (eq major-mode my-mode) ;; (key-assist))) ;; (define-key my-mode-map "?" ;; 'my-mode-keybinding-cheatsheet-launcher) ;; See the docstrings for functions `key-assist' and ;; `key-assist--get-cmds' for the description of ARGS that can be ;; used to customize the output. ;; ;;; Configuration: ;; Two variables are available to exclude items from the ;; presentation list: `key-assist-exclude-cmds' and ;; `key-assist-exclude-regexps'. See there for further information. ;; ;;; Compatability ;; Tested with Emacs 26.1 and emacs-snapshot 28(~2020-09-16), both in ;; debian. \f ;; ;;; Code: (require 'cl-lib) ;; cl-member, cl-position ;; ;;; Variables: (defvar key-assist-exclude-cmds '(ignore self-insert-command digit-argument negative-argument describe-mode) "List of commands to always exclude from `key-assist' output.") (defvar key-assist-exclude-regexps '("-mouse-") "List of regexps of commands to exclude from `key-assist' output.") ;; TODO: Don't depend upon a mouse command having the word '-mouse-' in it. ;; ;;; Internal functions: (defun key-assist--get-keybinding (cmd &optional key-map) "Return a string with CMD's shortest keybinding. Optional arg KEY-MAP defaults to local map." (let (shortest) (dolist (key (mapcar 'key-description (where-is-internal cmd key-map nil t))) (when (or (not shortest) (> (length shortest) (length key))) (setq shortest key))) shortest)) (defun key-assist--get-description (cmd) "Return a string with CMD's description. CMD is a symbol of an interactive command." ;; TODO: Change hard-coded length to an ARG. (let ((doc (documentation cmd t))) (format "\t%s" (if (or (not doc) (not (string-match "\n" doc)) (zerop (match-beginning 0))) (concat (symbol-name cmd) " (not documented)") (substring doc 0 (match-beginning 0)))))) (defun key-assist--vet-cmd (cmd result-list) "Check whether CMD should be on a `key-assist' list. Each element of RESULT-LIST is a CMD already accepted, in the form '(keybinding-string, CMD, description-string). See `key-assist-exclude-cmds' and `key-assist-exclude-regexps'." (and (symbolp cmd) (commandp cmd) (not (cl-member cmd result-list :test (lambda (cmd l) (equal cmd (nth 1 l))))) (not (memq cmd key-assist-exclude-cmds)) (let ((not-found t) (cmd-string (symbol-name cmd))) (dolist (regexp key-assist-exclude-regexps) (when (string-match regexp cmd-string) (setq not-found nil))) not-found))) (defun key-assist--parse-cmd (cmd result-list &optional key-map) "Extract a command and shortest keybinding from a keymap. If KEY-MAP is nil, use the local map, and look for CMD there. Each element of RESULT-LIST is a CMD already accepted, in the form '(keybinding-string, CMD, description-string). This is an internal function used by `key-assist'. Returns a list whose elements are a keybinding string, a command symbol, and a description string." (when (key-assist--vet-cmd cmd result-list) (let* ((key-map (when (keymapp key-map) key-map)) (shortest (key-assist--get-keybinding cmd key-map))) (when shortest (list shortest cmd (concat shortest (key-assist--get-description cmd))))))) (defun key-assist--get-cmds (spec &optional nosort nofinish) "Return a list of commands, keybindings, and descriptions. Returns a list of CONS, whose CAR is the command, and whose CDR is a string of the form \"shortest-keybinding tab-character command-description\". Optional arg SPEC may be a regexp string of desired commands. If NIL, a regexp is generated based upon the first word of the buffer's major mode. SPEC may also be a keymap of desired commands. In both of these cases, the resulting list is sorted alphabetically by keybinding length. SPEC has additional options of being either a list of commands, or a list of CONS whose CAR is a command, and whose CDR is either a description-string or a function which returns a description string. A final programmatic option is for SPEC to be any combination of the above options. For that most complex case, the first list element of SPEC must be the symbol 'collection. For none of these additional options is sorting performed. Optional arg NOSORT can be a function to replace the default sort algorithm with the programmer's desired post-processing, or some other non-nil value for no sorting at all. If a function, it should accept a single list of elements (keybinding-string commandp description-string) and should return a list of elements (anything commandp description-string). Optional arg NOFINISH return a list in `key-assist--parse-cmd' format instead of the list of CONS described above. It is used internally for processing 'collection lists." (when (and spec (not (and (stringp spec) (zerop (length spec))))) (when (and (stringp spec) (boundp (intern spec)) (keymapp (symbol-value (intern spec)))) (setq spec (symbol-value (intern spec)))) (let (result-elem (result-list '())) (cond ((keymapp spec) (let (cmd) (dolist (elem spec) (cond ((atom elem)) ;; eg. 'keymap ((listp (setq cmd (cdr elem)))) ;; TODO: possibly also embedded keymap? ((commandp cmd) ;; this excludes 'menubar (when (setq result-elem (key-assist--parse-cmd cmd result-list)) (push result-elem result-list))))))) ((stringp spec) (mapatoms (lambda (x) (and (commandp x) (string-match spec (symbol-name x)) (when (setq result-elem (key-assist--parse-cmd x result-list)) (push result-elem result-list)))))) ((listp spec) (cond ((eq (car spec) 'collection) (dolist (collection-element (cdr spec)) ;; Maybe it's more efficient to sort each collection element? (let ((temp-list (key-assist--get-cmds collection-element 'nosort 'nofinish))) (dolist (elem temp-list) (push elem result-list))))) ((commandp (car spec)) (dolist (cmd spec) (when (setq result-elem (key-assist--parse-cmd cmd result-list)) (push result-elem result-list)))) (t ; spec is a list of CONS (cmd . (or string function)) (dolist (elem spec) (when (key-assist--vet-cmd (car elem) result-list) (let ((shortest (key-assist--get-keybinding (car elem)))) (when shortest (push (list shortest (car elem) (if (stringp (cadr elem)) (cadr elem) (funcall (cadr elem)))) result-list)))))))) (t (error "Improper SPEC format"))) (when (not nosort) (setq result-list (if (functionp nosort) (funcall nosort result-list) (sort result-list (lambda (a b) (cond ((= (length (car a)) (length (car b))) (string< (car a) (car b))) ((< (length (car a)) (length (car b)))) (t nil))))))) (if nofinish result-list (mapcar (lambda (x) (cons (nth 1 x) (nth 2 x))) result-list))))) ;; ;;; Interactive functions: (defun key-assist (&optional spec prompt nosort) "Prompt to eval a locally relevant function, with hints and keybindings. Press TAB to see the hints. Interactively, the optional arg SPEC is either a regexp string for candidate commands to match, or a keymap from which to prepare the hints. If NIL, a regexp is generated based upon the first word of the buffer's major mode. Results are presented sorted alphabetically by keybinding length. Programmatically, optional arg PROMPT can be used to customize the prompt. For the further programmatic options of SPEC and for a description of arg NOSORT, see function `key-assist--get-cmds'. See also variables `key-assist-exclude-regexps' and `key-assist-exclude-cmds'." (interactive) (when (not spec) (setq spec (symbol-name major-mode) spec (substring spec 0 (1+ (string-match "-" spec))) spec (read-regexp (format "Press RET for keybinding cheatsheet/launcher for \"%s\" commands, Or enter a different command regexp or keymap name: " spec) spec))) (when (or (not spec) (and (stringp spec) (zerop (length spec)))) (user-error "Nothing to do!")) (let (commands choices choice minibuffer-history) (while (not choices) (setq commands (key-assist--get-cmds spec nosort)) (when (not (setq choices (mapcar 'cdr commands))) (setq spec (read-regexp (format "No choices found for \"%s\". Try a differernt command regexp or keymap name: " spec) spec)))) (while (not (setq choice (cl-position (completing-read (or prompt "You may need to press TAB to see the result list. Select an item on the list to launch it: ") choices nil t) choices :test 'equal)))) (command-execute (car (nth choice commands))))) \f ;; ;;; Conclusion: (provide 'key-assist) ;;; key-assist.el ends here ;; NOTE: For integration into emacs: ;; * defcustoms should include :version "28.1" ^ permalink raw reply [flat|nested] 17+ messages in thread
* bug#43709: minibuffer keybinding cheatsheet and launcher [V3 CODE SUBMISSION] 2020-10-15 18:53 ` bug#43709: minibuffer keybinding cheatsheet and launcher [V3 " Boruch Baum @ 2020-10-15 18:57 ` Drew Adams 2021-07-21 12:14 ` bug#43709: minibuffer keybinding cheatsheet and launcher [CODE SUBMISSION] Lars Ingebrigtsen 1 sibling, 0 replies; 17+ messages in thread From: Drew Adams @ 2020-10-15 18:57 UTC (permalink / raw) To: Boruch Baum, 43709; +Cc: emacs-devel Please don't send the same message to both the bug list and emacs-devel. ^ permalink raw reply [flat|nested] 17+ messages in thread
* bug#43709: minibuffer keybinding cheatsheet and launcher [CODE SUBMISSION] 2020-10-15 18:53 ` bug#43709: minibuffer keybinding cheatsheet and launcher [V3 " Boruch Baum 2020-10-15 18:57 ` Drew Adams @ 2021-07-21 12:14 ` Lars Ingebrigtsen 2021-07-21 13:48 ` Boruch Baum 1 sibling, 1 reply; 17+ messages in thread From: Lars Ingebrigtsen @ 2021-07-21 12:14 UTC (permalink / raw) To: Boruch Baum; +Cc: 43709 Boruch Baum <boruch_baum@gmx.com> writes: > It's been two weeks since I heard any feedback on this proposal. Can I > nudge? What's the decision for future action or inaction on this? I've tried out this package, and it's interesting, but I don't think it's generally useful enough to include in Emacs. But packaging it up for ELPA might make sense. -- (domestic pets only, the antidote for overdose, milk.) bloggy blog: http://lars.ingebrigtsen.no ^ permalink raw reply [flat|nested] 17+ messages in thread
* bug#43709: minibuffer keybinding cheatsheet and launcher [CODE SUBMISSION] 2021-07-21 12:14 ` bug#43709: minibuffer keybinding cheatsheet and launcher [CODE SUBMISSION] Lars Ingebrigtsen @ 2021-07-21 13:48 ` Boruch Baum 2021-07-22 6:49 ` Phil Sainty 0 siblings, 1 reply; 17+ messages in thread From: Boruch Baum @ 2021-07-21 13:48 UTC (permalink / raw) To: Lars Ingebrigtsen; +Cc: 43709 On 2021-07-21 14:14, Lars Ingebrigtsen wrote: > Boruch Baum <boruch_baum@gmx.com> writes: > > > It's been two weeks since I heard any feedback on this proposal. Can I > > nudge? What's the decision for future action or inaction on this? > > I've tried out this package, and it's interesting, but I don't think > it's generally useful enough to include in Emacs. But packaging it up > for ELPA might make sense. > Done. + https://github.com/melpa/melpa/pull/7198 + https://melpa.org/#/key-assist -- hkp://keys.gnupg.net CA45 09B5 5351 7C11 A9D1 7286 0036 9E45 1595 8BC0 ^ permalink raw reply [flat|nested] 17+ messages in thread
* bug#43709: minibuffer keybinding cheatsheet and launcher [CODE SUBMISSION] 2021-07-21 13:48 ` Boruch Baum @ 2021-07-22 6:49 ` Phil Sainty 0 siblings, 0 replies; 17+ messages in thread From: Phil Sainty @ 2021-07-22 6:49 UTC (permalink / raw) To: Boruch Baum; +Cc: Lars Ingebrigtsen, 43709 On 2021-07-22 01:48, Boruch Baum wrote: > On 2021-07-21 14:14, Lars Ingebrigtsen wrote: >> I've tried out this package, and it's interesting, but I don't think >> it's generally useful enough to include in Emacs. But packaging it up >> for ELPA might make sense. > > Done. > + https://github.com/melpa/melpa/pull/7198 > + https://melpa.org/#/key-assist I suspect the suggestion was GNU ELPA rather than MELPA. In any case, as you've already proposed it for Emacs, do consider submitting it to GNU ELPA. -Phil ^ permalink raw reply [flat|nested] 17+ messages in thread
end of thread, other threads:[~2021-07-22 6:49 UTC | newest] Thread overview: 17+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2020-09-29 16:59 bug#43709: minibuffer keybinding cheatsheet and launcher [CODE SUBMISSION] Boruch Baum 2020-09-29 17:41 ` Drew Adams 2020-09-29 18:22 ` Boruch Baum 2020-09-29 19:05 ` Drew Adams 2020-09-29 18:26 ` Boruch Baum 2020-10-01 13:51 ` Stefan Monnier 2020-10-01 15:31 ` Boruch Baum 2020-10-01 16:04 ` Stefan Monnier 2020-10-01 20:46 ` Boruch Baum 2020-10-01 18:25 ` Boruch Baum 2020-10-02 7:14 ` bug#43709: minibuffer keybinding cheatsheet and launcher [V2 CODE SUBMISSION] Boruch Baum 2020-10-02 18:24 ` Drew Adams 2020-10-15 18:53 ` bug#43709: minibuffer keybinding cheatsheet and launcher [V3 " Boruch Baum 2020-10-15 18:57 ` Drew Adams 2021-07-21 12:14 ` bug#43709: minibuffer keybinding cheatsheet and launcher [CODE SUBMISSION] Lars Ingebrigtsen 2021-07-21 13:48 ` Boruch Baum 2021-07-22 6:49 ` Phil Sainty
Code repositories for project(s) associated with this public inbox https://git.savannah.gnu.org/cgit/emacs.git This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox; as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).