1
Fork 0
mirror of git://git.sv.gnu.org/emacs.git synced 2026-01-30 04:10:54 -08:00

Add facility to make module functions interactive (Bug#23486).

* 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.
This commit is contained in:
Philipp Stephani 2020-09-13 20:21:41 +02:00
parent 3eb4e0db5c
commit da0e75e741
9 changed files with 130 additions and 16 deletions

View 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

View file

@ -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

View 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))

View 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;
}

View 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);

View 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 *);

View 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);

View 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;
}

View 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