]> git.eshelyaron.com Git - emacs.git/commitdiff
Add facility to make module functions interactive (Bug#23486).
authorPhilipp Stephani <phst@google.com>
Sun, 13 Sep 2020 18:21:41 +0000 (20:21 +0200)
committerPhilipp Stephani <phst@google.com>
Sun, 13 Sep 2020 18:26:47 +0000 (20:26 +0200)
* src/module-env-28.h: Add field for 'make_interactive' function.

* src/emacs-module.c (Lisp_Module_Function): Add new field holding the
interactive form.
(allocate_module_function): Adapt to structure layout change.
(module_make_interactive, module_function_interactive_form): New
functions.
(initialize_environment): Use them.

* src/eval.c (Fcommandp):
* src/data.c (Finteractive_form): Also handle interactive module
functions.

* test/data/emacs-module/mod-test.c (Fmod_test_identity): New test
function.
(emacs_module_init): Create two interactive module test functions.

* test/src/emacs-module-tests.el (module/interactive/return-t)
(module/interactive/return-t-int, module/interactive/identity):
New unit tests.

* doc/lispref/internals.texi (Module Functions): Document new
function.  Rework paragraph about wrapping module functions, as the
example no longer applies.

* etc/NEWS: Document new facility.

doc/lispref/internals.texi
etc/NEWS
src/data.c
src/emacs-module.c
src/eval.c
src/lisp.h
src/module-env-28.h
test/data/emacs-module/mod-test.c
test/src/emacs-module-tests.el

index d70c3543f2ae19ad559875116ddb61adfcca343a..cc18b8523310a2db78a9d52e9a525d5de4e5ce09 100644 (file)
@@ -1425,28 +1425,46 @@ violations of the above requirements.  @xref{Initial Options,,,emacs,
 The GNU Emacs Manual}.
 
 Using the module @acronym{API}, it is possible to define more complex
-function and data types: interactive functions, inline functions,
-macros, etc.  However, the resulting C code will be cumbersome and
-hard to read.  Therefore, we recommend that you limit the module code
-which creates functions and data structures to the absolute minimum,
-and leave the rest for a Lisp package that will accompany your module,
-because doing these additional tasks in Lisp is much easier, and will
-produce a much more readable code.  For example, given a module
-function @code{module-func} defined as above, one way of making an
-interactive command @code{module-cmd} based on it is with the
-following simple Lisp wrapper:
+function and data types: inline functions, macros, etc.  However, the
+resulting C code will be cumbersome and hard to read.  Therefore, we
+recommend that you limit the module code which creates functions and
+data structures to the absolute minimum, and leave the rest for a Lisp
+package that will accompany your module, because doing these
+additional tasks in Lisp is much easier, and will produce a much more
+readable code.  For example, given a module function
+@code{module-func} defined as above, one way of making a macro
+@code{module-macro} based on it is with the following simple Lisp
+wrapper:
 
 @lisp
-(defun module-cmd (&rest args)
-  "Documentation string for the command."
-  (interactive @var{spec})
-  (apply 'module-func args))
+(defmacro module-macro (&rest args)
+  "Documentation string for the macro."
+  (module-func args))
 @end lisp
 
 The Lisp package which goes with your module could then load the
 module using the @code{load} primitive (@pxref{Dynamic Modules}) when
 the package is loaded into Emacs.
 
+By default, module functions created by @code{make_function} are not
+interactive.  To make them interactive, you can use the following
+function.
+
+@deftypefun void make_interactive (emacs_env *@var{env}, emacs_value @var{function}, emacs_value @var{spec})
+This function, which is available since Emacs 28, makes the function
+@var{function} interactive using the interactive specification
+@var{spec}.  Emacs interprets @var{spec} like the argument to the
+@code{interactive} form.  @ref{Using Interactive}, and
+@pxref{Interactive Codes}.  @var{function} must be an Emacs module
+function returned by @code{make_function}.
+@end deftypefun
+
+Note that there is no native module support for retrieving the
+interactive specification of a module function.  Use the function
+@code{interactive-form} for that.  @ref{Using Interactive}.  It is not
+possible to make a module function non-interactive once you have made
+it interactive using @code{make_interactive}.
+
 @anchor{Module Function Finalizers}
 If you want to run some code when a module function object (i.e., an
 object returned by @code{make_function}) is garbage-collected, you can
index db2adcec1555184b72e189f12ba31dc7ff03233c..52092f2ef72cec99dc4aae7d6dbb0d6be2c28dc2 100644 (file)
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -1347,6 +1347,10 @@ This removes the final remaining trace of old-style backquotes.
 'emacs_function' and 'emacs_finalizer' for module functions and
 finalizers, respectively.
 
+** Module functions can now be made interactive.  Use
+'make_interactive' to give a module function an interactive
+specification.
+
 ** Module functions can now install an optional finalizer that is
 called when the function object is garbage-collected.  Use
 'set_function_finalizer' to set the finalizer and
index 59d148166fecb6d973634ea48ff0b6e0f18db559..dae8b10ef5599987f26c30e51d756225364b7459 100644 (file)
@@ -906,6 +906,13 @@ Value, if non-nil, is a list (interactive SPEC).  */)
       if (PVSIZE (fun) > COMPILED_INTERACTIVE)
        return list2 (Qinteractive, AREF (fun, COMPILED_INTERACTIVE));
     }
+  else if (MODULE_FUNCTIONP (fun))
+    {
+      Lisp_Object form
+        = module_function_interactive_form (XMODULE_FUNCTION (fun));
+      if (! NILP (form))
+        return form;
+    }
   else if (AUTOLOADP (fun))
     return Finteractive_form (Fautoload_do_load (fun, cmd, Qnil));
   else if (CONSP (fun))
index a0bab118019372d0eba426d67278d337175151b8..3581daad1129f1fdccbfe8b87cf5dd1f250b3a75 100644 (file)
@@ -551,7 +551,7 @@ struct Lisp_Module_Function
   union vectorlike_header header;
 
   /* Fields traced by GC; these must come first.  */
-  Lisp_Object documentation;
+  Lisp_Object documentation, interactive_form;
 
   /* Fields ignored by GC.  */
   ptrdiff_t min_arity, max_arity;
@@ -564,7 +564,7 @@ static struct Lisp_Module_Function *
 allocate_module_function (void)
 {
   return ALLOCATE_PSEUDOVECTOR (struct Lisp_Module_Function,
-                                documentation, PVEC_MODULE_FUNCTION);
+                                interactive_form, PVEC_MODULE_FUNCTION);
 }
 
 #define XSET_MODULE_FUNCTION(var, ptr) \
