mirror of
https://github.com/neovim/neovim.git
synced 2025-10-05 17:36:29 +00:00
Merge pull request #36004 from zeertzjq/tui-buf-overflow
fix(tui): handle sequence larger than entire buffer
This commit is contained in:
@@ -279,19 +279,19 @@ static void schedule_termrequest(Terminal *term, char *sequence, size_t sequence
|
|||||||
(void *)(intptr_t)term->cursor.col);
|
(void *)(intptr_t)term->cursor.col);
|
||||||
}
|
}
|
||||||
|
|
||||||
static int parse_osc8(VTermStringFragment frag, int *attr)
|
static int parse_osc8(const char *str, size_t len, int *attr)
|
||||||
FUNC_ATTR_NONNULL_ALL
|
FUNC_ATTR_NONNULL_ALL
|
||||||
{
|
{
|
||||||
// Parse the URI from the OSC 8 sequence and add the URL to our URL set.
|
// Parse the URI from the OSC 8 sequence and add the URL to our URL set.
|
||||||
// Skip the ID, we don't use it (for now)
|
// Skip the ID, we don't use it (for now)
|
||||||
size_t i = 0;
|
size_t i = 0;
|
||||||
for (; i < frag.len; i++) {
|
for (; i < len; i++) {
|
||||||
if (frag.str[i] == ';') {
|
if (str[i] == ';') {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (frag.str[i] != ';') {
|
if (str[i] != ';') {
|
||||||
// Invalid OSC sequence
|
// Invalid OSC sequence
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@@ -299,14 +299,13 @@ static int parse_osc8(VTermStringFragment frag, int *attr)
|
|||||||
// Move past the semicolon
|
// Move past the semicolon
|
||||||
i++;
|
i++;
|
||||||
|
|
||||||
if (i >= frag.len) {
|
if (i >= len) {
|
||||||
// Empty OSC 8, no URL
|
// Empty OSC 8, no URL
|
||||||
*attr = 0;
|
*attr = 0;
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
char *url = xmemdupz(&frag.str[i], frag.len - i + 1);
|
char *url = xmemdupz(str + i, len - i);
|
||||||
url[frag.len - i] = 0;
|
|
||||||
*attr = hl_add_url(0, url);
|
*attr = hl_add_url(0, url);
|
||||||
xfree(url);
|
xfree(url);
|
||||||
|
|
||||||
@@ -322,16 +321,7 @@ static int on_osc(int command, VTermStringFragment frag, void *user)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (command == 8) {
|
if (command != 8 && !has_event(EVENT_TERMREQUEST)) {
|
||||||
int attr = 0;
|
|
||||||
if (parse_osc8(frag, &attr)) {
|
|
||||||
VTermState *state = vterm_obtain_state(term->vt);
|
|
||||||
VTermValue value = { .number = attr };
|
|
||||||
vterm_state_set_penattr(state, VTERM_ATTR_URI, VTERM_VALUETYPE_INT, &value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!has_event(EVENT_TERMREQUEST)) {
|
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -341,8 +331,20 @@ static int on_osc(int command, VTermStringFragment frag, void *user)
|
|||||||
}
|
}
|
||||||
kv_concat_len(term->termrequest_buffer, frag.str, frag.len);
|
kv_concat_len(term->termrequest_buffer, frag.str, frag.len);
|
||||||
if (frag.final) {
|
if (frag.final) {
|
||||||
char *sequence = xmemdup(term->termrequest_buffer.items, term->termrequest_buffer.size);
|
if (command == 8) {
|
||||||
schedule_termrequest(user, sequence, term->termrequest_buffer.size);
|
int attr = 0;
|
||||||
|
const int off = STRLEN_LITERAL("\x1b]8;");
|
||||||
|
if (parse_osc8(term->termrequest_buffer.items + off,
|
||||||
|
term->termrequest_buffer.size - off, &attr)) {
|
||||||
|
VTermState *state = vterm_obtain_state(term->vt);
|
||||||
|
VTermValue value = { .number = attr };
|
||||||
|
vterm_state_set_penattr(state, VTERM_ATTR_URI, VTERM_VALUETYPE_INT, &value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (has_event(EVENT_TERMREQUEST)) {
|
||||||
|
char *sequence = xmemdup(term->termrequest_buffer.items, term->termrequest_buffer.size);
|
||||||
|
schedule_termrequest(user, sequence, term->termrequest_buffer.size);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
@@ -79,6 +79,7 @@ struct TUIData {
|
|||||||
Loop *loop;
|
Loop *loop;
|
||||||
unibi_var_t params[9];
|
unibi_var_t params[9];
|
||||||
char buf[OUTBUF_SIZE];
|
char buf[OUTBUF_SIZE];
|
||||||
|
char *buf_to_flush; ///< If non-null, flush this instead of buf[].
|
||||||
size_t bufpos;
|
size_t bufpos;
|
||||||
TermInput input;
|
TermInput input;
|
||||||
uv_loop_t write_loop;
|
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);
|
unibi_out(tui, unibi_cursor_address);
|
||||||
}
|
}
|
||||||
|
|
||||||
#define UNIBI_OUT(fn) \
|
#define UNIBI_OUT(fn, name_fn) \
|
||||||
do { \
|
do { \
|
||||||
const char *str = NULL; \
|
const char *str = NULL; \
|
||||||
if (unibi_index >= 0) { \
|
if (unibi_index >= 0) { \
|
||||||
@@ -1913,11 +1914,14 @@ retry: \
|
|||||||
unibi_format(vars, vars + 26, str, params, out, tui, pad, tui); \
|
unibi_format(vars, vars + 26, str, params, out, tui, pad, tui); \
|
||||||
if (tui->overflow) { \
|
if (tui->overflow) { \
|
||||||
tui->bufpos = orig_pos; \
|
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) { \
|
if (orig_pos > 0) { \
|
||||||
flush_buf(tui); \
|
flush_buf(tui); \
|
||||||
|
orig_pos = 0; \
|
||||||
goto retry; \
|
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; \
|
tui->cork = false; \
|
||||||
@@ -1925,11 +1929,15 @@ retry: \
|
|||||||
} while (0)
|
} while (0)
|
||||||
static void unibi_out(TUIData *tui, int unibi_index)
|
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)
|
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
|
#undef UNIBI_OUT
|
||||||
|
|
||||||
@@ -1949,8 +1957,14 @@ static void out(void *ctx, const char *str, size_t len)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
flush_buf(tui);
|
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);
|
memcpy(tui->buf + tui->bufpos, str, len);
|
||||||
tui->bufpos += len;
|
tui->bufpos += len;
|
||||||
@@ -2591,7 +2605,7 @@ static void flush_buf(TUIData *tui)
|
|||||||
bufs[0].base = pre;
|
bufs[0].base = pre;
|
||||||
bufs[0].len = UV_BUF_LEN(flush_buf_start(tui, pre, sizeof(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[1].len = UV_BUF_LEN(tui->bufpos);
|
||||||
|
|
||||||
bufs[2].base = post;
|
bufs[2].base = post;
|
||||||
@@ -2609,6 +2623,7 @@ static void flush_buf(TUIData *tui)
|
|||||||
}
|
}
|
||||||
uv_run(&tui->write_loop, UV_RUN_DEFAULT);
|
uv_run(&tui->write_loop, UV_RUN_DEFAULT);
|
||||||
}
|
}
|
||||||
|
tui->buf_to_flush = NULL;
|
||||||
tui->bufpos = 0;
|
tui->bufpos = 0;
|
||||||
tui->overflow = false;
|
tui->overflow = false;
|
||||||
}
|
}
|
||||||
|
@@ -388,6 +388,19 @@ describe(':terminal', function()
|
|||||||
^This is an {100:example} of a link |
|
^This is an {100:example} of a link |
|
||||||
|*6
|
|*6
|
||||||
]])
|
]])
|
||||||
|
-- Also works if OSC 8 is split into multiple fragments.
|
||||||
|
api.nvim_chan_send(chan, '\nThis is another \027]8;;https')
|
||||||
|
n.poke_eventloop()
|
||||||
|
api.nvim_chan_send(chan, '://')
|
||||||
|
n.poke_eventloop()
|
||||||
|
api.nvim_chan_send(chan, 'example')
|
||||||
|
n.poke_eventloop()
|
||||||
|
api.nvim_chan_send(chan, '.com\027\\EXAMPLE\027]8;;\027\\ of a link')
|
||||||
|
screen:expect([[
|
||||||
|
^This is an {100:example} of a link |
|
||||||
|
This is another {100:EXAMPLE} of a link |
|
||||||
|
|*5
|
||||||
|
]])
|
||||||
end)
|
end)
|
||||||
|
|
||||||
it('zoomout with large horizontal output #30374', function()
|
it('zoomout with large horizontal output #30374', function()
|
||||||
|
@@ -2513,8 +2513,8 @@ describe('TUI', function()
|
|||||||
]])
|
]])
|
||||||
child_exec_lua([[
|
child_exec_lua([[
|
||||||
vim.api.nvim_buf_set_lines(0, 0, 0, true, {'Hello'})
|
vim.api.nvim_buf_set_lines(0, 0, 0, true, {'Hello'})
|
||||||
local ns = vim.api.nvim_create_namespace('test')
|
_G.NS = vim.api.nvim_create_namespace('test')
|
||||||
vim.api.nvim_buf_set_extmark(0, ns, 0, 1, {
|
vim.api.nvim_buf_set_extmark(0, _G.NS, 0, 1, {
|
||||||
end_col = 3,
|
end_col = 3,
|
||||||
url = 'https://example.com',
|
url = 'https://example.com',
|
||||||
})
|
})
|
||||||
@@ -2522,6 +2522,19 @@ describe('TUI', function()
|
|||||||
retry(nil, 1000, function()
|
retry(nil, 1000, function()
|
||||||
eq({ { id = 0xE1EA0000, url = 'https://example.com' } }, exec_lua([[return _G.urls]]))
|
eq({ { id = 0xE1EA0000, url = 'https://example.com' } }, exec_lua([[return _G.urls]]))
|
||||||
end)
|
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)
|
end)
|
||||||
|
|
||||||
it('TermResponse works with vim.wait() from another autocommand #32706', function()
|
it('TermResponse works with vim.wait() from another autocommand #32706', function()
|
||||||
@@ -3996,6 +4009,44 @@ describe('TUI client', function()
|
|||||||
end
|
end
|
||||||
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()
|
it('throws error when no server exists', function()
|
||||||
clear()
|
clear()
|
||||||
local screen = tt.setup_child_nvim({
|
local screen = tt.setup_child_nvim({
|
||||||
|
Reference in New Issue
Block a user