]> git.eshelyaron.com Git - emacs.git/commitdiff
Allow toggling completion modes for `M-x' with `M-X'
authorLars Ingebrigtsen <larsi@gnus.org>
Fri, 24 Jun 2022 18:16:51 +0000 (20:16 +0200)
committerLars Ingebrigtsen <larsi@gnus.org>
Fri, 24 Jun 2022 18:18:21 +0000 (20:18 +0200)
* doc/lispref/commands.texi (Interactive Call): Document it.
* lisp/minibuffer.el (minibuffer-local-must-match-map): Bind 'M-X'.

* lisp/simple.el (execute-extended-command-cycle): New command.
(read-extended-command): Use it to allow toggling (bug#47215).
(read-extended-command-1): Renamed from `read-extended-command'.
(execute-extended-command-for-buffer): Factored out most of the
code...
(command-completion--command-for-this-buffer-function): ... to
here.
(extended-command-versions): New variable.

This code is based on a patch by Felician Nemeth
<felician.nemeth@gmail.com>.

doc/lispref/commands.texi
etc/NEWS
lisp/minibuffer.el
lisp/simple.el

index 6f022183336fcd4e3459ee3adcfd04c4e4f3f87d..ed32814329b1261297b06c5a13477cdded2404ba 100644 (file)
@@ -897,6 +897,10 @@ keymaps.  This command is the normal definition of @kbd{M-S-x}
 (that's ``meta shift x'').
 @end deffn
 
+Both these commands prompt for a command name, but with different
+completion rules.  You can toggle between these two modes by using the
+@kbd{M-S-x} command while being prompted.
+
 @node Distinguish Interactive
 @section Distinguish Interactive Calls
 @cindex distinguish interactive calls
index cb9e7417b6ebf45b45f002038dd0223f036a8e95..e3b4df227eb624143cc46bf5bec06d8693886fed 100644 (file)
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -314,6 +314,16 @@ startup.  Previously, these functions ignored
 \f
 * Changes in Emacs 29.1
 
++++
+** New key binding after 'M-x' or 'M-X': 'M-X'.
+Emacs allows different completion predicates to be used with 'M-x'
+(i.e., 'execute-extended-command') via the
+'read-extended-command-predicate' user option.  Emacs also has the
+'M-X' (note upper case) command, which only displays commands
+especially relevant to the current buffer.  Emacs now allows toggling
+between these modes while the user is inputting a command by hitting
+'M-X' while in the minibuffer.
+
 ---
 ** Interactively, 'kill-buffer' will now offer to save the buffer if unsaved.
 
index e42d83af3426e90ebba602292b4f245d147ea15f..9ffaff7c8e28a912e4a42222317f9659b67e23e7 100644 (file)
@@ -2780,6 +2780,7 @@ The completion method is determined by `completion-at-point-functions'."
 (defvar-keymap minibuffer-local-must-match-map
   :doc "Local keymap for minibuffer input with completion, for exact match."
   :parent minibuffer-local-completion-map
+  "M-X" #'execute-extended-command-cycle
   "RET" #'minibuffer-complete-and-exit
   "C-j" #'minibuffer-complete-and-exit)
 
index 653cffae62ca5e979e8a18d75463f9b04d4b105f..8f82ff3a8e855a5a55e24ca70da93723eb718c09 100644 (file)
@@ -2207,9 +2207,53 @@ in that buffer."
                         command-completion-default-include-p)
                  (function :tag "Other function")))
 
-(defun read-extended-command ()
+(defun execute-extended-command-cycle ()
+  "Choose the next version of the extended command predicates.
+See `extended-command-versions'."
+  (interactive)
+  (throw 'cycle
+         (cons (minibuffer-contents)
+               (- (point) (minibuffer-prompt-end)))))
+
+(defvar extended-command-versions
+  (list (list "M-x " (lambda () read-extended-command-predicate))
+        (list "M-X " #'command-completion--command-for-this-buffer-function))
+  "Alist of prompts and what the extended command predicate should be.
+This is used by the \\<minibuffer-local-must-match-map>\\[execute-extended-command-cycle] command when reading an extended command.")
+
+(defun read-extended-command (&optional prompt)
   "Read command name to invoke in `execute-extended-command'.
 This function uses the `read-extended-command-predicate' user option."
+  (let ((default-predicate read-extended-command-predicate)
+        (read-extended-command-predicate read-extended-command-predicate)
+        already-typed ret)
+    ;; If we have a prompt (which is the name of the version of the
+    ;; command), then set up the predicate from
+    ;; `extended-command-versions'.
+    (if (not prompt)
+        (setq prompt (caar extended-command-versions))
+      (setq read-extended-command-predicate
+            (funcall (cadr (assoc prompt extended-command-versions)))))
+    ;; Normally this will only execute once.
+    (while (not (stringp ret))
+      (when (consp (setq ret (catch 'cycle
+                               (read-extended-command-1 prompt
+                                                        already-typed))))
+        ;; But if the user hit `M-X', then we `throw'ed out to that
+        ;; `catch', and we cycle to the next setting.
+        (let ((next (or (cadr (memq (assoc prompt extended-command-versions)
+                                    extended-command-versions))
+                        ;; Last one; cycle back to the first.
+                        (car extended-command-versions))))
+          ;; Restore the user's default predicate.
+          (setq read-extended-command-predicate default-predicate)
+          ;; Then calculate the next.
+          (setq prompt (car next)
+                read-extended-command-predicate (funcall (cadr next))
+                already-typed ret))))
+    ret))
+
+(defun read-extended-command-1 (prompt initial-input)
   (let ((buffer (current-buffer)))
     (minibuffer-with-setup-hook
         (lambda ()
@@ -2234,8 +2278,8 @@ This function uses the `read-extended-command-predicate' user option."
                              (cons def (delete def all))
                            all)))))
       ;; Read a string, completing from and restricting to the set of
