* 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.
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
'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
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))
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;
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) \
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)
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;
}
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);
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 *);
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);
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. */
#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;
}
(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