From b47b0caba8586df57778f8cee07e5a9fa9ddb47c Mon Sep 17 00:00:00 2001 From: luukvbaal Date: Tue, 10 Mar 2026 15:35:24 +0100 Subject: [PATCH] fix(message): concatenate multi-chunk nvim_echo({err}) for exception message #38131 Problem: Exception error message only prints the first chunk of a multi-chunk nvim_echo() message. Solution: Concatenate consecutive message chunks in the exception message list. --- src/nvim/ex_eval.c | 8 +++++++- src/nvim/message.c | 10 +++++----- test/functional/api/vim_spec.lua | 7 +++++++ 3 files changed, 19 insertions(+), 6 deletions(-) diff --git a/src/nvim/ex_eval.c b/src/nvim/ex_eval.c index 9f997b76ed..cedb58b4d0 100644 --- a/src/nvim/ex_eval.c +++ b/src/nvim/ex_eval.c @@ -155,7 +155,7 @@ bool aborted_in_try(void) /// When several messages appear in the same command, the first is usually the /// most specific one and used as the exception value. The "severe" flag can be /// set to true, if a later but severer message should be used instead. -bool cause_errthrow(const char *mesg, bool multiline, bool severe, bool *ignore) +bool cause_errthrow(const char *mesg, bool multiline, bool concat, bool severe, bool *ignore) FUNC_ATTR_NONNULL_ALL { msglist_T *elem; @@ -241,6 +241,12 @@ bool cause_errthrow(const char *mesg, bool multiline, bool severe, bool *ignore) if (msg_list != NULL) { msglist_T **plist = msg_list; while (*plist != NULL) { + // Concatenate (a multihl message) instead. + if ((*plist)->next == NULL && concat) { + (*plist)->msg = xrealloc((*plist)->msg, strlen((*plist)->msg) + strlen(mesg) + 1); + (*plist)->throw_msg = strcat((*plist)->msg, mesg); + return true; + } plist = &(*plist)->next; } diff --git a/src/nvim/message.c b/src/nvim/message.c index adcda9d5af..c6d4cd95c6 100644 --- a/src/nvim/message.c +++ b/src/nvim/message.c @@ -292,8 +292,8 @@ void msg_multiline(String str, int hl_id, bool check_int, bool hist, bool *need_ } } -// Avoid starting a new message for each chunk and adding message to history in msg_keep(). -static bool is_multihl = false; +// Ensure a single msg_show event, msg_list and history entry for entire multihl message. +static int is_multihl = 0; /// Format a progress message, adding title and percent if given. /// @@ -360,7 +360,6 @@ MsgID msg_multihl(MsgID id, HlMessage hl_msg, const char *kind, bool history, bo if (kind != NULL) { msg_ext_set_kind(kind); } - is_multihl = true; msg_ext_skip_flush = true; // provide a new id if not given @@ -384,6 +383,7 @@ MsgID msg_multihl(MsgID id, HlMessage hl_msg, const char *kind, bool history, bo for (uint32_t i = 0; i < kv_size(hl_msg); i++) { HlMessageChunk chunk = kv_A(hl_msg, i); + is_multihl++; if (err) { emsg_multiline(chunk.text.data, kind, chunk.hl_id, true); } else { @@ -397,7 +397,7 @@ MsgID msg_multihl(MsgID id, HlMessage hl_msg, const char *kind, bool history, bo } msg_ext_skip_flush = false; - is_multihl = false; + is_multihl = 0; no_wait_return--; msg_end(); @@ -776,7 +776,7 @@ bool emsg_multiline(const char *s, const char *kind, int hl_id, bool multiline) // be found, the message will be displayed later on.) "ignore" is set // when the message should be ignored completely (used for the // interrupt message). - if (cause_errthrow(s, multiline, severe, &ignore)) { + if (cause_errthrow(s, multiline, is_multihl > 1, severe, &ignore)) { if (!ignore) { did_emsg++; } diff --git a/test/functional/api/vim_spec.lua b/test/functional/api/vim_spec.lua index 6029da0fc4..6447cbd0e4 100644 --- a/test/functional/api/vim_spec.lua +++ b/test/functional/api/vim_spec.lua @@ -445,6 +445,13 @@ describe('API', function() api.nvim_exec2('hi VisualNC', { output = true }) eq('VisualNC xxx cleared', api.nvim_exec2('hi VisualNC', { output = true }).output) end) + + it('captures multi-chunk err nvim_echo() #36883', function() + eq( + 'nvim_exec2(), line 1: Vim(call):abc', + pcall_err(request, 'nvim_exec2', 'call nvim_echo([["a"],["b"],["c"] ], 0, #{err:1})', {}) + ) + end) end) describe('nvim_command', function()