@@ -630,6 +630,24 @@ module_finalize_function (const struct Lisp_Module_Function *func)
     func->finalizer (func->data);
 }
 
+static void
+module_make_interactive (emacs_env *env, emacs_value function, emacs_value spec)
+{
+  MODULE_FUNCTION_BEGIN ();
+  Lisp_Object lisp_fun = value_to_lisp (function);
+  CHECK_MODULE_FUNCTION (lisp_fun);
+  Lisp_Object lisp_spec = value_to_lisp (spec);
+  /* Normalize (interactive nil) to (interactive). */
+  XMODULE_FUNCTION (lisp_fun)->interactive_form
+    = NILP (lisp_spec) ? list1 (Qinteractive) : list2 (Qinteractive, lisp_spec);
+}
+
+Lisp_Object
+module_function_interactive_form (const struct Lisp_Module_Function *fun)
+{
+  return fun->interactive_form;
+}
+
 static emacs_value
 module_funcall (emacs_env *env, emacs_value func, ptrdiff_t nargs,
                emacs_value *args)
@@ -1463,6 +1481,7 @@ initialize_environment (emacs_env *env, struct emacs_env_private *priv)
   env->get_function_finalizer = module_get_function_finalizer;
   env->set_function_finalizer = module_set_function_finalizer;
   env->open_channel = module_open_channel;
+  env->make_interactive = module_make_interactive;
   Vmodule_environments = Fcons (make_mint_ptr (env), Vmodule_environments);
   return env;
 }
index 126ee2e95554587fd24eba5ee217d343063adb06..fdc3cd1e9f4a1c55e36047fe2e6ecb8a4464b850 100644 (file)
@@ -1948,6 +1948,13 @@ then strings and vectors are not accepted.  */)
   else if (COMPILEDP (fun))
     return (PVSIZE (fun) > COMPILED_INTERACTIVE ? Qt : if_prop);
 
+  /* Module functions are interactive if their `interactive_form'
+     field is non-nil. */
+  else if (MODULE_FUNCTIONP (fun))
+    return NILP (module_function_interactive_form (XMODULE_FUNCTION (fun)))
+             ? if_prop
+             : Qt;
+
   /* Strings and vectors are keyboard macros.  */
   if (STRINGP (fun) || VECTORP (fun))
     return (NILP (for_call_interactively) ? Qt : Qnil);
