feat(ui): specify whether msg_show event comes from typed command

Problem:  Unable to tell whether a msg_show event is emitted as a result
          a command typed on the cmdline (UI may want to represent these
          differently from other messages).
Solution: Add trigger parameter that is set to "typed_cmd" for
          a message emitted due to an interactively typed command.
          Possible extensions are mapping/timer/event but it's hard to
          imagine a UI distinguishing those so not added here.
This commit is contained in:
Luuk van Baal
2026-02-17 20:40:56 +01:00
parent 1901832f26
commit 97549ad7cf
7 changed files with 62 additions and 16 deletions

View File

@@ -829,9 +829,9 @@ will be set to zero, but can be changed and used for the replacing cmdline or
message window. Cmdline state is emitted as |ui-cmdline| events, which the UI
must handle.
["msg_show", kind, content, replace_last, history, append, msg_id] ~
["msg_show", kind, content, replace_last, history, append, id, trigger] ~
Display a message to the user. Update (replace) any existing message
matching `msg_id`.
matching `id`.
kind
Name indicating the message kind:
@@ -887,9 +887,14 @@ must handle.
True if the message should be appended to the previous message,
rather than started on a new line. Is set for |:echon|.
msg_id
id
Unique identifier for the message. It can either be an integer or
string. When message of same id appears it should replace the older message.
string. A (visible) message with the same id should be replaced.
trigger
Type of action that triggered the message:
"" (empty) Unknown (consider a |feature-request|)
"typed_cmd" Interactively typed command on the |cmdline|
["msg_clear"] ~
Clear all messages currently displayed by "msg_show", emitted after

View File

@@ -234,8 +234,7 @@ EVENTS
• A new `empty` message kind is emitted for an empty (e.g. `:echo ""`) message.
• |CmdlineLeave| sets |v:char| to the character that stops the Cmdline mode.
• |CmdlineLeavePre| triggered before preparing to leave the command line.
• New `append` parameter for |ui-messages| `msg_show` event.
• New `msg_id` parameter for |ui-messages| `msg_show` event.
• New `append`, `id` and `trigger` parameter for |ui-messages| `msg_show` event.
• 'rulerformat' is emitted as `msg_ruler` when not part of the statusline.
• Creating or updating a progress message with |nvim_echo()| triggers a |Progress| event.
• |MarkSet| is triggered after a |mark| is set by the user (currently doesn't

View File

@@ -165,7 +165,7 @@ void wildmenu_hide(void)
FUNC_API_SINCE(3) FUNC_API_REMOTE_ONLY;
void msg_show(String kind, Array content, Boolean replace_last, Boolean history, Boolean append,
Object id)
Object id, String trigger)
FUNC_API_SINCE(6) FUNC_API_REMOTE_ONLY;
void msg_clear(void)
FUNC_API_SINCE(6) FUNC_API_REMOTE_ONLY;

View File

