diff --git a/builtin/runtime/help.lua b/builtin/runtime/help.lua index 5ac5096..0e31b56 100644 --- a/builtin/runtime/help.lua +++ b/builtin/runtime/help.lua @@ -380,11 +380,21 @@ pmacs.command.define { -- the bounded price of not breaking documented commands, and it carries -- a deprecation path a later stage can take. +-- `invoke`, NOT `invoke_interactive`. The forwarder must work however it +-- was itself reached, and `pmacs.command.invoke('editor.describe-setting')` +-- is a real caller (`tests/config_registry_acceptance.rs`) — CI caught +-- that, because the acceptance pin here only drove the M-x path. +-- +-- Plain `invoke` is also the correct semantics, not merely the working +-- one: the interactive-command boundary is rotated once, by whatever +-- entry point the user actually used, for the name the user actually +-- typed. Rotating again on the inner call would record a second +-- boundary for a command the user never invoked. local function forward(old_name, new_name) pmacs.command.define { name = old_name, description = string.format("Deprecated alias for `%s`.", new_name), - fn = function() pmacs.command.invoke_interactive(new_name) end, + fn = function() pmacs.command.invoke(new_name) end, } end diff --git a/tests/discovery_acceptance.rs b/tests/discovery_acceptance.rs index 3eea579..8385b13 100644 --- a/tests/discovery_acceptance.rs +++ b/tests/discovery_acceptance.rs @@ -434,6 +434,29 @@ fn d8_preservation_the_old_names_forward() { ); } +/// **P (acceptance 8, cont.)** — the forwarders also work when invoked +/// **programmatically**, not only from M-x. +/// +/// This pin exists because its absence shipped a bug: the forwarder body +/// used `invoke_interactive`, which raises when the alias is reached +/// through `pmacs.command.invoke` — a real caller in +/// `config_registry_acceptance`. The M-x pin above passed throughout, +/// because M-x is not the only way in. +#[test] +fn d8c_preservation_the_forwarders_work_programmatically() { + let s = editor(); + for old in ["editor.describe-command", "editor.describe-setting"] { + let ok: bool = eval( + &s, + &format!("return pcall(pmacs.command.invoke, {old:?}) and true or false"), + ); + assert!( + ok, + "{old} must be invocable programmatically, not only from the palette" + ); + } +} + /// **P (acceptance 8, cont.)** — the untouched list commands still work. #[test] fn d8b_preservation_list_buffers_and_workers_are_untouched() {