emacs-diffs
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

scratch/command c010d9e 1/2: Add support for the new `command' special f


From: Lars Ingebrigtsen
Subject: scratch/command c010d9e 1/2: Add support for the new `command' special form
Date: Thu, 11 Feb 2021 10:23:12 -0500 (EST)

branch: scratch/command
commit c010d9eef8365b370a16cea4eaac09816ecb03f8
Author: Lars Ingebrigtsen <larsi@gnus.org>
Commit: Lars Ingebrigtsen <larsi@gnus.org>

    Add support for the new `command' special form
---
 doc/lispref/commands.texi   | 18 +++++++++++
 lisp/emacs-lisp/bytecomp.el | 43 ++++++++++++++++----------
 lisp/emacs-lisp/edebug.el   |  3 ++
 lisp/simple.el              | 10 ++++++-
 src/callint.c               | 19 ++++++++++++
 src/data.c                  | 73 +++++++++++++++++++++++++++++++++++++++++----
 src/emacs-module.c          |  8 ++++-
 src/eval.c                  |  8 +++--
 src/lisp.h                  |  3 ++
 src/lread.c                 |  1 +
 10 files changed, 162 insertions(+), 24 deletions(-)

diff --git a/doc/lispref/commands.texi b/doc/lispref/commands.texi
index 3a2c7d0..a1d4d7e 100644
--- a/doc/lispref/commands.texi
+++ b/doc/lispref/commands.texi
@@ -116,6 +116,12 @@ serves as a flag, telling the Emacs command loop that the 
function can
 be called interactively.  The argument of the @code{interactive} form
 specifies how the arguments for an interactive call should be read.
 
+  Many commands are specific to certain major modes and do not make
+sense outside of that context.  These commands can be marked with
+@code{command} instead of @code{interactive}, and take an additional
+parameter that specifies the mode (or modes) the command is applicable
+for.
+
 @cindex @code{interactive-form} property
   Alternatively, an @code{interactive} form may be specified in a
 function symbol's @code{interactive-form} property.  A non-@code{nil}
@@ -186,6 +192,18 @@ interactive form to an existing function, or change how 
its arguments
 are processed interactively, without redefining the function.
 @end defspec
 
+@defspec command modes &optional arg-descriptor
+The @code{command} special form is identical to @code{interactive} in
+its effect, but 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), and will make
+@kbd{C-h m} list these commands.
+
+@var{modes} can be either a single symbol (a mode name), or a list of
+symbols (several mode names).
+@end defspec
+
 There are three possibilities for the argument @var{arg-descriptor}:
 
 @itemize @bullet
diff --git a/lisp/emacs-lisp/bytecomp.el b/lisp/emacs-lisp/bytecomp.el
index 89068a1..e59a1f5 100644
--- a/lisp/emacs-lisp/bytecomp.el
+++ b/lisp/emacs-lisp/bytecomp.el
@@ -2939,7 +2939,9 @@ 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 (or (assq 'interactive body)
+                  (assq 'command body)))
+         command-modes)
     (when lexical-binding
       (dolist (var arglistvars)
         (when (assq var byte-compile--known-dynamic-vars)
@@ -2951,27 +2953,37 @@ 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)))
+            (when (or (and (eq (car int) 'interactive)
+                            (cdr (cdr int)))
+                       (and (eq (car int) 'command)
+                            (cdr (cdr (cdr int)))))
+              (byte-compile-warn "malformed interactive spec: %s"
+                                 (prin1-to-string int)))
+             (when (eq (car int) 'command)
+               (setq command-modes (cadr int))
+               (unless (listp command-modes)
+                 (setq command-modes (list command-modes))))
             ;; 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,
             ;; but don't use the result.
-            (let* ((form (nth 1 int))
+            (let* ((form (if (eq (car int) 'interactive)
+                              (nth 1 int)
+                            (nth 2 int)))
                     (newform (byte-compile-top-level form)))
               (while (memq (car-safe form) '(let let* progn save-excursion))
                 (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 +3014,10 @@ for symbols generated by the byte compiler itself."
                      (list (help-add-fundoc-usage doc arglist)))
                     ((or doc int)
                      (list doc)))
-              ;; optionally, the interactive spec.
+              ;; optionally, the interactive spec (and the modes the
+              ;; command applies to).
               (if int
-                  (list (nth 1 int))))))))
+                  (list (cons (nth 1 int) command-modes))))))))
 
 (defvar byte-compile-reserved-constants 0)
 
diff --git a/lisp/emacs-lisp/edebug.el b/lisp/emacs-lisp/edebug.el
index 0733dce..3c86b2e 100644
--- a/lisp/emacs-lisp/edebug.el
+++ b/lisp/emacs-lisp/edebug.el
@@ -2211,6 +2211,9 @@ into `edebug--cl-macrolet-defs' which is checked in 
`edebug-list-form-args'."
 (def-edebug-spec interactive
   (&optional &or stringp def-form))
 
+(def-edebug-spec command
+  (symbolp [&optional &or stringp def-form]))
+
 ;; A function-form is for an argument that may be a function or a form.
 ;; This specially recognizes anonymous functions quoted with quote.
 (def-edebug-spec function-form
diff --git a/lisp/simple.el b/lisp/simple.el
index 0c5bcb6..fbd23a0 100644
--- a/lisp/simple.el
+++ b/lisp/simple.el
@@ -1948,7 +1948,15 @@ to get different commands to edit and resubmit."
             (affixation-function . read-extended-command--affixation)
             (category . command))
          (complete-with-action action obarray string pred)))
-     #'commandp t nil 'extended-command-history)))
+     #'command-for-mode-p t nil 'extended-command-history)))
+
+(defun command-for-mode-p (symbol)
+  "Say whether SYMBOL should be offered as a completion.
+This is true if it's a command and the command is applicable to
+the current major mode."
+  (and (commandp symbol)
+       (or (null (command-modes symbol))
+           (apply #'derived-mode-p (command-modes symbol)))))
 
 (defun read-extended-command--affixation (command-names)
   (with-selected-window (or (minibuffer-selected-window) (selected-window))
diff --git a/src/callint.c b/src/callint.c
index d3f49bc..ba5a406 100644
--- a/src/callint.c
+++ b/src/callint.c
@@ -111,6 +111,24 @@ usage: (interactive &optional ARG-DESCRIPTOR)  */
   return Qnil;
 }
 
+DEFUN ("command", Fcommand, Scommand, 0, UNEVALLED, 0,
+       doc: /* Specify interactive arguments for a mode-specific command.
+This is like `interactive' (which see), except that it allows
+specifying which major mode (or modes) the command is meant for.  This
+has the effect of limiting completion in commands like `M-x' to the
+relevant commands for the current mode, and will also make `C-h m' list
+these commands.
+
+MODES can be either a symbol (a single mode), or a list of symbols
+(several modes).
+
+usage: (command MODES &optional ARG-DESCRIPTOR)  */
+       attributes: const)
+  (Lisp_Object args)
+{
+  return Qnil;
+}
+
 /* Quotify EXP: if EXP is constant, return it.
    If EXP is not constant, return (quote EXP).  */
 static Lisp_Object
