Extend the syntax of `interactive' to list applicable modes

* doc/lispref/commands.texi (Using Interactive): Document the
extended `interactive' form.
* doc/lispref/loading.texi (Autoload): Document list-of-modes
form.

* lisp/emacs-lisp/autoload.el (make-autoload): Pick the list of
modes from `interactive' out of the functions.

* lisp/emacs-lisp/bytecomp.el (byte-compile-lambda): Allow for the
extended `interactive' form.

* src/callint.c (Finteractive): Document the extended form.

* src/data.c (Finteractive_form): Return the interactive form in
the old format (even when there's an extended `interactive') to
avoid having other parts of Emacs be aware of this.
(Fcommand_modes): New defun.

* src/emacs-module.c (GCALIGNED_STRUCT): Allow for modules to
return command modes.

* src/lisp.h: New function module_function_command_modes.
This commit is contained in:
Lars Ingebrigtsen 2021-02-14 13:21:24 +01:00
parent 8d517daf77
commit 58e0c8ee86
11 changed files with 179 additions and 28 deletions

View file

@ -156,7 +156,7 @@ commands by adding the @code{interactive} form to them.
makes a Lisp function an interactively-callable command, and how to
examine a command's @code{interactive} form.
@defspec interactive arg-descriptor
@defspec interactive &optional arg-descriptor &rest modes
This special form declares that a function is a command, and that it
may therefore be called interactively (via @kbd{M-x} or by entering a
key sequence bound to it). The argument @var{arg-descriptor} declares
@ -177,6 +177,23 @@ forms are executed; at this time, if the @code{interactive} form
occurs within the body, the form simply returns @code{nil} without
even evaluating its argument.
The @var{modes} list allows specifying which modes the command is
meant to be used in. This affects, for instance, completion in
@kbd{M-x} (commands won't be offered as completions if they don't
match (using @code{derived-mode-p}) the current major mode, or if the
mode is a minor mode, whether it's switched on in the current buffer).
This will also make @kbd{C-h m} list these commands (if they aren't
bound to any keys).
For instance:
@lisp
(interactive "p" dired-mode)
@end lisp
This will mark the command as applicable for modes derived from
@code{dired-mode} only.
By convention, you should put the @code{interactive} form in the
function body, as the first top-level form. If there is an
@code{interactive} form in both the @code{interactive-form} symbol

View file

@ -510,6 +510,9 @@ specification is not given here; it's not needed unless the user
actually calls @var{function}, and when that happens, it's time to load
the real definition.
If @var{interactive} is a list, it is interpreted as a list of modes
this command is applicable for.
You can autoload macros and keymaps as well as ordinary functions.
Specify @var{type} as @code{macro} if @var{function} is really a macro.
Specify @var{type} as @code{keymap} if @var{function} is really a

View file

@ -2266,6 +2266,14 @@ back in Emacs 23.1. The affected functions are: 'make-obsolete',
* Lisp Changes in Emacs 28.1
+++
** The 'interactive' syntax has been extended to allow listing applicable modes.
Forms like '(interactive "p" dired-mode)' can be used to annotate the
commands as being applicable for modes derived from 'dired-mode',
or if the mode is a minor mode, that the current buffer has that
minor mode activated. Note that using this form will create byte code
that is not compatible with byte code in previous Emacs versions.
+++
** New buffer-local variable 'minor-modes'.
This permanently buffer-local variable holds a list of currently

View file

@ -141,9 +141,12 @@ expression, in which case we want to handle forms differently."
((stringp (car-safe rest)) (car rest))))
;; Look for an interactive spec.
(interactive (pcase body
((or `((interactive . ,_) . ,_)
`(,_ (interactive . ,_) . ,_))
t))))
((or `((interactive . ,iargs) . ,_)
`(,_ (interactive . ,iargs) . ,_))
;; List of modes or just t.
(if (nthcdr 1 iargs)
(list 'quote (nthcdr 1 iargs))
t)))))
;; Add the usage form at the end where describe-function-1
;; can recover it.
(when (consp args) (setq doc (help-add-fundoc-usage doc args)))
@ -207,7 +210,11 @@ expression, in which case we want to handle forms differently."
easy-mmode-define-minor-mode
define-minor-mode))
t)
(eq (car-safe (car body)) 'interactive))
(and (eq (car-safe (car body)) 'interactive)
;; List of modes or just t.
(or (if (nthcdr 1 (car body))
(list 'quote (nthcdr 1 (car body)))
t))))
,(if macrop ''macro nil))))
;; For defclass forms, use `eieio-defclass-autoload'.

View file

@ -2939,7 +2939,8 @@ for symbols generated by the byte compiler itself."
;; unless it is the last element of the body.
(if (cdr body)
(setq body (cdr body))))))
(int (assq 'interactive body)))
(int (assq 'interactive body))
command-modes)
(when lexical-binding
(dolist (var arglistvars)
(when (assq var byte-compile--known-dynamic-vars)
@ -2951,9 +2952,10 @@ for symbols generated by the byte compiler itself."
(if (eq int (car body))
(setq body (cdr body)))
(cond ((consp (cdr int))
(if (cdr (cdr int))
(byte-compile-warn "malformed interactive spec: %s"
(prin1-to-string int)))
(unless (seq-every-p #'symbolp (cdr (cdr int)))
(byte-compile-warn "malformed interactive specc: %s"
(prin1-to-string int)))
(setq command-modes (cdr (cdr int)))
;; If the interactive spec is a call to `list', don't
;; compile it, because `call-interactively' looks at the
;; args of `list'. Actually, compile it to get warnings,
@ -2964,14 +2966,15 @@ for symbols generated by the byte compiler itself."
(while (consp (cdr form))
(setq form (cdr form)))
(setq form (car form)))
(if (and (eq (car-safe form) 'list)
;; For code using lexical-binding, form is not
;; valid lisp, but rather an intermediate form
;; which may include "calls" to
;; internal-make-closure (Bug#29988).
(not lexical-binding))
nil
(setq int `(interactive ,newform)))))
(setq int
(if (and (eq (car-safe form) 'list)
;; For code using lexical-binding, form is not
;; valid lisp, but rather an intermediate form
;; which may include "calls" to
;; internal-make-closure (Bug#29988).
(not lexical-binding))
`(interactive ,form)
`(interactive ,newform)))))
((cdr int)
(byte-compile-warn "malformed interactive spec: %s"
(prin1-to-string int)))))
@ -3002,9 +3005,16 @@ for symbols generated by the byte compiler itself."
(list (help-add-fundoc-usage doc arglist)))
((or doc int)
(list doc)))
;; optionally, the interactive spec.
(if int
(list (nth 1 int))))))))
;; optionally, the interactive spec (and the modes the
;; command applies to).
(cond
;; We have some command modes, so use the vector form.
(command-modes
(list (vector (nth 1 int) command-modes)))
;; No command modes, use the simple form with just the
;; interactive spec.
(int
(list (nth 1 int)))))))))
(defvar byte-compile-reserved-constants 0)

View file

@ -104,7 +104,14 @@ If the string begins with `^' and `shift-select-mode' is non-nil,
Emacs first calls the function `handle-shift-selection'.
You may use `@', `*', and `^' together. They are processed in the
order that they appear, before reading any arguments.
usage: (interactive &optional ARG-DESCRIPTOR) */
If MODES is present, it should be a list of mode names (symbols) that
this command is applicable for. The main effect of this is that
`M-x TAB' (by default) won't list this command if the current buffer's
mode doesn't match the list. That is, if either the major mode isn't
derived from them, or (when it's a minor mode) the mode isn't in effect.
usage: (interactive &optional ARG-DESCRIPTOR &rest MODES) */
attributes: const)
(Lisp_Object args)
{

View file

@ -904,7 +904,17 @@ Value, if non-nil, is a list (interactive SPEC). */)
else if (COMPILEDP (fun))
{
if (PVSIZE (fun) > COMPILED_INTERACTIVE)
return list2 (Qinteractive, AREF (fun, COMPILED_INTERACTIVE));
{
Lisp_Object form = AREF (fun, COMPILED_INTERACTIVE);
if (VECTORP (form))
/* The vector form is the new form, where the first
element is the interactive spec, and the second is the
command modes. */
return list2 (Qinteractive, AREF (form, 0));
else
/* Old form -- just the interactive spec. */
return list2 (Qinteractive, form);
}
}
#ifdef HAVE_MODULES
else if (MODULE_FUNCTIONP (fun))
@ -920,10 +930,80 @@ Value, if non-nil, is a list (interactive SPEC). */)
else if (CONSP (fun))
{
Lisp_Object funcar = XCAR (fun);
if (EQ (funcar, Qclosure))
return Fassq (Qinteractive, Fcdr (Fcdr (XCDR (fun))));
else if (EQ (funcar, Qlambda))
return Fassq (Qinteractive, Fcdr (XCDR (fun)));
if (EQ (funcar, Qclosure)
|| EQ (funcar, Qlambda))
{
Lisp_Object form = Fcdr (XCDR (fun));
if (EQ (funcar, Qclosure))
form = Fcdr (form);
Lisp_Object spec = Fassq (Qinteractive, form);
if (NILP (Fcdr (Fcdr (spec))))
return spec;
else
return list2 (Qinteractive, Fcar (Fcdr (spec)));
}
}
return Qnil;
}
DEFUN ("command-modes", Fcommand_modes, Scommand_modes, 1, 1, 0,
doc: /* Return the modes COMMAND is defined for.
If COMMAND is not a command, the return value is nil.
The value, if non-nil, is a list of mode name symbols. */)
(Lisp_Object command)
{
Lisp_Object fun = indirect_function (command); /* Check cycles. */
if (NILP (fun))
return Qnil;
fun = command;
while (SYMBOLP (fun))
fun = Fsymbol_function (fun);
if (SUBRP (fun))
{
if (!NILP (XSUBR (fun)->command_modes))
return XSUBR (fun)->command_modes;
}
else if (COMPILEDP (fun))
{
Lisp_Object form = AREF (fun, COMPILED_INTERACTIVE);
if (VECTORP (form))
/* New form -- the second element is the command modes. */
return AREF (form, 1);
else
/* Old .elc file -- no command modes. */
return Qnil;
}
#ifdef HAVE_MODULES
else if (MODULE_FUNCTIONP (fun))
{
Lisp_Object form
= module_function_command_modes (XMODULE_FUNCTION (fun));
if (! NILP (form))
return form;
}
#endif
else if (AUTOLOADP (fun))
{
Lisp_Object modes = Fnth (make_int (3), fun);
if (CONSP (modes))
return modes;
else
return Qnil;
}
else if (CONSP (fun))
{
Lisp_Object funcar = XCAR (fun);
if (EQ (funcar, Qclosure)
|| EQ (funcar, Qlambda))
{
Lisp_Object form = Fcdr (XCDR (fun));
if (EQ (funcar, Qclosure))
form = Fcdr (form);
return Fcdr (Fcdr (Fassq (Qinteractive, form)));
}
}
return Qnil;
}
@ -3908,6 +3988,7 @@ syms_of_data (void)
defsubr (&Sindirect_variable);
defsubr (&Sinteractive_form);
defsubr (&Scommand_modes);
defsubr (&Seq);
defsubr (&Snull);
defsubr (&Stype_of);
@ -4030,6 +4111,7 @@ This variable cannot be set; trying to do so will signal an error. */);
DEFSYM (Qunlet, "unlet");
DEFSYM (Qset, "set");
DEFSYM (Qset_default, "set-default");
DEFSYM (Qcommand_modes, "command-modes");
defsubr (&Sadd_variable_watcher);
defsubr (&Sremove_variable_watcher);
defsubr (&Sget_variable_watchers);

View file

@ -549,7 +549,7 @@ struct Lisp_Module_Function
union vectorlike_header header;
/* Fields traced by GC; these must come first. */
Lisp_Object documentation, interactive_form;
Lisp_Object documentation, interactive_form, command_modes;
/* Fields ignored by GC. */
ptrdiff_t min_arity, max_arity;
@ -646,6 +646,12 @@ module_function_interactive_form (const struct Lisp_Module_Function *fun)
return fun->interactive_form;
}
Lisp_Object
module_function_command_modes (const struct Lisp_Module_Function *fun)
{
return fun->command_modes;
}
static emacs_value
module_funcall (emacs_env *env, emacs_value func, ptrdiff_t nargs,
emacs_value *args)