index 88e69b9061df8af8f5ccf931da775e07b805acb4..a24898004d472fd03d724458e8552a6653791acb 100644 (file)
@@ -4210,6 +4210,8 @@ extern Lisp_Object funcall_module (Lisp_Object, ptrdiff_t, Lisp_Object *);
 extern Lisp_Object module_function_arity (const struct Lisp_Module_Function *);
 extern Lisp_Object module_function_documentation
   (struct Lisp_Module_Function const *);
+extern Lisp_Object module_function_interactive_form
+  (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 *);
index 5d884c148c437dc2c92664bb3e88461a0db5f099..40b03b92b521503b5a0d59ce1c9f10da47f58263 100644 (file)
@@ -12,3 +12,7 @@
 
   int (*open_channel) (emacs_env *env, emacs_value pipe_process)
     EMACS_ATTRIBUTE_NONNULL (1);
+
+  void (*make_interactive) (emacs_env *env, emacs_value function,
+                            emacs_value spec)
+    EMACS_ATTRIBUTE_NONNULL (1);
index 37186fcc4d1a572f3144367479b183524a293ff4..da298d4e3984e6c4509d9685b959a826d859b31e 100644 (file)
@@ -673,6 +673,14 @@ Fmod_test_async_pipe (emacs_env *env, ptrdiff_t nargs, emacs_value *args,
   return env->intern (env, "nil");
 }
 
+static emacs_value
+Fmod_test_identity (emacs_env *env, ptrdiff_t nargs, emacs_value *args,
+                    void *data)
+{
+  assert (nargs == 1);
+  return args[0];
+}
+
 /* Lisp utilities for easier readability (simple wrappers).  */
 
 /* Provide FEATURE to Emacs.  */
@@ -764,6 +772,19 @@ emacs_module_init (struct emacs_runtime *ert)
 
 #undef DEFUN
 
+  emacs_value constant_fn
+    = env->make_function (env, 0, 0, Fmod_test_return_t, NULL, NULL);
+  env->make_interactive (env, constant_fn, env->intern (env, "nil"));
+  bind_function (env, "mod-test-return-t-int", constant_fn);
+
+  emacs_value identity_fn
+    = env->make_function (env, 1, 1, Fmod_test_identity, NULL, NULL);
+  const char *interactive_spec = "i";
+  env->make_interactive (env, identity_fn,
+                         env->make_string (env, interactive_spec,
+                                           strlen (interactive_spec)));
+  bind_function (env, "mod-test-identity", identity_fn);
+
   provide (env, "mod-test");
   return 0;
 }
index 096c6b3057486b9bb9c4a1623b1056826209ba2f..1eebb418cf3c85ea04f6fac28d9a960c82887c22 100644 (file)
@@ -468,4 +468,36 @@ See Bug#36226."
             (should (equal (buffer-string) "data from thread")))
         (delete-process process)))))
 
+(ert-deftest module/interactive/return-t ()
+  (should (functionp (symbol-function #'mod-test-return-t)))
+  (should (module-function-p (symbol-function #'mod-test-return-t)))
+  (should-not (commandp #'mod-test-return-t))
+  (should-not (commandp (symbol-function #'mod-test-return-t)))
+  (should-not (interactive-form #'mod-test-return-t))
+  (should-not (interactive-form (symbol-function #'mod-test-return-t)))
+  (should-error (call-interactively #'mod-test-return-t)
+                :type 'wrong-type-argument))
+
+(ert-deftest module/interactive/return-t-int ()
+  (should (functionp (symbol-function #'mod-test-return-t-int)))
+  (should (module-function-p (symbol-function #'mod-test-return-t-int)))
+  (should (commandp #'mod-test-return-t-int))
+  (should (commandp (symbol-function #'mod-test-return-t-int)))
+  (should (equal (interactive-form #'mod-test-return-t-int) '(interactive)))
+  (should (equal (interactive-form (symbol-function #'mod-test-return-t-int))
+                 '(interactive)))
+  (should (eq (mod-test-return-t-int) t))
+  (should (eq (call-interactively #'mod-test-return-t-int) t)))
+
+(ert-deftest module/interactive/identity ()
+  (should (functionp (symbol-function #'mod-test-identity)))
+  (should (module-function-p (symbol-function #'mod-test-identity)))
+  (should (commandp #'mod-test-identity))
+  (should (commandp (symbol-function #'mod-test-identity)))
+  (should (equal (interactive-form #'mod-test-identity) '(interactive "i")))
+  (should (equal (interactive-form (symbol-function #'mod-test-identity))
+                 '(interactive "i")))
+  (should (eq (mod-test-identity 123) 123))
+  (should-not (call-interactively #'mod-test-identity)))
+
 ;;; emacs-module-tests.el ends here