@@ -891,6 +909,7 @@ a way to turn themselves off when a mouse command switches 
windows.  */);
   Vmouse_leave_buffer_hook = Qnil;
 
   defsubr (&Sinteractive);
+  defsubr (&Scommand);
   defsubr (&Scall_interactively);
   defsubr (&Sfuncall_interactively);
   defsubr (&Sprefix_numeric_value);
diff --git a/src/data.c b/src/data.c
index 38cde0f..eac2488 100644
--- a/src/data.c
+++ b/src/data.c
@@ -904,7 +904,7 @@ 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));
+       return list2 (Qinteractive, Fcar (AREF (fun, COMPILED_INTERACTIVE)));
     }
 #ifdef HAVE_MODULES
   else if (MODULE_FUNCTIONP (fun))
@@ -920,10 +920,71 @@ 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 (Qcommand, form);
+         if (!NILP (spec))
+           return Fcons (Qinteractive, Fcdr (Fcdr (spec)));
+         else
+           return Fassq (Qinteractive, form);
+       }
+    }
+  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))
+    {
+      if (PVSIZE (fun) > COMPILED_INTERACTIVE)
+       return Fcdr (AREF (fun, COMPILED_INTERACTIVE));
+    }
+#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))
+    return Fcommand_modes (Fautoload_do_load (fun, command, 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);
+         Lisp_Object spec = Fassq (Qcommand, form);
+         if (spec)
+           return list1 (Fcar (Fcdr (spec)));
+       }
     }
   return Qnil;
 }
@@ -3908,6 +3969,7 @@ syms_of_data (void)
 
   defsubr (&Sindirect_variable);
   defsubr (&Sinteractive_form);
+  defsubr (&Scommand_modes);
   defsubr (&Seq);
   defsubr (&Snull);
   defsubr (&Stype_of);
@@ -4030,6 +4092,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);
diff --git a/src/emacs-module.c b/src/emacs-module.c
index 894dffc..f8fb54c 100644
--- a/src/emacs-module.c
+++ b/src/emacs-module.c
@@ -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)
diff --git a/src/eval.c b/src/eval.c
index 3aff3b5..869a283 100644
--- a/src/eval.c
+++ b/src/eval.c
@@ -2072,9 +2072,12 @@ then strings and vectors are not accepted.  */)
   funcar = XCAR (fun);
   if (EQ (funcar, Qclosure))
     return (!NILP (Fassq (Qinteractive, Fcdr (Fcdr (XCDR (fun)))))
-           ? Qt : if_prop);
+           || !NILP (Fassq (Qcommand, Fcdr (Fcdr (XCDR (fun))))))
+      ? Qt : if_prop;
   else if (EQ (funcar, Qlambda))
-    return !NILP (Fassq (Qinteractive, Fcdr (XCDR (fun)))) ? Qt : if_prop;
+    return (!NILP (Fassq (Qinteractive, Fcdr (XCDR (fun))))
+           || !NILP (Fassq (Qcommand, Fcdr (XCDR (fun)))))
+      ? Qt : if_prop;
   else if (EQ (funcar, Qautoload))
     return !NILP (Fcar (Fcdr (Fcdr (XCDR (fun))))) ? Qt : if_prop;
   else
@@ -4224,6 +4227,7 @@ before making `inhibit-quit' nil.  */);
   DEFSYM (Qexit, "exit");
 
   DEFSYM (Qinteractive, "interactive");
+  DEFSYM (Qcommand, "command");
   DEFSYM (Qcommandp, "commandp");
   DEFSYM (Qand_rest, "&rest");
   DEFSYM (Qand_optional, "&optional");
diff --git a/src/lisp.h b/src/lisp.h
index 409a1e7..5710112 100644
--- a/src/lisp.h
+++ b/src/lisp.h
@@ -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
   {
@@ -4220,6 +4221,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 *);
diff --git a/src/lread.c b/src/lread.c
index dea1b23..8b8ba93 100644
--- a/src/lread.c
+++ b/src/lread.c
@@ -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!  */



reply via email to

[Prev in Thread] Current Thread [Next in Thread]