fix(tui): handle sequence larger than entire buffer

This commit is contained in:
zeertzjq
2025-10-03 15:47:38 +08:00
parent b6b80824cc
commit f64475a226
2 changed files with 75 additions and 9 deletions

View File

@@ -79,6 +79,7 @@ struct TUIData {
Loop *loop;
unibi_var_t params[9];
char buf[OUTBUF_SIZE];
char *buf_to_flush; ///< If non-null, flush this instead of buf[].
size_t bufpos;
TermInput input;
uv_loop_t write_loop;
@@ -1895,7 +1896,7 @@ static void unibi_goto(TUIData *tui, int row, int col)
unibi_out(tui, unibi_cursor_address);
}
#define UNIBI_OUT(fn) \
#define UNIBI_OUT(fn, name_fn) \
do { \
const char *str = NULL; \
if (unibi_index >= 0) { \
@@ -1913,11 +1914,14 @@ retry: \
unibi_format(vars, vars + 26, str, params, out, tui, pad, tui); \
if (tui->overflow) { \
tui->bufpos = orig_pos; \
/* If orig_pos is 0, there's nothing to flush and retrying won't work. */ \
/* TODO(zeertzjq): should this situation still be handled? */ \
if (orig_pos > 0) { \
flush_buf(tui); \
orig_pos = 0; \
goto retry; \
} else { /* orig_pos == 0 */ \
/* There's nothing to flush and retrying won't work. */ \
ELOG("TUI: escape sequence for %s too long", name_fn(unibi_index)); \
tui->overflow = false; \
} \
} \
tui->cork = false; \
@@ -1925,11 +1929,15 @@ retry: \
} while (0)
static void unibi_out(TUIData *tui, int unibi_index)
{
UNIBI_OUT(unibi_get_str);
#define UNIBI_NAME_STR(i) unibi_name_str((unsigned)(i))
UNIBI_OUT(unibi_get_str, UNIBI_NAME_STR);
#undef UNIBI_NAME_STR
}
static void unibi_out_ext(TUIData *tui, int unibi_index)
{
UNIBI_OUT(unibi_get_ext_str);
#define UNIBI_GET_EXT_STR_NAME(i) unibi_get_ext_str_name(tui->ut, (unsigned)(i))
UNIBI_OUT(unibi_get_ext_str, UNIBI_GET_EXT_STR_NAME);
#undef UNIBI_GET_EXT_STR_NAME
}
#undef UNIBI_OUT
@@ -1949,8 +1957,14 @@ static void out(void *ctx, const char *str, size_t len)
return;
}
flush_buf(tui);
if (len > sizeof(tui->buf)) {
// Don't use tui->buf[] when the string to output is too long. #30794
tui->buf_to_flush = (char *)str;
tui->bufpos = len;
flush_buf(tui);
return;
}
}
// TODO(zeertzjq): handle string longer than buffer size? #30794
memcpy(tui->buf + tui->bufpos, str, len);
tui->bufpos += len;
@@ -2591,7 +2605,7 @@ static void flush_buf(TUIData *tui)
bufs[0].base = pre;
bufs[0].len = UV_BUF_LEN(flush_buf_start(tui, pre, sizeof(pre)));
bufs[1].base = tui->buf;
bufs[1].base = tui->buf_to_flush != NULL ? tui->buf_to_flush : tui->buf;
bufs[1].len = UV_BUF_LEN(tui->bufpos);
bufs[2].base = post;
@@ -2609,6 +2623,7 @@ static void flush_buf(TUIData *tui)
}
uv_run(&tui->write_loop, UV_RUN_DEFAULT);
}
tui->buf_to_flush = NULL;
tui->bufpos = 0;
tui->overflow = false;
}

View File

@@ -2513,8 +2513,8 @@ describe('TUI', function()
]])
child_exec_lua([[
vim.api.nvim_buf_set_lines(0, 0, 0, true, {'Hello'})
local ns = vim.api.nvim_create_namespace('test')
vim.api.nvim_buf_set_extmark(0, ns, 0, 1, {
_G.NS = vim.api.nvim_create_namespace('test')
vim.api.nvim_buf_set_extmark(0, _G.NS, 0, 1, {
end_col = 3,
url = 'https://example.com',
})
@@ -2522,6 +2522,19 @@ describe('TUI', function()
retry(nil, 1000, function()
eq({ { id = 0xE1EA0000, url = 'https://example.com' } }, exec_lua([[return _G.urls]]))
end)
-- No crash with very long URL #30794
child_exec_lua([[
vim.api.nvim_buf_set_extmark(0, _G.NS, 0, 3, {
end_col = 5,
url = 'https://example.com/' .. ('a'):rep(65536),
})
]])
retry(nil, nil, function()
eq({
{ id = 0xE1EA0000, url = 'https://example.com' },
{ id = 0xE1EA0001, url = 'https://example.com/' .. ('a'):rep(65536) },
}, exec_lua([[return _G.urls]]))
end)
end)
it('TermResponse works with vim.wait() from another autocommand #32706', function()
@@ -3996,6 +4009,44 @@ describe('TUI client', function()
end
end)
it('does not crash or hang with a very long title', function()
local server, _, screen_client = start_headless_server_and_client()
local server_exec_lua = tt.make_lua_executor(server)
if not server_exec_lua('return pcall(require, "ffi")') then
pending('missing LuaJIT FFI')
end
local bufname = api.nvim_buf_get_name(0)
-- Normally a title cannot be longer than the 65535-byte buffer as maketitle()
-- limits it length. Use FFI to send a very long title directly.
server_exec_lua([=[
local ffi = require('ffi')
local cstr = ffi.typeof('char[?]')
local function to_cstr(string)
return cstr(#string + 1, string)
end
ffi.cdef([[
typedef struct { char *data; size_t size; } String;
void ui_call_set_title(String title);
]])
local len = 65536
local title = ffi.new('String', { data = to_cstr(('a'):rep(len)), size = len })
ffi.C.ui_call_set_title(title)
]=])
screen_client:expect_unchanged()
assert_log('TUI: escape sequence for ext%.set_title too long', testlog)
eq(bufname, api.nvim_buf_get_var(0, 'term_title'))
-- Following escape sequences are not affected.
server:request('nvim_set_option_value', 'title', true, {})
retry(nil, nil, function()
eq('[No Name] + - Nvim', api.nvim_buf_get_var(0, 'term_title'))
end)
end)
it('throws error when no server exists', function()
clear()
local screen = tt.setup_child_nvim({