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:
parent
3eb4e0db5c
commit
da0e75e741
9 changed files with 130 additions and 16 deletions
|
|
@ -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
|
||||
|
|
|
|||
4
etc/NEWS
4
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
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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 *);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue