From mboxrd@z Thu Jan 1 00:00:00 1970 Path: news.gmane.io!.POSTED.blaine.gmane.org!not-for-mail From: Boruch Baum Newsgroups: gmane.emacs.bugs,gmane.emacs.devel Subject: bug#43709: minibuffer keybinding cheatsheet and launcher [V3 CODE SUBMISSION] Date: Thu, 15 Oct 2020 14:53:48 -0400 Message-ID: <20201015185347.bgavyydizae7qn3f@E15-2016.optimum.net> References: <20201002071419.qw33jc3rpcywlucm@E15-2016.optimum.net> <99eeecac-729c-42fc-8b51-67a0e8de14f5@default> Mime-Version: 1.0 Content-Type: multipart/mixed; boundary="kqx2hg7tqgnnrbox" Injection-Info: ciao.gmane.io; posting-host="blaine.gmane.org:116.202.254.214"; logging-data="38363"; mail-complaints-to="usenet@ciao.gmane.io" User-Agent: NeoMutt/20180716 Cc: emacs-devel@gnu.org To: 43709@debbugs.gnu.org Original-X-From: bug-gnu-emacs-bounces+geb-bug-gnu-emacs=m.gmane-mx.org@gnu.org Thu Oct 15 20:55:43 2020 Return-path: Envelope-to: geb-bug-gnu-emacs@m.gmane-mx.org Original-Received: from lists.gnu.org ([209.51.188.17]) by ciao.gmane.io with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.92) (envelope-from ) id 1kT8Pe-0009rI-Sa for geb-bug-gnu-emacs@m.gmane-mx.org; Thu, 15 Oct 2020 20:55:43 +0200 Original-Received: from localhost ([::1]:55658 helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1kT8Pd-0004cn-B1 for geb-bug-gnu-emacs@m.gmane-mx.org; Thu, 15 Oct 2020 14:55:41 -0400 Original-Received: from eggs.gnu.org ([2001:470:142:3::10]:45214) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1kT8P1-0004cQ-94 for bug-gnu-emacs@gnu.org; Thu, 15 Oct 2020 14:55:03 -0400 Original-Received: from debbugs.gnu.org ([209.51.188.43]:45008) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.90_1) (envelope-from ) id 1kT8Oz-0000Ai-SD for bug-gnu-emacs@gnu.org; Thu, 15 Oct 2020 14:55:02 -0400 Original-Received: from Debian-debbugs by debbugs.gnu.org with local (Exim 4.84_2) (envelope-from ) id 1kT8Oz-00065F-RY for bug-gnu-emacs@gnu.org; Thu, 15 Oct 2020 14:55:01 -0400 X-Loop: help-debbugs@gnu.org Resent-From: Boruch Baum Original-Sender: "Debbugs-submit" Resent-CC: bug-gnu-emacs@gnu.org Resent-Date: Thu, 15 Oct 2020 18:55:01 +0000 Resent-Message-ID: Resent-Sender: help-debbugs@gnu.org X-GNU-PR-Message: followup 43709 X-GNU-PR-Package: emacs X-GNU-PR-Keywords: patch X-Debbugs-Original-Cc: 43709@debbugs.gnu.org, Emacs-Devel List Original-Received: via spool by 43709-submit@debbugs.gnu.org id=B43709.160278804423315 (code B ref 43709); Thu, 15 Oct 2020 18:55:01 +0000 Original-Received: (at 43709) by debbugs.gnu.org; 15 Oct 2020 18:54:04 +0000 Original-Received: from localhost ([127.0.0.1]:56554 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1kT8O3-00063y-AX for submit@debbugs.gnu.org; Thu, 15 Oct 2020 14:54:03 -0400 Original-Received: from mout.gmx.net ([212.227.17.21]:56331) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1kT8O1-00063S-DE for 43709@debbugs.gnu.org; Thu, 15 Oct 2020 14:54:02 -0400 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=gmx.net; s=badeba3b8450; t=1602788033; bh=mvgDYNglJuPPVX1TTn5F0Dcxgpdx0NEAP9TjoK14Lhg=; h=X-UI-Sender-Class:Date:From:To:Cc:Subject:References:In-Reply-To; b=k9b9vLbxaeiD6EgMHZJp+hdz5tZCX2zlriNXLl35wzcs/tcysUNug9L6j8l1H/RCk VrFhOypByPC2K6o4pXVmtDQD0y8Md5d7F22QaalV3jkhpQuaBx+yNUDJx+7hYfKMuL zf6QGo9F9tLlUsnkKnr0vySCoqvw6nEhEGVCOH4k= X-UI-Sender-Class: 01bb95c1-4bf8-414a-932a-4f6e2808ef9c Original-Received: from E15-2016.optimum.net ([72.89.170.172]) by mail.gmx.com (mrgmx105 [212.227.17.174]) with ESMTPSA (Nemesis) id 1MVeMG-1kujvZ0W3g-00RVaI; Thu, 15 Oct 2020 20:53:53 +0200 Content-Disposition: inline In-Reply-To: <99eeecac-729c-42fc-8b51-67a0e8de14f5@default> X-Provags-ID: V03:K1:Tlns5B14HEv1nk9hN8Ct4KQMAZtX5wnclAzuKmCscLj4tfNdyQ5 3YToInw8JtC5OQoGPzHh1ahTmh6BWaiPqWm+KJ2f2EAHiVs9gDL5pxYh88OF1glmnOzWha2 PnKwqRuchXJja2W9xObTxR2mCff6UR+5vLj+vMZrktV80JwHWxWKUskMLEsLXKePEjLP4FQ V8zE7t9SOSouM/dTIrz6Q== X-UI-Out-Filterresults: notjunk:1;V03:K0:G9yTqjhDMqI=:CyuzlRtJGrjhbX5c7xvisy LRR40LzNiVnT8iUvcA0pk2pdvcd3zQQZtSVJHj6ovda2med1N1ENMjDDfcFbiyWWbf/maMX+I 659Z7f98fUjFHM0vJ2OyhocoEF+Aw3msTOrR84qAZtC+gqbhw51xihZohR4C2WnG7gqakRvBF LlwMM1v3gZLmX7M536Y6OU58fHx5I+WrBI+jZvAM4/JslDeiwjyNs90f7lPpRBTbJ9Bav+Y6P bFrJ6vdZ/ZTPWoxWMoaqhjZPlOsjUbX1cZNIILNaUehkCHHBV64UCc+Opo4GOtsFmUP+tAScW 1xJVSXjhgk135xwKduQoT+NS0CLh0Kgg54jmI2/S0PWmTZLaNlq/+EvaJCFcFuOThB67s9Vnu /Bq/uVmb1LKorr54o5t+6K8hghtFwoKH+CX/V/0Ko3EnB2ySQCqBpXZO5cY0uXN3PsuavFx5v ye2pK9oG7bS1Kj0lUbmTNzuGTYlS5QnRyUsJdr6DV9pG8wwefIn1doJms83YTg+0AxxZU/60n bJoHd4FjPDjRjcXNF/1PBFpgMv1K5aRuMviWjWhe6231LBFlWyzFkzaqrUi2d0SXEskhYrXxN N+9tdZ51s3FtHXfel2tcIL2XB/ic3qJVJpp6ZfGHcPx0i/m/NmA8k4p4q8+3LLYq5LXHtlarb W+Reo13WD5qOsZ+J4DHsZ4TWfZM0ra6DSsnGuB9k5Z7ztMd/CL5iJ2iTcuVchymdjDZBZgw8i VaO9GI8Ltu0TWN9i/S3/lUMPmXf77dJ6QpjQorv3gIFyEv6X4/Ox+7MVCxObcfi7gSxLYhWl X-BeenThere: debbugs-submit@debbugs.gnu.org X-Mailman-Version: 2.1.18 Precedence: list X-BeenThere: bug-gnu-emacs@gnu.org List-Id: "Bug reports for GNU Emacs, the Swiss army knife of text editors" List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: bug-gnu-emacs-bounces+geb-bug-gnu-emacs=m.gmane-mx.org@gnu.org Original-Sender: "bug-gnu-emacs" Xref: news.gmane.io gmane.emacs.bugs:190623 gmane.emacs.devel:257757 Archived-At: --kqx2hg7tqgnnrbox Content-Type: text/plain; charset=us-ascii Content-Disposition: inline Content-Transfer-Encoding: quoted-printable 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. =2D- hkp://keys.gnupg.net CA45 09B5 5351 7C11 A9D1 7286 0036 9E45 1595 8BC0 --kqx2hg7tqgnnrbox Content-Type: text/plain; charset=iso-8859-1 Content-Disposition: attachment; filename="key-assist.el" Content-Transfer-Encoding: quoted-printable ;;; key-assist.el --- Minibuffer keybinding cheatsheet and launcher -*- le= xical-binding: t -*- ;; Copyright =A9 2020, Boruch Baum ;; Available for assignment to the Free Software Foundation, Inc. ;; Author: Boruch Baum ;; Maintainer: Boruch Baum ;; 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 . ;; ;;; 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 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 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. =0C ;; ;;; 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 c= md))))))) (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 embed= ded 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 'n= osort '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-li= st)) (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 ((=3D (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 r= esult list. Select an item on the list to launch it: ") choices nil t) choices :test 'equal)))) (command-execute (car (nth choice commands))))) =0C ;; ;;; Conclusion: (provide 'key-assist) ;;; key-assist.el ends here ;; NOTE: For integration into emacs: ;; * defcustoms should include :version "28.1" --kqx2hg7tqgnnrbox--