fix(events): avoid recursive loop_uv_run() from vim.ui_attach() shell message

Problem:  vim.ui_attach() msg_show callback runs the risk of a recursive
          loop_uv_run() when trying to display a message from a shell
          command stream.
Solution: Schedule the message callback on the fast_events queue.
(cherry picked from commit fa302037f9)
This commit is contained in:
Luuk van Baal
2026-04-03 14:39:25 +02:00
committed by github-actions[bot]
parent c09e82d12a
commit c3e52bb264
2 changed files with 38 additions and 26 deletions

View File

@@ -283,7 +283,7 @@ void msg_multiline(String str, int hl_id, bool check_int, bool hist, bool *need_
if (check_int && got_int) {
return;
}
if (*s == '\n' || *s == TAB || *s == '\r') {
if (*s == '\n' || *s == TAB || *s == '\r' || *s == BELL) {
// Print all chars before the delimiter
msg_outtrans_len(chunk, (int)(s - chunk), hl_id, hist);
@@ -291,7 +291,11 @@ void msg_multiline(String str, int hl_id, bool check_int, bool hist, bool *need_
msg_clr_eos();
*need_clear = false;
}
msg_putchar_hl((uint8_t)(*s), hl_id);
if (*s == BELL) {
vim_beep(kOptBoFlagShell);
} else {
msg_putchar_hl((uint8_t)(*s), hl_id);
}
chunk = s + 1;
}
s++;

View File

@@ -7,6 +7,7 @@
#include "auto/config.h"
#include "klib/kvec.h"
#include "nvim/api/private/helpers.h"
#include "nvim/ascii_defs.h"
#include "nvim/buffer.h"
#include "nvim/charset.h"
@@ -1119,6 +1120,17 @@ static void out_data_ring(const char *output, size_t size)
}
}
static void out_data_event(void **argv)
{
bool need_clear = true;
int hl = (int)(intptr_t)argv[2] == STDERR_FILENO ? HLF_SE : HLF_SO;
msg_ext_set_kind((int)(intptr_t)argv[2] == STDERR_FILENO ? "shell_err" : "shell_out");
msg_ext_append = true;
msg_multiline(cbuf_as_string((char *)argv[0], (size_t)argv[1]), hl, false, false, &need_clear);
xfree(argv[0]);
ui_flush();
}
/// Continue to append data to last screen line.
///
/// @param output Data to append to screen lines.
@@ -1129,33 +1141,29 @@ static void out_data_append_to_screen(const char *output, size_t *count, int fd,
{
const char *p = output;
const char *end = output + *count;
msg_ext_set_kind(fd == STDERR_FILENO ? "shell_err" : "shell_out");
msg_ext_append = true;
// Note: this is not 100% precise:
// 1. we don't check if received continuation bytes are already invalid
// and we thus do some buffering that could be avoided
// 2. we don't compose chars over buffer boundaries, even if we see an
// incomplete UTF-8 sequence that could be composing with the last
// complete sequence.
// This will be corrected when we switch to vterm based implementation
while (p < end) {
if (*p == '\n' || *p == '\r' || *p == TAB || *p == BELL) {
msg_putchar_hl((uint8_t)(*p), fd == STDERR_FILENO ? HLF_SE : HLF_SO);
p++;
} else {
// Note: this is not 100% precise:
// 1. we don't check if received continuation bytes are already invalid
// and we thus do some buffering that could be avoided
// 2. we don't compose chars over buffer boundaries, even if we see an
// incomplete UTF-8 sequence that could be composing with the last
// complete sequence.
// This will be corrected when we switch to vterm based implementation
int i = *p ? utfc_ptr2len_len(p, (int)(end - p)) : 1;
if (!eof && i == 1 && utf8len_tab_zero[*(uint8_t *)p] > (end - p)) {
*count = (size_t)(p - output);
goto end;
}
msg_outtrans_len(p, i, fd == STDERR_FILENO ? HLF_SE : HLF_SO, false);
p += i;
int i = *p ? utfc_ptr2len_len(p, (int)*count - (int)(p - output)) : 1;
if (!eof && i == 1 && utf8len_tab_zero[*(uint8_t *)p] > (end - p)) {
*count = (size_t)(p - output);
break;
}
p += i;
}
// Process after uv_run() to avoid recursion in vim.ui_attach() msg_show callback #38664.
char *str = xmemdupz(output, *count);
if (ui_has(kUIMessages)) {
multiqueue_put(main_loop.fast_events, out_data_event,
(void *)str, (void *)*count, (void *)(intptr_t)fd);
} else {
out_data_event((void *[]){ (void *)str, (void *)*count, (void *)(intptr_t)fd });
}
end:
ui_flush();
}
static size_t out_data_cb(RStream *stream, const char *ptr, size_t count, void *data, bool eof)