feat(ui): "append" parameter for "msg_show" UI events

Problem:  Consecutive "msg_show" events stemming from an `:echon`
          command are supposed to be appended without a newline, this
          information is not encoded in the "msg_show" event.
Solution: Add an "append" parameter to the "msg_show" event that is set
          to true to indicate the message should not start on a new line.
Considered alternative: Emit a newline for the common case instead at the
start of a new message. That way UIs can more closely follow the logic
as it is implemented for the message grid currently. This would be a
breaking change. The "append" parameter seems OK.
This commit is contained in:
Luuk van Baal
2025-05-23 13:03:28 +02:00
committed by luukvbaal
parent 7c2b4a0eaf
commit abb40ecedd
9 changed files with 44 additions and 62 deletions

View File

@ -141,6 +141,7 @@ EDITOR
EVENTS EVENTS
• |CmdlineLeavePre| triggered before preparing to leave the command line. • |CmdlineLeavePre| triggered before preparing to leave the command line.
• New `append` paremeter for |ui-messages| `msg_show` event.
HIGHLIGHTS HIGHLIGHTS

View File

@ -810,7 +810,7 @@ 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 message window. Cmdline state is emitted as |ui-cmdline| events, which the UI
must handle. must handle.
["msg_show", kind, content, replace_last, history] ~ ["msg_show", kind, content, replace_last, history, append] ~
Display a message to the user. Display a message to the user.
kind kind
@ -860,6 +860,10 @@ must handle.
history history
True if the message was added to the |:messages| history. True if the message was added to the |:messages| history.
append
True if the message should be appeneded to the previous message,
rather than started on a new line. Is set for |:echon|.
["msg_clear"] ~ ["msg_clear"] ~
Clear all messages currently displayed by "msg_show". (Messages sent Clear all messages currently displayed by "msg_show". (Messages sent
by other "msg_" events below will not be affected). by other "msg_" events below will not be affected).

View File

@ -160,7 +160,7 @@ void wildmenu_select(Integer selected)
void wildmenu_hide(void) void wildmenu_hide(void)
FUNC_API_SINCE(3) FUNC_API_REMOTE_ONLY; FUNC_API_SINCE(3) FUNC_API_REMOTE_ONLY;
void msg_show(String kind, Array content, Boolean replace_last, Boolean history) void msg_show(String kind, Array content, Boolean replace_last, Boolean history, Boolean append)
FUNC_API_SINCE(6) FUNC_API_FAST FUNC_API_REMOTE_ONLY; FUNC_API_SINCE(6) FUNC_API_FAST FUNC_API_REMOTE_ONLY;
void msg_clear(void) void msg_clear(void)
FUNC_API_SINCE(6) FUNC_API_REMOTE_ONLY; FUNC_API_SINCE(6) FUNC_API_REMOTE_ONLY;

View File

@ -7863,6 +7863,7 @@ void ex_echo(exarg_T *eap)
char *tofree = encode_tv2echo(&rettv, NULL); char *tofree = encode_tv2echo(&rettv, NULL);
if (*tofree != NUL) { if (*tofree != NUL) {
msg_ext_set_kind("echo"); msg_ext_set_kind("echo");
msg_ext_append = eap->cmdidx == CMD_echon;
msg_multiline(cstr_as_string(tofree), echo_hl_id, true, false, &need_clear); msg_multiline(cstr_as_string(tofree), echo_hl_id, true, false, &need_clear);
} }
xfree(tofree); xfree(tofree);

View File

