diff options
-rw-r--r-- | src/nvim/ex_cmds.c | 5 | ||||
-rw-r--r-- | src/nvim/ex_docmd.c | 2 | ||||
-rw-r--r-- | src/nvim/ex_getln.c | 2 | ||||
-rw-r--r-- | src/nvim/getchar.c | 29 | ||||
-rw-r--r-- | src/nvim/keymap.c | 1 | ||||
-rw-r--r-- | src/nvim/keymap.h | 3 | ||||
-rw-r--r-- | src/nvim/os/input.c | 9 | ||||
-rw-r--r-- | src/nvim/rbuffer.c | 18 | ||||
-rw-r--r-- | src/nvim/rbuffer.h | 2 | ||||
-rw-r--r-- | src/nvim/tui/input.c | 106 | ||||
-rw-r--r-- | src/nvim/tui/input.h | 4 | ||||
-rw-r--r-- | test/functional/helpers.lua | 1 | ||||
-rw-r--r-- | test/functional/shell/bang_filter_spec.lua | 49 | ||||
-rw-r--r-- | test/functional/terminal/helpers.lua | 46 | ||||
-rw-r--r-- | test/functional/terminal/tui_spec.lua | 106 | ||||
-rw-r--r-- | test/functional/ui/input_spec.lua | 70 | ||||
-rw-r--r-- | test/functional/ui/screen.lua | 3 |
17 files changed, 374 insertions, 82 deletions
diff --git a/src/nvim/ex_cmds.c b/src/nvim/ex_cmds.c index 6cb379f0b4..81abf2fa63 100644 --- a/src/nvim/ex_cmds.c +++ b/src/nvim/ex_cmds.c @@ -1246,8 +1246,9 @@ do_shell ( // 1" command to the terminal. ui_cursor_goto(msg_row, msg_col); (void)call_shell(cmd, flags, NULL); - did_check_timestamps = FALSE; - need_check_timestamps = TRUE; + msg_didout = true; + did_check_timestamps = false; + need_check_timestamps = true; // put the message cursor at the end of the screen, avoids wait_return() // to overwrite the text that the external command showed diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c index 3c09a3a2f8..f7162896ff 100644 --- a/src/nvim/ex_docmd.c +++ b/src/nvim/ex_docmd.c @@ -7624,7 +7624,7 @@ void update_topline_cursor(void) */ static void ex_normal(exarg_T *eap) { - if (curbuf->terminal) { + if (curbuf->terminal && State & TERM_FOCUS) { EMSG("Can't re-enter normal mode from terminal mode"); return; } diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c index a9d371d0eb..9739090d7c 100644 --- a/src/nvim/ex_getln.c +++ b/src/nvim/ex_getln.c @@ -302,7 +302,7 @@ getcmdline ( input_enable_events(); do { c = safe_vgetc(); - } while (c == K_IGNORE); + } while (c == K_IGNORE || c == K_PASTE); input_disable_events(); if (c == K_EVENT) { diff --git a/src/nvim/getchar.c b/src/nvim/getchar.c index 604425b3ba..990d0fb8e2 100644 --- a/src/nvim/getchar.c +++ b/src/nvim/getchar.c @@ -153,6 +153,7 @@ static char_u typebuf_init[TYPELEN_INIT]; /* initial typebuf.tb_buf */ static char_u noremapbuf_init[TYPELEN_INIT]; /* initial typebuf.tb_noremap */ static int last_recorded_len = 0; /* number of last recorded chars */ +static const uint8_t ui_toggle[] = { K_SPECIAL, KS_EXTRA, KE_PASTE, 0 }; #ifdef INCLUDE_GENERATED_DECLARATIONS # include "getchar.c.generated.h" @@ -1873,14 +1874,15 @@ static int vgetorpeek(int advance) } } - /* Check for match with 'pastetoggle' */ - if (*p_pt != NUL && mp == NULL && (State & (INSERT|NORMAL))) { - for (mlen = 0; mlen < typebuf.tb_len && p_pt[mlen]; - ++mlen) - if (p_pt[mlen] != typebuf.tb_buf[typebuf.tb_off - + mlen]) - break; - if (p_pt[mlen] == NUL) { /* match */ + // Check for a key that can toggle the 'paste' option + if (mp == NULL && (State & (INSERT|NORMAL))) { + bool match = typebuf_match_len(ui_toggle, &mlen); + if (!match && mlen != typebuf.tb_len && *p_pt != NUL) { + // didn't match ui_toggle_key and didn't try the whole typebuf, + // check the 'pastetoggle' + match = typebuf_match_len(p_pt, &mlen); + } + if (match) { /* write chars to script file(s) */ if (mlen > typebuf.tb_maplen) gotchars(typebuf.tb_buf + typebuf.tb_off @@ -4238,3 +4240,14 @@ static char_u * translate_mapping ( ga_append(&ga, NUL); return (char_u *)(ga.ga_data); } + +static bool typebuf_match_len(const uint8_t *str, int *mlen) +{ + int i; + for (i = 0; i < typebuf.tb_len && str[i]; i++) { + if (str[i] != typebuf.tb_buf[typebuf.tb_off + i]) + break; + } + *mlen = i; + return str[i] == NUL; // matched the whole string +} diff --git a/src/nvim/keymap.c b/src/nvim/keymap.c index 0c5c24184e..bf4f5e8c4d 100644 --- a/src/nvim/keymap.c +++ b/src/nvim/keymap.c @@ -284,6 +284,7 @@ static struct key_name_entry { {K_SNR, (char_u *)"SNR"}, {K_PLUG, (char_u *)"Plug"}, {K_CURSORHOLD, (char_u *)"CursorHold"}, + {K_PASTE, (char_u *)"Paste"}, {0, NULL} }; diff --git a/src/nvim/keymap.h b/src/nvim/keymap.h index 38b96b1b8c..119bff943a 100644 --- a/src/nvim/keymap.h +++ b/src/nvim/keymap.h @@ -247,6 +247,8 @@ enum key_extra { , KE_FOCUSGAINED /* focus gained */ , KE_FOCUSLOST /* focus lost */ , KE_EVENT // event + , KE_PASTE // special key to toggle the 'paste' option. + // sent only by UIs }; /* @@ -437,6 +439,7 @@ enum key_extra { #define K_CURSORHOLD TERMCAP2KEY(KS_EXTRA, KE_CURSORHOLD) #define K_EVENT TERMCAP2KEY(KS_EXTRA, KE_EVENT) +#define K_PASTE TERMCAP2KEY(KS_EXTRA, KE_PASTE) /* Bits for modifier mask */ /* 0x01 cannot be used, because the modifier must be 0x02 or higher */ diff --git a/src/nvim/os/input.c b/src/nvim/os/input.c index 09f162f79d..e2cff2f9c0 100644 --- a/src/nvim/os/input.c +++ b/src/nvim/os/input.c @@ -171,10 +171,17 @@ size_t input_enqueue(String keys) } if (*ptr == '<') { - // Invalid key sequence, skip until the next '>' or until *end + char *old_ptr = ptr; + // Invalid or incomplete key sequence, skip until the next '>' or until + // *end do { ptr++; } while (ptr < end && *ptr != '>'); + if (*ptr != '>') { + // Incomplete key sequence, return without consuming. + ptr = old_ptr; + break; + } ptr++; continue; } diff --git a/src/nvim/rbuffer.c b/src/nvim/rbuffer.c index 0a04ba1954..b3805a3a28 100644 --- a/src/nvim/rbuffer.c +++ b/src/nvim/rbuffer.c @@ -24,11 +24,13 @@ RBuffer *rbuffer_new(size_t capacity) rv->size = 0; rv->write_ptr = rv->read_ptr = rv->start_ptr; rv->end_ptr = rv->start_ptr + capacity; + rv->temp = NULL; return rv; } void rbuffer_free(RBuffer *buf) { + xfree(buf->temp); xfree(buf); } @@ -69,12 +71,20 @@ char *rbuffer_write_ptr(RBuffer *buf, size_t *write_count) FUNC_ATTR_NONNULL_ALL return buf->write_ptr; } -// Set read and write pointer for an empty RBuffer to the beginning of the -// buffer. +// Reset an RBuffer so read_ptr is at the beginning of the memory. If +// necessary, this moves existing data by allocating temporary memory. void rbuffer_reset(RBuffer *buf) FUNC_ATTR_NONNULL_ALL { - if (buf->size == 0) { - buf->write_ptr = buf->read_ptr = buf->start_ptr; + size_t temp_size; + if ((temp_size = rbuffer_size(buf))) { + if (buf->temp == NULL) { + buf->temp = xmalloc(rbuffer_capacity(buf)); + } + rbuffer_read(buf, buf->temp, buf->size); + } + buf->read_ptr = buf->write_ptr = buf->start_ptr; + if (temp_size) { + rbuffer_write(buf, buf->temp, temp_size); } } diff --git a/src/nvim/rbuffer.h b/src/nvim/rbuffer.h index b205db0b5a..35fb16508e 100644 --- a/src/nvim/rbuffer.h +++ b/src/nvim/rbuffer.h @@ -72,6 +72,8 @@ struct rbuffer { rbuffer_callback full_cb, nonfull_cb; void *data; size_t size; + // helper memory used to by rbuffer_reset if required + char *temp; char *end_ptr, *read_ptr, *write_ptr; char start_ptr[]; }; diff --git a/src/nvim/tui/input.c b/src/nvim/tui/input.c index 6c362540d0..32b3f1583c 100644 --- a/src/nvim/tui/input.c +++ b/src/nvim/tui/input.c @@ -9,7 +9,8 @@ #include "nvim/os/input.h" #include "nvim/event/rstream.h" -#define PASTETOGGLE_KEY "<f37>" +#define PASTETOGGLE_KEY "<Paste>" +#define KEY_BUFFER_SIZE 0xfff #ifdef INCLUDE_GENERATED_DECLARATIONS # include "tui/input.c.generated.h" @@ -20,6 +21,9 @@ void term_input_init(TermInput *input, Loop *loop) input->loop = loop; input->paste_enabled = false; input->in_fd = 0; + input->key_buffer = rbuffer_new(KEY_BUFFER_SIZE); + uv_mutex_init(&input->key_buffer_mutex); + uv_cond_init(&input->key_buffer_cond); const char *term = os_getenv("TERM"); if (!term) { @@ -34,15 +38,13 @@ void term_input_init(TermInput *input, Loop *loop) rstream_init_fd(loop, &input->read_stream, input->in_fd, 0xfff, input); // initialize a timer handle for handling ESC with libtermkey time_watcher_init(loop, &input->timer_handle, input); - // Set the pastetoggle option to a special key that will be sent when - // \e[20{0,1}~/ are received - Error err = ERROR_INIT; - vim_set_option(cstr_as_string("pastetoggle"), - STRING_OBJ(cstr_as_string(PASTETOGGLE_KEY)), &err); } void term_input_destroy(TermInput *input) { + rbuffer_free(input->key_buffer); + uv_mutex_destroy(&input->key_buffer_mutex); + uv_cond_destroy(&input->key_buffer_cond); time_watcher_close(&input->timer_handle, NULL); stream_close(&input->read_stream, NULL); termkey_destroy(input->tk); @@ -59,25 +61,56 @@ void term_input_stop(TermInput *input) time_watcher_stop(&input->timer_handle); } -static void input_enqueue_event(void **argv) +static void input_done_event(void **argv) { - char *buf = argv[0]; - input_enqueue(cstr_as_string(buf)); - xfree(buf); + input_done(); } -static void input_done_event(void **argv) +static void wait_input_enqueue(void **argv) { - input_done(); + TermInput *input = argv[0]; + RBUFFER_UNTIL_EMPTY(input->key_buffer, buf, len) { + size_t consumed = input_enqueue((String){.data = buf, .size = len}); + if (consumed) { + rbuffer_consumed(input->key_buffer, consumed); + } + rbuffer_reset(input->key_buffer); + if (consumed < len) { + break; + } + } + uv_mutex_lock(&input->key_buffer_mutex); + input->waiting = false; + uv_cond_signal(&input->key_buffer_cond); + uv_mutex_unlock(&input->key_buffer_mutex); } -static void enqueue_input(char *buf, size_t size) +static void flush_input(TermInput *input, bool wait_until_empty) { - loop_schedule(&loop, event_create(1, input_enqueue_event, 1, - xstrndup(buf, size))); + size_t drain_boundary = wait_until_empty ? 0 : 0xff; + do { + uv_mutex_lock(&input->key_buffer_mutex); + loop_schedule(&loop, event_create(1, wait_input_enqueue, 1, input)); + input->waiting = true; + while (input->waiting) { + uv_cond_wait(&input->key_buffer_cond, &input->key_buffer_mutex); + } + uv_mutex_unlock(&input->key_buffer_mutex); + } while (rbuffer_size(input->key_buffer) > drain_boundary); } -static void forward_simple_utf8(TermKeyKey *key) +static void enqueue_input(TermInput *input, char *buf, size_t size) +{ + if (rbuffer_size(input->key_buffer) > + rbuffer_capacity(input->key_buffer) - 0xff) { + // don't ever let the buffer get too full or we risk putting incomplete keys + // into it + flush_input(input, false); + } + rbuffer_write(input->key_buffer, buf, size); +} + +static void forward_simple_utf8(TermInput *input, TermKeyKey *key) { size_t len = 0; char buf[64]; @@ -92,11 +125,10 @@ static void forward_simple_utf8(TermKeyKey *key) ptr++; } - buf[len] = 0; - enqueue_input(buf, len); + enqueue_input(input, buf, len); } -static void forward_modified_utf8(TermKey *tk, TermKeyKey *key) +static void forward_modified_utf8(TermInput *input, TermKeyKey *key) { size_t len; char buf[64]; @@ -105,19 +137,19 @@ static void forward_modified_utf8(TermKey *tk, TermKeyKey *key) && key->code.sym == TERMKEY_SYM_ESCAPE) { len = (size_t)snprintf(buf, sizeof(buf), "<Esc>"); } else { - len = termkey_strfkey(tk, buf, sizeof(buf), key, TERMKEY_FORMAT_VIM); + len = termkey_strfkey(input->tk, buf, sizeof(buf), key, TERMKEY_FORMAT_VIM); } - enqueue_input(buf, len); + enqueue_input(input, buf, len); } -static void forward_mouse_event(TermKey *tk, TermKeyKey *key) +static void forward_mouse_event(TermInput *input, TermKeyKey *key) { char buf[64]; size_t len = 0; int button, row, col; TermKeyMouseEvent ev; - termkey_interpret_mouse(tk, key, &ev, &button, &row, &col); + termkey_interpret_mouse(input->tk, key, &ev, &button, &row, &col); if (ev != TERMKEY_MOUSE_PRESS && ev != TERMKEY_MOUSE_DRAG) { return; @@ -159,7 +191,7 @@ static void forward_mouse_event(TermKey *tk, TermKeyKey *key) } len += (size_t)snprintf(buf + len, sizeof(buf) - len, "><%d,%d>", col, row); - enqueue_input(buf, len); + enqueue_input(input, buf, len); } static TermKeyResult tk_getkey(TermKey *tk, TermKeyKey *key, bool force) @@ -189,17 +221,17 @@ static void tk_getkeys(TermInput *input, bool force) while ((result = tk_getkey(input->tk, &key, force)) == TERMKEY_RES_KEY) { if (key.type == TERMKEY_TYPE_UNICODE && !key.modifiers) { - forward_simple_utf8(&key); + forward_simple_utf8(input, &key); } else if (key.type == TERMKEY_TYPE_UNICODE || key.type == TERMKEY_TYPE_FUNCTION || key.type == TERMKEY_TYPE_KEYSYM) { - forward_modified_utf8(input->tk, &key); + forward_modified_utf8(input, &key); } else if (key.type == TERMKEY_TYPE_MOUSE) { - forward_mouse_event(input->tk, &key); + forward_mouse_event(input, &key); } } - if (result != TERMKEY_RES_AGAIN) { + if (result != TERMKEY_RES_AGAIN || input->paste_enabled) { return; } @@ -230,21 +262,7 @@ static bool handle_bracketed_paste(TermInput *input) if (input->paste_enabled == enable) { return true; } - if (enable) { - // Get the current mode - int state = get_real_state(); - if (state & NORMAL) { - // Enter insert mode - enqueue_input("i", 1); - } else if (state & VISUAL) { - // Remove the selected text and enter insert mode - enqueue_input("c", 1); - } else if (!(state & INSERT)) { - // Don't mess with the paste option - return true; - } - } - enqueue_input(PASTETOGGLE_KEY, sizeof(PASTETOGGLE_KEY) - 1); + enqueue_input(input, PASTETOGGLE_KEY, sizeof(PASTETOGGLE_KEY) - 1); input->paste_enabled = enable; return true; } @@ -326,7 +344,7 @@ static void read_cb(Stream *stream, RBuffer *buf, size_t c, void *data, } } } while (rbuffer_size(input->read_stream.buffer)); - + flush_input(input, true); // Make sure the next input escape sequence fits into the ring buffer // without wrap around, otherwise it could be misinterpreted. rbuffer_reset(input->read_stream.buffer); diff --git a/src/nvim/tui/input.h b/src/nvim/tui/input.h index 033f53b4e2..d7ee2b9e52 100644 --- a/src/nvim/tui/input.h +++ b/src/nvim/tui/input.h @@ -10,10 +10,14 @@ typedef struct term_input { int in_fd; bool paste_enabled; + bool waiting; TermKey *tk; TimeWatcher timer_handle; Loop *loop; Stream read_stream; + RBuffer *key_buffer; + uv_mutex_t key_buffer_mutex; + uv_cond_t key_buffer_cond; } TermInput; #ifdef INCLUDE_GENERATED_DECLARATIONS diff --git a/test/functional/helpers.lua b/test/functional/helpers.lua index 9cd1fd7ab3..80cb1e5ce3 100644 --- a/test/functional/helpers.lua +++ b/test/functional/helpers.lua @@ -372,5 +372,6 @@ return { set_session = set_session, write_file = write_file, rmdir = rmdir, + mkdir = lfs.mkdir, exc_exec = exc_exec, } diff --git a/test/functional/shell/bang_filter_spec.lua b/test/functional/shell/bang_filter_spec.lua new file mode 100644 index 0000000000..964dbd1029 --- /dev/null +++ b/test/functional/shell/bang_filter_spec.lua @@ -0,0 +1,49 @@ +-- Specs for bang/filter commands + +local helpers = require('test.functional.helpers') +local feed, execute, clear = helpers.feed, helpers.execute, helpers.clear +local mkdir, write_file, rmdir = helpers.mkdir, helpers.write_file, helpers.rmdir + +local Screen = require('test.functional.ui.screen') + + +describe('issues', function() + local screen + + before_each(function() + clear() + rmdir('bang_filter_spec') + mkdir('bang_filter_spec') + write_file('bang_filter_spec/f1', 'f1') + write_file('bang_filter_spec/f2', 'f2') + write_file('bang_filter_spec/f3', 'f3') + screen = Screen.new() + screen:attach() + end) + + after_each(function() + rmdir('bang_filter_spec') + end) + + it('#3269 Last line of shell output is not truncated', function() + execute([[nnoremap <silent>\l :!ls bang_filter_spec<cr>]]) + feed([[\l]]) + screen:expect([[ + ~ | + ~ | + ~ | + ~ | + ~ | + ~ | + ~ | + ~ | + :!ls bang_filter_spec | + | + f1 | + f2 | + f3 | + Press ENTER or type command to continue^ | + ]]) + end) + +end) diff --git a/test/functional/terminal/helpers.lua b/test/functional/terminal/helpers.lua index e488495139..ae13aab277 100644 --- a/test/functional/terminal/helpers.lua +++ b/test/functional/terminal/helpers.lua @@ -1,7 +1,7 @@ local helpers = require('test.functional.helpers') local Screen = require('test.functional.ui.screen') local nvim_dir = helpers.nvim_dir -local execute, nvim = helpers.execute, helpers.nvim +local execute, nvim, wait = helpers.execute, helpers.nvim, helpers.wait local function feed_data(data) nvim('set_var', 'term_data', data) @@ -31,13 +31,15 @@ local function clear_attrs() feed_termcode('[0;10m') end local function enable_mouse() feed_termcode('[?1002h') end local function disable_mouse() feed_termcode('[?1002l') end +local default_command = '["'..nvim_dir..'/tty-test'..'"]' -local function screen_setup(extra_height) +local function screen_setup(extra_height, command) nvim('command', 'highlight TermCursor cterm=reverse') nvim('command', 'highlight TermCursorNC ctermbg=11') nvim('set_var', 'terminal_scrollback_buffer_size', 10) if not extra_height then extra_height = 0 end + if not command then command = default_command end local screen = Screen.new(50, 7 + extra_height) screen:set_default_attr_ids({ [1] = {reverse = true}, -- focused cursor @@ -56,25 +58,29 @@ local function screen_setup(extra_height) -- tty-test puts the terminal into raw mode and echoes all input. tests are -- done by feeding it with terminfo codes to control the display and -- verifying output with screen:expect. - execute('enew | call termopen(["'..nvim_dir..'/tty-test"]) | startinsert') - -- wait for "tty ready" to be printed before each test or the terminal may - -- still be in canonical mode(will echo characters for example) - -- - local empty_line = ' ' - local expected = { - 'tty ready ', - '{1: } ', - empty_line, - empty_line, - empty_line, - empty_line, - } - for i = 1, extra_height do - table.insert(expected, empty_line) - end + execute('enew | call termopen('..command..') | startinsert') + if command == default_command then + -- wait for "tty ready" to be printed before each test or the terminal may + -- still be in canonical mode(will echo characters for example) + -- + local empty_line = ' ' + local expected = { + 'tty ready ', + '{1: } ', + empty_line, + empty_line, + empty_line, + empty_line, + } + for i = 1, extra_height do + table.insert(expected, empty_line) + end - table.insert(expected, '-- TERMINAL -- ') - screen:expect(table.concat(expected, '\n')) + table.insert(expected, '-- TERMINAL -- ') + screen:expect(table.concat(expected, '\n')) + else + wait() + end return screen end diff --git a/test/functional/terminal/tui_spec.lua b/test/functional/terminal/tui_spec.lua new file mode 100644 index 0000000000..d604703fee --- /dev/null +++ b/test/functional/terminal/tui_spec.lua @@ -0,0 +1,106 @@ +-- Some sanity checks for the TUI using the builtin terminal emulator +-- as a simple way to send keys and assert screen state. +local Screen = require('test.functional.ui.screen') +local helpers = require('test.functional.helpers') +local thelpers = require('test.functional.terminal.helpers') +local feed = thelpers.feed_data +local execute = helpers.execute + +describe('tui', function() + local screen + + before_each(function() + helpers.clear() + screen = thelpers.screen_setup(0, '["'..helpers.nvim_prog..'", "-u", "NONE", "--cmd", "set noswapfile"]') + screen.timeout = 30000 -- pasting can be really slow in the TUI + screen:expect([[ + {1: } | + ~ | + ~ | + ~ | + [No Name] | + | + -- TERMINAL -- | + ]]) + end) + + after_each(function() + screen:detach() + end) + + it('accepts basic utf-8 input', function() + feed('iabc\ntest1\ntest2') + screen:expect([[ + abc | + test1 | + test2{1: } | + ~ | + [No Name] [+] | + -- INSERT -- | + -- TERMINAL -- | + ]]) + feed('\x1b') + screen:expect([[ + abc | + test1 | + test{1:2} | + ~ | + [No Name] [+] | + | + -- TERMINAL -- | + ]]) + end) + + it('automatically sends <Paste> for bracketed paste sequences', function() + feed('i\x1b[200~') + screen:expect([[ + {1: } | + ~ | + ~ | + ~ | + [No Name] | + -- INSERT (paste) -- | + -- TERMINAL -- | + ]]) + feed('pasted from terminal') + screen:expect([[ + pasted from terminal{1: } | + ~ | + ~ | + ~ | + [No Name] [+] | + -- INSERT (paste) -- | + -- TERMINAL -- | + ]]) + feed('\x1b[201~') + screen:expect([[ + pasted from terminal{1: } | + ~ | + ~ | + ~ | + [No Name] [+] | + -- INSERT -- | + -- TERMINAL -- | + ]]) + end) + + it('can handle arbitrarily long bursts of input', function() + execute('set ruler') + local t = {} + for i = 1, 3000 do + t[i] = 'item ' .. tostring(i) + end + feed('i\x1b[200~') + feed(table.concat(t, '\n')) + feed('\x1b[201~') + screen:expect([[ + item 2997 | + item 2998 | + item 2999 | + item 3000{1: } | + [No Name] [+] 3000,10 Bot| + -- INSERT -- | + -- TERMINAL -- | + ]]) + end) +end) diff --git a/test/functional/ui/input_spec.lua b/test/functional/ui/input_spec.lua index 81af908045..a7c8e02def 100644 --- a/test/functional/ui/input_spec.lua +++ b/test/functional/ui/input_spec.lua @@ -2,6 +2,7 @@ local helpers = require('test.functional.helpers') local clear, execute, nvim = helpers.clear, helpers.execute, helpers.nvim local feed, next_message, eq = helpers.feed, helpers.next_message, helpers.eq local expect = helpers.expect +local Screen = require('test.functional.ui.screen') describe('mappings', function() local cid @@ -40,7 +41,76 @@ describe('mappings', function() end) end) +describe('feeding large chunks of input with <Paste>', function() + local screen + before_each(function() + clear() + screen = Screen.new() + screen:attach() + execute('set ruler') + end) + + it('ok', function() + local t = {} + for i = 1, 20000 do + t[i] = 'item ' .. tostring(i) + end + feed('i<Paste>') + screen:expect([[ + ^ | + ~ | + ~ | + ~ | + ~ | + ~ | + ~ | + ~ | + ~ | + ~ | + ~ | + ~ | + ~ | + -- INSERT (paste) -- | + ]]) + feed(table.concat(t, '<Enter>')) + screen:expect([[ + item 19988 | + item 19989 | + item 19990 | + item 19991 | + item 19992 | + item 19993 | + item 19994 | + item 19995 | + item 19996 | + item 19997 | + item 19998 | + item 19999 | + item 20000^ | + -- INSERT (paste) -- | + ]]) + feed('<Paste>') + screen:expect([[ + item 19988 | + item 19989 | + item 19990 | + item 19991 | + item 19992 | + item 19993 | + item 19994 | + item 19995 | + item 19996 | + item 19997 | + item 19998 | + item 19999 | + item 20000^ | + -- INSERT -- 20000,11 Bot | + ]]) + end) +end) + describe('input utf sequences that contain CSI/K_SPECIAL', function() + before_each(clear) it('ok', function() feed('i…<esc>') expect('…') diff --git a/test/functional/ui/screen.lua b/test/functional/ui/screen.lua index 08c4bfae0b..1be83d745e 100644 --- a/test/functional/ui/screen.lua +++ b/test/functional/ui/screen.lua @@ -163,6 +163,7 @@ function Screen.new(width, height) height = 14 end local self = setmetatable({ + timeout = default_screen_timeout, title = '', icon = '', bell = false, @@ -248,7 +249,7 @@ function Screen:wait(check, timeout) return true end - run(nil, notification_cb, nil, timeout or default_screen_timeout) + run(nil, notification_cb, nil, timeout or self.timeout) if not checked then err = check() end |