@@ -152,6 +152,7 @@ bool keep_msg_more = false; // keep_msg was set by msgmore()
// Extended msg state, currently used for external UIs with ext_messages
static const char *msg_ext_kind = NULL;
static const char *msg_ext_trigger = NULL;
static MsgID msg_ext_id = { .type = kObjectTypeInteger, .data.integer = 1 };
static Array *msg_ext_chunks = NULL;
static garray_T msg_ext_last_chunk = GA_INIT(sizeof(char), 40);
@@ -1650,6 +1651,13 @@ void msg_ext_set_kind(const char *msg_kind)
redir_col = msg_ext_append ? redir_col : 0;
}
void msg_ext_set_trigger(const char *trigger)
{
// Don't change the trigger of an existing batch:
msg_ext_ui_flush();
msg_ext_trigger = trigger;
}
/// Prepare for outputting characters in the command line.
void msg_start(void)
{
@@ -2285,7 +2293,7 @@ void msg_puts_len(const char *const str, const ptrdiff_t len, int hl_id, bool hi
if (msg_silent != 0 || *str == NUL) {
if (*str == NUL && ui_has(kUIMessages)) {
ui_call_msg_show(cstr_as_string("empty"), (Array)ARRAY_DICT_INIT, false, false, false,
INTEGER_OBJ(-1));
INTEGER_OBJ(-1), (String)STRING_INIT);
}
return;
}
@@ -3317,7 +3325,7 @@ void msg_ext_ui_flush(void)
Array *tofree = msg_ext_init_chunks();
ui_call_msg_show(cstr_as_string(msg_ext_kind), *tofree, msg_ext_overwrite, msg_ext_history,
msg_ext_append, msg_ext_id);
msg_ext_append, msg_ext_id, cstr_as_string(msg_ext_trigger));
// clear info after emitting message.
if (msg_ext_history) {
api_free_array(*tofree);

View File

@@ -3186,9 +3186,9 @@ static void nv_colon(cmdarg_T *cap)
}
}
// When typing, don't type below an old message
if (KeyTyped) {
compute_cmdrow();
msg_ext_set_trigger("typed_cmd"); // distinguish msg_show emitted for typed cmd
compute_cmdrow(); // when typing, don't type below an old message
}
if (is_lua) {
@@ -3198,6 +3198,7 @@ static void nv_colon(cmdarg_T *cap)
cmd_result = do_cmdline(NULL, is_cmdkey ? getcmdkeycmd : getexline, NULL,
cap->oap->op_type != OP_NOP ? DOCMD_KEEPLINE : 0);
}
msg_ext_set_trigger("");
if (cmd_result == false) {
// The Ex command failed, do not execute the operator.

View File

@@ -1636,6 +1636,34 @@ stack traceback:
},
})
end)
it('trigger', function()
command('echo "foo"')
screen:expect({
grid = [[
^ |
{1:~ }|*4
]],
messages = { { content = { { 'foo' } }, kind = 'echo', trigger = '' } },
})
command('map Q :echo "foo"<CR>')
feed('Q')
screen:expect({
grid = [[
^ |
{1:~ }|*4
]],
messages = { { content = { { 'foo' } }, kind = 'echo', trigger = '' } },
})
feed(':echo "foo"<CR>')
screen:expect({
grid = [[
^ |
{1:~ }|*4
]],
messages = { { content = { { 'foo' } }, kind = 'echo', trigger = 'typed_cmd' } },
})
end)
end)
describe('ui/builtin messages', function()

View File

@@ -671,7 +671,7 @@ screen:redraw_debug() to show all intermediate screen states.]]
-- the ext_ feature being disabled, or the feature currently not activated
-- (e.g. no external cmdline visible). Some extensions require
-- preprocessing to represent highlights in a reproducible way.
local extstate = self:_extstate_repr(attr_state)
local extstate = self:_extstate_repr(attr_state, expected)
if expected.mode ~= nil then
extstate.mode = self.mode
end
@@ -1440,7 +1440,7 @@ function Screen:_handle_wildmenu_hide()
self.wildmenu_items, self.wildmenu_pos = nil, nil
end
function Screen:_handle_msg_show(kind, chunks, replace_last, history, append, id, progress)
function Screen:_handle_msg_show(kind, chunks, replace_last, history, append, id, trigger)
local pos = #self.messages
if not replace_last or pos == 0 then
pos = pos + 1
@@ -1451,7 +1451,7 @@ function Screen:_handle_msg_show(kind, chunks, replace_last, history, append, id
history = history,
append = append,
id = id,
progress = progress,
trigger = trigger,
}
end
@@ -1560,7 +1560,7 @@ local function hl_id_to_name(self, id)
return id and self.hl_names[id] or nil
end
function Screen:_extstate_repr(attr_state)
function Screen:_extstate_repr(attr_state, exp)
local cmdline = {}
for i, entry in pairs(self.cmdline) do
entry = shallowcopy(entry)
@@ -1578,13 +1578,18 @@ function Screen:_extstate_repr(attr_state)
local messages = {}
for i, entry in ipairs(self.messages) do
local trigger = nil
if exp and exp.messages and exp.messages[i] and exp.messages[i].trigger ~= nil then
-- Late addition, only include when expected state includes it.
trigger = entry.trigger
end
messages[i] = {
kind = entry.kind,
content = self:_chunks_repr(entry.content, attr_state),
history = entry.history or nil,
append = entry.append or nil,
id = entry.kind == 'progress' and entry.id or nil,
progress = entry.kind == 'progress' and entry.progress or nil,
trigger = trigger,
}
end