-      ;; all defined commands.  Don't provide any initial input.
-      ;; Save the command read on the extended-command history list.
+      ;; all defined commands.  Save the command read on the
+      ;; extended-command history list.
       (completing-read
        (concat (cond
                ((eq current-prefix-arg '-) "- ")
@@ -2253,9 +2297,7 @@ This function uses the `read-extended-command-predicate' user option."
               ;; but actually a prompt other than "M-x" would be confusing,
               ;; because "M-x" is a well-known prompt to read a command
               ;; and it serves as a shorthand for "Extended command: ".
-               (if (memq 'shift (event-modifiers last-command-event))
-                  "M-X "
-                "M-x "))
+               (or prompt "M-x "))
        (lambda (string pred action)
          (if (and suggest-key-bindings (eq action 'metadata))
             '(metadata
@@ -2294,7 +2336,7 @@ This function uses the `read-extended-command-predicate' user option."
                          (funcall read-extended-command-predicate sym buffer)
                        (error (message "read-extended-command-predicate: %s: %s"
                                        sym (error-message-string err))))))))
-       t nil 'extended-command-history))))
+       t initial-input 'extended-command-history))))
 
 (defun command-completion-using-modes-p (symbol buffer)
   "Say whether SYMBOL has been marked as a mode-specific command in BUFFER."
@@ -2525,36 +2567,37 @@ minor modes), as well as commands bound in the active local key
 maps."
   (declare (interactive-only command-execute))
   (interactive
-   (let* ((execute-extended-command--last-typed nil)
-          (keymaps
-           ;; The major mode's keymap and any active minor modes.
-           (nconc
-            (and (current-local-map) (list (current-local-map)))
-            (mapcar
-             #'cdr
-             (seq-filter
-              (lambda (elem)
-                (symbol-value (car elem)))
-              minor-mode-map-alist))))
-          (read-extended-command-predicate
-           (lambda (symbol buffer)
-             (or (command-completion-using-modes-p symbol buffer)
-                 ;; Include commands that are bound in a keymap in the
-                 ;; current buffer.
-                 (and (where-is-internal symbol keymaps)
-                      ;; But not if they have a command predicate that
-                      ;; says that they shouldn't.  (This is the case
-                      ;; for `ignore' and `undefined' and similar
-                      ;; commands commonly found in keymaps.)
-                      (or (null (get symbol 'completion-predicate))
-                          (funcall (get symbol 'completion-predicate)
-                                   symbol buffer)))))))
+   (let ((execute-extended-command--last-typed nil))
      (list current-prefix-arg
-           (read-extended-command)
+           (read-extended-command "M-X ")
            execute-extended-command--last-typed)))
   (with-suppressed-warnings ((interactive-only execute-extended-command))
     (execute-extended-command prefixarg command-name typed)))
 
+(defun command-completion--command-for-this-buffer-function ()
+  (let ((keymaps
+         ;; The major mode's keymap and any active minor modes.
+         (nconc
+          (and (current-local-map) (list (current-local-map)))
+          (mapcar
+           #'cdr
+           (seq-filter
+            (lambda (elem)
+              (symbol-value (car elem)))
+            minor-mode-map-alist)))))
+    (lambda (symbol buffer)
+      (or (command-completion-using-modes-p symbol buffer)
+          ;; Include commands that are bound in a keymap in the
+          ;; current buffer.
+          (and (where-is-internal symbol keymaps)
+               ;; But not if they have a command predicate that
+               ;; says that they shouldn't.  (This is the case
+               ;; for `ignore' and `undefined' and similar
+               ;; commands commonly found in keymaps.)
+               (or (null (get symbol 'completion-predicate))
+                   (funcall (get symbol 'completion-predicate)
+                            symbol buffer)))))))
+
 (cl-defgeneric function-documentation (function)
   "Extract the raw docstring info from FUNCTION.
 FUNCTION is expected to be a function value rather than, say, a mere symbol.