@ -3185,7 +3185,8 @@ void msg_ext_ui_flush(void)
msg_ext_emit_chunk(); msg_ext_emit_chunk();
if (msg_ext_chunks->size > 0) { if (msg_ext_chunks->size > 0) {
Array *tofree = msg_ext_init_chunks(); Array *tofree = msg_ext_init_chunks();
ui_call_msg_show(cstr_as_string(msg_ext_kind), *tofree, msg_ext_overwrite, msg_ext_history); ui_call_msg_show(cstr_as_string(msg_ext_kind), *tofree, msg_ext_overwrite, msg_ext_history,
msg_ext_append);
if (msg_ext_history || strequal(msg_ext_kind, "return_prompt")) { if (msg_ext_history || strequal(msg_ext_kind, "return_prompt")) {
api_free_array(*tofree); api_free_array(*tofree);
} else { } else {
@ -3205,6 +3206,7 @@ void msg_ext_ui_flush(void)
} }
msg_ext_overwrite = false; msg_ext_overwrite = false;
msg_ext_history = false; msg_ext_history = false;
msg_ext_append = false;
msg_ext_kind = NULL; msg_ext_kind = NULL;
} }
} }

View File

@ -33,6 +33,8 @@ extern MessageHistoryEntry *msg_hist_last;
EXTERN bool msg_ext_need_clear INIT( = false); EXTERN bool msg_ext_need_clear INIT( = false);
// Set to true to force grouping a set of message chunks into a single `cmdline_show` event. // Set to true to force grouping a set of message chunks into a single `cmdline_show` event.
EXTERN bool msg_ext_skip_flush INIT( = false); EXTERN bool msg_ext_skip_flush INIT( = false);
// Set to true when message should be appended to previous message line.
EXTERN bool msg_ext_append INIT( = false);
/// allocated grid for messages. Used unless ext_messages is active. /// allocated grid for messages. Used unless ext_messages is active.
/// See also the description at msg_scroll_flush() /// See also the description at msg_scroll_flush()

View File

@ -221,7 +221,6 @@ describe('vim.ui_attach', function()
messages = { messages = {
{ {
content = { { '\nSave changes?\n', 6, 10 } }, content = { { '\nSave changes?\n', 6, 10 } },
history = false,
kind = 'confirm', kind = 'confirm',
}, },
}, },
@ -477,7 +476,6 @@ describe('vim.ui_attach', function()
}, },
{ {
content = { { 'Press ENTER or type command to continue', 100, 18 } }, content = { { 'Press ENTER or type command to continue', 100, 18 } },
history = false,
kind = 'return_prompt', kind = 'return_prompt',
}, },
}, },
@ -530,7 +528,6 @@ describe('vim.ui_attach', function()
}, },
{ {
content = { { 'Press ENTER or type command to continue', 100, 18 } }, content = { { 'Press ENTER or type command to continue', 100, 18 } },
history = false,
kind = 'return_prompt', kind = 'return_prompt',
}, },
}, },

View File