View file

@ -2080,14 +2080,21 @@ then strings and vectors are not accepted. */)
DEFUN ("autoload", Fautoload, Sautoload, 2, 5, 0,
doc: /* Define FUNCTION to autoload from FILE.
FUNCTION is a symbol; FILE is a file name string to pass to `load'.
Third arg DOCSTRING is documentation for the function.
Fourth arg INTERACTIVE if non-nil says function can be called interactively.
Fourth arg INTERACTIVE if non-nil says function can be called
interactively. If INTERACTIVE is a list, it is interpreted as a list
of modes the function is applicable for.
Fifth arg TYPE indicates the type of the object:
nil or omitted says FUNCTION is a function,
`keymap' says FUNCTION is really a keymap, and
`macro' or t says FUNCTION is really a macro.
Third through fifth args give info about the real definition.
They default to nil.
If FUNCTION is already defined other than as an autoload,
this does nothing and returns nil. */)
(Lisp_Object function, Lisp_Object file, Lisp_Object docstring, Lisp_Object interactive, Lisp_Object type)

View file

@ -2060,6 +2060,7 @@ struct Lisp_Subr
const char *symbol_name;
const char *intspec;
EMACS_INT doc;
Lisp_Object command_modes;
} GCALIGNED_STRUCT;
union Aligned_Lisp_Subr
{
@ -4221,6 +4222,8 @@ extern Lisp_Object module_function_documentation
(struct Lisp_Module_Function const *);
extern Lisp_Object module_function_interactive_form
(const struct Lisp_Module_Function *);
extern Lisp_Object module_function_command_modes
(const struct Lisp_Module_Function *);
extern module_funcptr module_function_address
(struct Lisp_Module_Function const *);
extern void *module_function_data (const struct Lisp_Module_Function *);

View file

@ -4467,6 +4467,7 @@ defsubr (union Aligned_Lisp_Subr *aname)
XSETPVECTYPE (sname, PVEC_SUBR);
XSETSUBR (tem, sname);
set_symbol_function (sym, tem);
sname->command_modes = Qnil;
}
#ifdef NOTDEF /* Use fset in subr.el now! */