summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--doc/emacs/m-x.texi5
-rw-r--r--doc/lispref/commands.texi9
-rw-r--r--etc/NEWS5
-rw-r--r--lisp/emacs-lisp/seq.el1
-rw-r--r--lisp/simple.el131
5 files changed, 103 insertions, 48 deletions
diff --git a/doc/emacs/m-x.texi b/doc/emacs/m-x.texi
index 865220fb218..689125e7b4a 100644
--- a/doc/emacs/m-x.texi
+++ b/doc/emacs/m-x.texi
@@ -94,3 +94,8 @@ the command is followed by arguments.
@kbd{M-x} works by running the command
@code{execute-extended-command}, which is responsible for reading the
name of another command and invoking it.
+
+@vindex read-extended-command-predicate
+ This command heeds the @code{read-extended-command-predicate}
+variable, which will (by default) filter out commands that are not
+applicable to the current major mode (or enabled minor modes).
diff --git a/doc/lispref/commands.texi b/doc/lispref/commands.texi
index d60745a825b..b3bcdf35c9f 100644
--- a/doc/lispref/commands.texi
+++ b/doc/lispref/commands.texi
@@ -773,6 +773,15 @@ part of the prompt.
@result{} t
@end group
@end example
+
+@vindex read-extended-command-predicate
+This command heeds the @code{read-extended-command-predicate}
+variable, which will (by default) filter out commands that are not
+applicable to the current major mode (or enabled minor modes).
+@code{read-extended-command-predicate} will be called with two
+parameters: The symbol that is to be included or not, and the current
+buffer. If should return non-@code{nil} if the command is to be
+included when completing.
@end deffn
@node Distinguish Interactive
diff --git a/etc/NEWS b/etc/NEWS
index 3b6467bf45c..9c3396d33af 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -251,6 +251,11 @@ commands. The new keystrokes are 'C-x x g' ('revert-buffer'),
* Editing Changes in Emacs 28.1
++++
+** New user option 'read-extended-command-predicate'.
+This option controls how 'M-x TAB' performs completions. The default
+predicate excludes modes for which the command is not applicable.
+
---
** 'eval-expression' now no longer signals an error on incomplete expressions.
Previously, typing 'M-: ( RET' would result in Emacs saying "End of
diff --git a/lisp/emacs-lisp/seq.el b/lisp/emacs-lisp/seq.el
index 31c15fea90d..55ce6d9426d 100644
--- a/lisp/emacs-lisp/seq.el
+++ b/lisp/emacs-lisp/seq.el
@@ -455,6 +455,7 @@ negative integer or 0, nil is returned."
(setq sequence (seq-drop sequence n)))
(nreverse result))))
+;;;###autoload
(cl-defgeneric seq-intersection (sequence1 sequence2 &optional testfn)
"Return a list of the elements that appear in both SEQUENCE1 and SEQUENCE2.
Equality is defined by TESTFN if non-nil or by `equal' if nil."
diff --git a/lisp/simple.el b/lisp/simple.el
index 9057355a7ab..015fa9e4d55 100644
--- a/lisp/simple.el
+++ b/lisp/simple.el
@@ -1900,55 +1900,90 @@ to get different commands to edit and resubmit."
(defvar extended-command-history nil)
(defvar execute-extended-command--last-typed nil)
+(defcustom read-extended-command-predicate #'completion-in-mode-p
+ "Predicate to use to determine which commands to include when completing.
+The predicate function is called with two parameter: The
+symbol (i.e., command) in question that should be included or
+not, and the current buffer. The predicate should return non-nil
+if the command should be present when doing `M-x TAB'."
+ :version "28.1"
+ :type '(choice (const :tag "Exclude commands not relevant to this mode"
+ #'completion-in-mode-p)
+ (const :tag "All commands" (lambda (_ _) t))
+ (function :tag "Other function")))
+
(defun read-extended-command ()
- "Read command name to invoke in `execute-extended-command'."
- (minibuffer-with-setup-hook
- (lambda ()
- (add-hook 'post-self-insert-hook
- (lambda ()
- (setq execute-extended-command--last-typed
- (minibuffer-contents)))
- nil 'local)
- (setq-local minibuffer-default-add-function
- (lambda ()
- ;; Get a command name at point in the original buffer
- ;; to propose it after M-n.
- (let ((def (with-current-buffer
- (window-buffer (minibuffer-selected-window))
- (and (commandp (function-called-at-point))
- (format "%S" (function-called-at-point)))))
- (all (sort (minibuffer-default-add-completions)
- #'string<)))
- (if def
- (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.
- (completing-read
- (concat (cond
- ((eq current-prefix-arg '-) "- ")
- ((and (consp current-prefix-arg)
- (eq (car current-prefix-arg) 4)) "C-u ")
- ((and (consp current-prefix-arg)
- (integerp (car current-prefix-arg)))
- (format "%d " (car current-prefix-arg)))
- ((integerp current-prefix-arg)
- (format "%d " current-prefix-arg)))
- ;; This isn't strictly correct if `execute-extended-command'
- ;; is bound to anything else (e.g. [menu]).
- ;; It could use (key-description (this-single-command-keys)),
- ;; 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: ".
- "M-x ")
- (lambda (string pred action)
- (if (and suggest-key-bindings (eq action 'metadata))
- '(metadata
- (affixation-function . read-extended-command--affixation)
- (category . command))
- (complete-with-action action obarray string pred)))
- #'commandp t nil 'extended-command-history)))
+ "Read command name to invoke in `execute-extended-command'.
+This function uses the `read-extended-command-predicate' user option."
+ (let ((buffer (current-buffer)))
+ (minibuffer-with-setup-hook
+ (lambda ()
+ (add-hook 'post-self-insert-hook
+ (lambda ()
+ (setq execute-extended-command--last-typed
+ (minibuffer-contents)))
+ nil 'local)
+ (setq-local minibuffer-default-add-function
+ (lambda ()
+ ;; Get a command name at point in the original buffer
+ ;; to propose it after M-n.
+ (let ((def
+ (with-current-buffer
+ (window-buffer (minibuffer-selected-window))
+ (and (commandp (function-called-at-point))
+ (format
+ "%S" (function-called-at-point)))))
+ (all (sort (minibuffer-default-add-completions)
+ #'string<)))
+ (if def
+ (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.
+ (completing-read
+ (concat (cond
+ ((eq current-prefix-arg '-) "- ")
+ ((and (consp current-prefix-arg)
+ (eq (car current-prefix-arg) 4)) "C-u ")
+ ((and (consp current-prefix-arg)
+ (integerp (car current-prefix-arg)))
+ (format "%d " (car current-prefix-arg)))
+ ((integerp current-prefix-arg)
+ (format "%d " current-prefix-arg)))
+ ;; This isn't strictly correct if `execute-extended-command'
+ ;; is bound to anything else (e.g. [menu]).
+ ;; It could use (key-description (this-single-command-keys)),
+ ;; 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: ".
+ "M-x ")
+ (lambda (string pred action)
+ (if (and suggest-key-bindings (eq action 'metadata))
+ '(metadata
+ (affixation-function . read-extended-command--affixation)
+ (category . command))
+ (complete-with-action action obarray string pred)))
+ (lambda (sym)
+ (and (commandp sym)
+ (if (get sym 'completion-predicate)
+ (funcall (get sym 'completion-predicate) sym buffer)
+ (funcall read-extended-command-predicate sym buffer))))
+ t nil 'extended-command-history))))
+
+(defun completion-in-mode-p (symbol buffer)
+ "Say whether SYMBOL should be offered as a completion.
+This is true if the command is applicable to the major mode in
+BUFFER."
+ (or (null (command-modes symbol))
+ ;; It's derived from a major mode.
+ (apply #'provided-mode-derived-p
+ (buffer-local-value 'major-mode buffer)
+ (command-modes symbol))
+ ;; It's a minor mode.
+ (seq-intersection (command-modes symbol)
+ (buffer-local-value 'minor-modes buffer)
+ #'eq)))
(defun completion-with-modes-p (modes buffer)
(apply #'provided-mode-derived-p