@ -60,7 +60,6 @@ describe('ui/ext_messages', function()
messages = { messages = {
{ {
content = { { '\ntest\n', 6, 10 } }, content = { { '\ntest\n', 6, 10 } },
history = false,
kind = 'confirm', kind = 'confirm',
}, },
}, },
@ -92,7 +91,6 @@ describe('ui/ext_messages', function()
messages = { messages = {
{ {
content = { { '\ntest\n', 6, 10 } }, content = { { '\ntest\n', 6, 10 } },
history = false,
kind = 'confirm', kind = 'confirm',
}, },
}, },
@ -104,17 +102,14 @@ describe('ui/ext_messages', function()
messages = { messages = {
{ {
content = { { '\ntest\n', 6, 10 } }, content = { { '\ntest\n', 6, 10 } },
history = false,
kind = 'confirm', kind = 'confirm',
}, },
{ {
content = { { '1' } }, content = { { '1' } },
history = false,
kind = 'echo', kind = 'echo',
}, },
{ {
content = { { 'Press ENTER or type command to continue', 6, 18 } }, content = { { 'Press ENTER or type command to continue', 6, 18 } },
history = false,
kind = 'return_prompt', kind = 'return_prompt',
}, },
}, },
@ -214,7 +209,6 @@ describe('ui/ext_messages', function()
messages = { messages = {
{ {
content = { { '?line ' } }, content = { { '?line ' } },
history = false,
kind = 'search_cmd', kind = 'search_cmd',
}, },
}, },
@ -238,7 +232,6 @@ describe('ui/ext_messages', function()
{ 'links to', 18, 5 }, { 'links to', 18, 5 },
{ ' SpecialChar' }, { ' SpecialChar' },
}, },
history = false,
kind = 'list_cmd', kind = 'list_cmd',
}, },
}, },
@ -285,7 +278,6 @@ describe('ui/ext_messages', function()
messages = { messages = {
{ {
content = { { 'The only match' } }, content = { { 'The only match' } },
history = false,
kind = 'completion', kind = 'completion',
}, },
}, },
@ -337,12 +329,10 @@ describe('ui/ext_messages', function()
{ 'guibg=', 18, 5 }, { 'guibg=', 18, 5 },
{ 'LightBlue' }, { 'LightBlue' },
}, },
history = false,
kind = 'list_cmd', kind = 'list_cmd',
}, },
{ {
content = { { '\n\tLast set from Lua (run Nvim with -V1 for more details)' } }, content = { { '\n\tLast set from Lua (run Nvim with -V1 for more details)' } },
history = false,
kind = 'verbose', kind = 'verbose',
}, },
{ {
@ -355,17 +345,14 @@ describe('ui/ext_messages', function()
{ 'guibg=', 18, 5 }, { 'guibg=', 18, 5 },
{ 'LightMagenta' }, { 'LightMagenta' },
}, },
history = false,
kind = 'list_cmd', kind = 'list_cmd',
}, },
{ {
content = { { '\n\tLast set from Lua (run Nvim with -V1 for more details)' } }, content = { { '\n\tLast set from Lua (run Nvim with -V1 for more details)' } },
history = false,
kind = 'verbose', kind = 'verbose',
}, },
{ {
content = { { 'Press ENTER or type command to continue', 6, 18 } }, content = { { 'Press ENTER or type command to continue', 6, 18 } },
history = false,
kind = 'return_prompt', kind = 'return_prompt',
}, },
}, },
@ -406,7 +393,6 @@ describe('ui/ext_messages', function()
}, },
{ {
content = { { 'Press ENTER or type command to continue', 6, 18 } }, content = { { 'Press ENTER or type command to continue', 6, 18 } },
history = false,
kind = 'return_prompt', kind = 'return_prompt',
}, },
}, },
@ -462,27 +448,22 @@ describe('ui/ext_messages', function()
messages = { messages = {
{ {
content = { { (':!%s\r\n[No write since last change]\n'):format(cmd) } }, content = { { (':!%s\r\n[No write since last change]\n'):format(cmd) } },
history = false,
kind = 'shell_cmd', kind = 'shell_cmd',
}, },
{ {
content = { { ('stdout%s\n'):format(t.is_os('win') and '\r' or '') } }, content = { { ('stdout%s\n'):format(t.is_os('win') and '\r' or '') } },
history = false,
kind = 'shell_out', kind = 'shell_out',
}, },
{ {
content = { { ('stderr%s\n'):format(t.is_os('win') and '\r' or ''), 9, 71 } }, content = { { ('stderr%s\n'):format(t.is_os('win') and '\r' or ''), 9, 71 } },
history = false,
kind = 'shell_err', kind = 'shell_err',
}, },
{ {
content = { { '\nshell returned 3\n\n' } }, content = { { '\nshell returned 3\n\n' } },
history = false,
kind = 'shell_ret', kind = 'shell_ret',
}, },
{ {
content = { { 'Press ENTER or type command to continue', 6, 18 } }, content = { { 'Press ENTER or type command to continue', 6, 18 } },
history = false,
kind = 'return_prompt', kind = 'return_prompt',
}, },
}, },
@ -497,7 +478,6 @@ describe('ui/ext_messages', function()
messages = { messages = {
{ {
content = { { '\nType Name Content', 101, 23 }, { '\n c ". ' } }, content = { { '\nType Name Content', 101, 23 }, { '\n c ". ' } },
history = false,
kind = 'list_cmd', kind = 'list_cmd',
}, },
}, },
@ -517,7 +497,6 @@ describe('ui/ext_messages', function()
{ 'ChanInfo', 101, 23 }, { 'ChanInfo', 101, 23 },
{ '\n*foo' }, { '\n*foo' },
}, },
history = false,
kind = 'list_cmd', kind = 'list_cmd',
}, },
}, },
@ -534,7 +513,6 @@ describe('ui/ext_messages', function()
messages = { messages = {
{ {
content = { { 'line 1\nline ' } }, content = { { 'line 1\nline ' } },
history = false,
kind = 'list_cmd', kind = 'list_cmd',
}, },
}, },
@ -592,7 +570,6 @@ describe('ui/ext_messages', function()
}, },
{ {
content = { { 'Press ENTER or type command to continue', 6, 18 } }, content = { { 'Press ENTER or type command to continue', 6, 18 } },
history = false,
kind = 'return_prompt', kind = 'return_prompt',
}, },
}, },
@ -623,7 +600,6 @@ describe('ui/ext_messages', function()
}, },
{ {
content = { { 'Press ENTER or type command to continue', 6, 18 } }, content = { { 'Press ENTER or type command to continue', 6, 18 } },
history = false,
kind = 'return_prompt', kind = 'return_prompt',
}, },
}, },
@ -688,7 +664,6 @@ describe('ui/ext_messages', function()
messages = { messages = {
{ {
content = { { 'Press ENTER or type command to continue', 6, 18 } }, content = { { 'Press ENTER or type command to continue', 6, 18 } },
history = false,
kind = 'return_prompt', kind = 'return_prompt',
}, },
}, },
@ -731,7 +706,6 @@ describe('ui/ext_messages', function()
messages = { messages = {
{ {
content = { { 'Press ENTER or type command to continue', 6, 18 } }, content = { { 'Press ENTER or type command to continue', 6, 18 } },
history = false,
kind = 'return_prompt', kind = 'return_prompt',
}, },
}, },
@ -757,7 +731,7 @@ describe('ui/ext_messages', function()
]], ]],
cmdline = { { abort = false } }, cmdline = { { abort = false } },
messages = { messages = {
{ content = { { '/line W [1/2]' } }, kind = 'search_count', history = false }, { content = { { '/line W [1/2]' } }, kind = 'search_count' },
}, },
} }
@ -769,7 +743,7 @@ describe('ui/ext_messages', function()
{1:~ }|*3 {1:~ }|*3
]], ]],
messages = { messages = {
{ content = { { '/line [2/2]' } }, kind = 'search_count', history = false }, { content = { { '/line [2/2]' } }, kind = 'search_count' },
}, },
} }
end) end)
@ -784,11 +758,10 @@ describe('ui/ext_messages', function()
]], ]],
cmdline = { { abort = false } }, cmdline = { { abort = false } },
messages = { messages = {
{ content = { { 'x #1' } }, kind = 'list_cmd', history = false }, { content = { { 'x #1' } }, kind = 'list_cmd' },
{ content = { { 'y #2' } }, kind = 'list_cmd', history = false }, { content = { { 'y #2' } }, kind = 'list_cmd' },
{ {
content = { { 'Press ENTER or type command to continue', 6, 18 } }, content = { { 'Press ENTER or type command to continue', 6, 18 } },
history = false,
kind = 'return_prompt', kind = 'return_prompt',
}, },
}, },
@ -908,7 +881,6 @@ describe('ui/ext_messages', function()
messages = { messages = {
{ {
content = { { 'Press ENTER or type command to continue', 6, 18 } }, content = { { 'Press ENTER or type command to continue', 6, 18 } },
history = false,
kind = 'return_prompt', kind = 'return_prompt',
}, },
}, },
@ -1156,7 +1128,6 @@ describe('ui/ext_messages', function()
messages = { messages = {
{ {
content = { { 'xyz' } }, content = { { 'xyz' } },
history = false,
kind = 'echo', kind = 'echo',
}, },
}, },
@ -1194,7 +1165,6 @@ describe('ui/ext_messages', function()
messages = { messages = {
{ {
content = { { 'Press ENTER or type command to continue', 6, 18 } }, content = { { 'Press ENTER or type command to continue', 6, 18 } },
history = false,
kind = 'return_prompt', kind = 'return_prompt',
}, },
}, },
@ -1326,7 +1296,6 @@ stack traceback:
{ '*', 18, 1 }, { '*', 18, 1 },
{ ' k' }, { ' k' },
}, },
history = false,
kind = 'list_cmd', kind = 'list_cmd',
}, },
}, },
@ -1347,7 +1316,6 @@ stack traceback:
messages = { messages = {
{ {
content = { { 'wildmenu wildmode\n' } }, content = { { 'wildmenu wildmode\n' } },
history = false,
kind = 'wildlist', kind = 'wildlist',
}, },
}, },
@ -1382,7 +1350,6 @@ stack traceback:
messages = { messages = {
{ {
content = { { 'Change "helllo" to:\n 1 "Hello"\n 2 "Hallo"\n 3 "Hullo"' } }, content = { { 'Change "helllo" to:\n 1 "Hello"\n 2 "Hallo"\n 3 "Hullo"' } },
history = false,
kind = 'confirm', kind = 'confirm',
}, },
}, },
@ -1405,7 +1372,6 @@ stack traceback:
messages = { messages = {
{ {
content = { { 'Change "helllo" to:\n 1 "Hello"\n 2 "Hallo"\n 3 "Hullo"' } }, content = { { 'Change "helllo" to:\n 1 "Hello"\n 2 "Hallo"\n 3 "Hullo"' } },
history = false,
kind = 'confirm', kind = 'confirm',
}, },
}, },
@ -1437,7 +1403,6 @@ stack traceback:
messages = { messages = {
{ {
content = { { 'input0\ninput1' } }, content = { { 'input0\ninput1' } },
history = false,
kind = 'confirm', kind = 'confirm',
}, },
}, },
@ -1487,7 +1452,6 @@ stack traceback:
{ {
content = { { '\n 1 %a "[No Name]" line 1' } }, content = { { '\n 1 %a "[No Name]" line 1' } },
kind = 'list_cmd', kind = 'list_cmd',
history = false,
}, },
}, },
} }
@ -1502,7 +1466,6 @@ stack traceback:
messages = { messages = {
{ {
content = { { 'Press ENTER or type command to continue', 6, 18 } }, content = { { 'Press ENTER or type command to continue', 6, 18 } },
history = false,
kind = 'return_prompt', kind = 'return_prompt',
}, },
}, },
@ -1631,11 +1594,7 @@ stack traceback:
{1:~ }|*4 {1:~ }|*4
]], ]],
messages = { messages = {
{ { content = { { 'bar' } }, kind = 'echo' },
content = { { 'bar' } },
history = false,
kind = 'echo',
},
}, },
}) })
feed('g<lt>') feed('g<lt>')
@ -1647,7 +1606,6 @@ stack traceback:
messages = { messages = {
{ {
content = { { 'Press ENTER or type command to continue', 6, 18 } }, content = { { 'Press ENTER or type command to continue', 6, 18 } },
history = false,
kind = 'return_prompt', kind = 'return_prompt',
}, },
}, },
@ -1685,7 +1643,6 @@ stack traceback:
messages = { messages = {
{ {
content = { { 'Press ENTER or type command to continue', 6, 18 } }, content = { { 'Press ENTER or type command to continue', 6, 18 } },
history = false,
kind = 'return_prompt', kind = 'return_prompt',
}, },
}, },
@ -1708,7 +1665,6 @@ stack traceback:
messages = { messages = {
{ {
content = { { ' shiftwidth=8\n tabstop=8\n softtabstop=0' } }, content = { { ' shiftwidth=8\n tabstop=8\n softtabstop=0' } },
history = false,
kind = 'list_cmd', kind = 'list_cmd',
}, },
}, },
@ -1730,6 +1686,25 @@ stack traceback:
{1:~ }|*4 {1:~ }|*4
]]) ]])
end) end)
it(':echon sets append', function()
command('echo "foo" | echon " bar" | echon " baz"')
screen:expect({
grid = [[
^ |
{1:~ }|*4
]],
messages = {
{ content = { { 'foo' } }, kind = 'echo' },
{ content = { { ' bar' } }, kind = 'echo', append = true },
{ content = { { ' baz' } }, kind = 'echo', append = true },
{
content = { { 'Press ENTER or type command to continue', 6, 18 } },
kind = 'return_prompt',
},
},
})
end)
end) end)
describe('ui/builtin messages', function() describe('ui/builtin messages', function()
@ -2394,7 +2369,6 @@ describe('ui/ext_messages', function()
messages = { messages = {
{ {
content = { { 'Press ENTER or type command to continue', 6, 18 } }, content = { { 'Press ENTER or type command to continue', 6, 18 } },
history = false,
kind = 'return_prompt', kind = 'return_prompt',
}, },
}, },
@ -2476,7 +2450,7 @@ describe('ui/ext_messages', function()
]], ]],
cmdline = { { abort = false } }, cmdline = { { abort = false } },
messages = { messages = {
{ content = { { ' cmdheight=0' } }, kind = 'list_cmd', history = false }, { content = { { ' cmdheight=0' } }, kind = 'list_cmd' },
}, },
}) })
@ -2493,7 +2467,7 @@ describe('ui/ext_messages', function()
]], ]],
cmdline = { { abort = false } }, cmdline = { { abort = false } },
messages = { messages = {
{ content = { { ' laststatus=3' } }, kind = 'list_cmd', history = false }, { content = { { ' laststatus=3' } }, kind = 'list_cmd' },
}, },
}) })
@ -2514,7 +2488,7 @@ describe('ui/ext_messages', function()
]], ]],
cmdline = { { abort = false } }, cmdline = { { abort = false } },
messages = { messages = {
{ content = { { ' cmdheight=0' } }, kind = 'list_cmd', history = false }, { content = { { ' cmdheight=0' } }, kind = 'list_cmd' },
}, },
}) })
end) end)

View File

@ -1383,12 +1383,12 @@ function Screen:_handle_wildmenu_hide()
self.wildmenu_items, self.wildmenu_pos = nil, nil self.wildmenu_items, self.wildmenu_pos = nil, nil
end end
function Screen:_handle_msg_show(kind, chunks, replace_last, history) function Screen:_handle_msg_show(kind, chunks, replace_last, history, append)
local pos = #self.messages local pos = #self.messages
if not replace_last or pos == 0 then if not replace_last or pos == 0 then
pos = pos + 1 pos = pos + 1
end end
self.messages[pos] = { kind = kind, content = chunks, history = history } self.messages[pos] = { kind = kind, content = chunks, history = history, append = append }
end end
function Screen:_handle_msg_clear() function Screen:_handle_msg_clear()
@ -1507,7 +1507,8 @@ function Screen:_extstate_repr(attr_state)
messages[i] = { messages[i] = {
kind = entry.kind, kind = entry.kind,
content = self:_chunks_repr(entry.content, attr_state), content = self:_chunks_repr(entry.content, attr_state),
history = entry.history, history = entry.history or nil,
append = entry.append or nil,
} }
end end