diff options
author | Justin M. Keyes <justinkz@gmail.com> | 2019-08-25 10:20:38 +0200 |
---|---|---|
committer | Justin M. Keyes <justinkz@gmail.com> | 2019-08-27 22:13:45 +0200 |
commit | 5b41070c639f979023178042bea8e5fcc8a898fe (patch) | |
tree | c7e3c39c63e325950b8b123ed567e38898f406a1 | |
parent | bfc5a18f4b6cb4bc2335440254c346d731063b46 (diff) | |
download | rneovim-5b41070c639f979023178042bea8e5fcc8a898fe.tar.gz rneovim-5b41070c639f979023178042bea8e5fcc8a898fe.tar.bz2 rneovim-5b41070c639f979023178042bea8e5fcc8a898fe.zip |
paste: implement redo (AKA dot-repeat)
- Normal-mode redo idiom(?): prepend "i" and append ESC.
- Insert-mode only needs AppendToRedobuffLit().
- Cmdline-mode: only paste the first line.
-rw-r--r-- | src/nvim/api/vim.c | 23 | ||||
-rw-r--r-- | src/nvim/lua/vim.lua | 30 | ||||
-rw-r--r-- | src/nvim/tui/input.c | 4 | ||||
-rw-r--r-- | src/nvim/tui/input.h | 3 | ||||
-rw-r--r-- | test/functional/terminal/tui_spec.lua | 211 |
5 files changed, 204 insertions, 67 deletions
diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index b355491dcc..6d6fd85266 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -1212,7 +1212,7 @@ Dictionary nvim_get_namespaces(void) /// Sets redo/undo. Faster than |nvim_input()|. /// /// @param data Multiline input. May be binary (containing NUL bytes). -/// @param phase Pass -1 to paste as one big buffer (i.e. without streaming). +/// @param phase -1: paste in a single call (i.e. without streaming). /// To "stream" a paste, call `nvim_paste` sequentially with /// these `phase` values: /// - 1: starts the paste (exactly once) @@ -1227,8 +1227,13 @@ Boolean nvim_paste(String data, Integer phase, Error *err) api_set_error(err, kErrorTypeValidation, "Invalid phase: %"PRId64, phase); return false; } + if (!(State & CMDLINE) && !(State & INSERT) && (phase == -1 || phase == 1)) { + ResetRedobuff(); + AppendCharToRedobuff('a'); // Dot-repeat. + } + Array lines = string_to_array(data); Array args = ARRAY_DICT_INIT; - ADD(args, ARRAY_OBJ(string_to_array(data))); + ADD(args, ARRAY_OBJ(lines)); ADD(args, INTEGER_OBJ(phase)); Object rv = nvim_execute_lua(STATIC_CSTR_AS_STRING("return vim._paste(...)"), @@ -1236,6 +1241,20 @@ Boolean nvim_paste(String data, Integer phase, Error *err) // Abort paste if handler does not return true. bool ok = !ERROR_SET(err) && (rv.type == kObjectTypeBoolean && rv.data.boolean); + if (ok && !(State & CMDLINE)) { // Dot-repeat. + for (size_t i = 0; i < lines.size; i++) { + String s = lines.items[i].data.string; + assert(data.size <= INT_MAX); + AppendToRedobuffLit((char_u *)s.data, (int)s.size); + // readfile()-style: "\n" is indicated by presence of N+1 item. + if (i + 1 < lines.size) { + AppendCharToRedobuff(NL); + } + } + } + if (!(State & CMDLINE) && !(State & INSERT) && (phase == -1 || phase == 3)) { + AppendCharToRedobuff(ESC); // Dot-repeat. + } api_free_object(rv); api_free_array(args); diff --git a/src/nvim/lua/vim.lua b/src/nvim/lua/vim.lua index 59438c8667..05951fbd0f 100644 --- a/src/nvim/lua/vim.lua +++ b/src/nvim/lua/vim.lua @@ -93,35 +93,32 @@ local function _os_proc_children(ppid) return children end --- Default paste function. +-- Default nvim_paste() handler. local _paste = (function() - local tdots = 0 - local tredraw = 0 - local tick = 0 + local tdots, tredraw, tick, got_line1 = 0, 0, 0, false return function(lines, phase) local call = vim.api.nvim_call_function local now = vim.loop.now() local mode = call('mode', {}):sub(1,1) - if phase == 1 then - tdots = now - tredraw = now - tick = 0 - -- TODO - -- if mode == 'i' or mode == 'R' then - -- nvim_cancel() - -- end + if phase < 2 then -- Reset flags. + tdots, tredraw, tick, got_line1 = now, now, 0, false end - if mode == 'i' or mode == 'R' then + if mode == 'c' and not got_line1 then -- cmdline-mode: paste only 1 line. + got_line1 = (#lines > 1) + vim.api.nvim_set_option('paste', true) -- For nvim_input(). + local line1, _ = string.gsub(lines[1], '[\r\n\012\027]', ' ') + vim.api.nvim_input(line1) -- Scrub "\r". + elseif mode == 'i' or mode == 'R' then vim.api.nvim_put(lines, 'c', false, true) else vim.api.nvim_put(lines, 'c', true, true) end - if (now - tredraw >= 1000) or phase == 1 or phase == 3 then + if (now - tredraw >= 1000) or phase == -1 or phase > 2 then tredraw = now vim.api.nvim_command('redraw') vim.api.nvim_command('redrawstatus') end - if (now - tdots >= 100) then + if phase ~= -1 and (now - tdots >= 100) then local dots = ('.'):rep(tick % 4) tdots = now tick = tick + 1 @@ -129,8 +126,9 @@ local _paste = (function() -- message when there are zero dots. vim.api.nvim_command(('echo "%s"'):format(dots)) end - if phase == 3 then + if phase == -1 or phase == 3 then vim.api.nvim_command('echo ""') + vim.api.nvim_set_option('paste', false) end return true -- Paste will not continue if not returning `true`. end diff --git a/src/nvim/tui/input.c b/src/nvim/tui/input.c index 33062e88d3..f9f39c36ff 100644 --- a/src/nvim/tui/input.c +++ b/src/nvim/tui/input.c @@ -373,9 +373,9 @@ static bool handle_bracketed_paste(TermInput *input) tinput_flush(input, true); // Paste phase: "first-chunk". input->paste = 1; - } else { + } else if (input->paste != 0) { // Paste phase: "last-chunk". - input->paste = 3; + input->paste = input->paste == 2 ? 3 : -1; tinput_flush(input, true); // Paste phase: "disabled". input->paste = 0; diff --git a/src/nvim/tui/input.h b/src/nvim/tui/input.h index 26a8447eb2..a4071fab40 100644 --- a/src/nvim/tui/input.h +++ b/src/nvim/tui/input.h @@ -9,7 +9,8 @@ typedef struct term_input { int in_fd; - uint8_t paste; // Phases: 0=disabled 1=first-chunk 2=continue 3=last-chunk + // Phases: -1=all 0=disabled 1=first-chunk 2=continue 3=last-chunk + int8_t paste; bool waiting; TermKey *tk; #if TERMKEY_VERSION_MAJOR > 0 || TERMKEY_VERSION_MINOR > 18 diff --git a/test/functional/terminal/tui_spec.lua b/test/functional/terminal/tui_spec.lua index e6d9dcddb9..08ff06e0a5 100644 --- a/test/functional/terminal/tui_spec.lua +++ b/test/functional/terminal/tui_spec.lua @@ -49,6 +49,24 @@ describe('TUI', function() screen:detach() end) + -- Wait for mode in the child Nvim (avoid "typeahead race" #10826). + local function wait_for_mode(mode) + retry(nil, nil, function() + local _, m = child_session:request('nvim_get_mode') + eq(mode, m.mode) + end) + end + + -- Assert buffer contents in the child Nvim. + local function expect_child_buf_lines(expected) + assert(type({}) == type(expected)) + retry(nil, nil, function() + local _, buflines = child_session:request( + 'nvim_buf_get_lines', 0, 0, -1, false) + eq(expected, buflines) + end) + end + it('rapid resize #7572 #7628', function() -- Need buffer rows to provoke the behavior. feed_data(":edit test/functional/fixtures/bigfile.txt:") @@ -136,7 +154,7 @@ describe('TUI', function() ]]) end) - it('accepts ascii control sequences', function() + it('accepts ASCII control sequences', function() feed_data('i') feed_data('\022\007') -- ctrl+g feed_data('\022\022') -- ctrl+v @@ -187,25 +205,142 @@ describe('TUI', function() | {3:-- TERMINAL --} | ]]) + -- Dot-repeat/redo. + feed_data('2.') + screen:expect([[ + "pasted from terminapasted from terminalpasted fro| + m termina{1:l}l" | + {4:~ }| + {4:~ }| + {5:[No Name] [+] }| + | + {3:-- TERMINAL --} | + ]]) + -- Undo. + feed_data('u') + expect_child_buf_lines({'"pasted from terminal"'}) + feed_data('u') + expect_child_buf_lines({''}) + end) + + it('paste: normal-mode', function() + feed_data(':set ruler') + wait_for_mode('c') + feed_data('\n') + wait_for_mode('n') + local expected = {'line 1', ' line 2', 'ESC:\027 / CR: \013'} + local expected_attr = { + [3] = {bold = true}, + [4] = {foreground = tonumber('0x00000c')}, + [5] = {bold = true, reverse = true}, + [11] = {foreground = tonumber('0x000051')}, + [12] = {reverse = true, foreground = tonumber('0x000051')}, + } + -- "bracketed paste" + feed_data('\027[200~'..table.concat(expected,'\n')..'\027[201~') + screen:expect{ + grid=[[ + line 1 | + line 2 | + ESC:{11:^[} / CR: {12:^}{11:M} | + {4:~ }| + {5:[No Name] [+] 3,13-14 All}| + | + {3:-- TERMINAL --} | + ]], + attr_ids=expected_attr} + -- Dot-repeat/redo. + feed_data('.') + screen:expect{ + grid=[[ + line 2 | + ESC:{11:^[} / CR: {11:^M}line 1 | + line 2 | + ESC:{11:^[} / CR: {12:^}{11:M} | + {5:[No Name] [+] 5,13-14 Bot}| + | + {3:-- TERMINAL --} | + ]], + attr_ids=expected_attr} + -- Undo. + feed_data('u') + expect_child_buf_lines(expected) + feed_data('u') + expect_child_buf_lines({''}) + end) + + it('paste: cmdline-mode inserts 1 line', function() + feed_data('ifoo\n') -- Insert some text (for dot-repeat later). + feed_data('\027:""') -- Enter Cmdline-mode. + feed_data('\027[D') -- <Left> to place cursor between quotes. + wait_for_mode('c') + -- "bracketed paste" + feed_data('\027[200~line 1\nline 2\n\027[201~') + screen:expect{grid=[[ + foo | + | + {4:~ }| + {4:~ }| + {5:[No Name] [+] }| + :"line 1{1:"} | + {3:-- TERMINAL --} | + ]]} + -- Dot-repeat/redo. + feed_data('\027\000') + wait_for_mode('n') + feed_data('.') + screen:expect{grid=[[ + foo | + foo | + {1: } | + {4:~ }| + {5:[No Name] [+] }| + | + {3:-- TERMINAL --} | + ]]} + end) + + it('paste: cmdline-mode collects chunks of unfinished line', function() + local function expect_cmdline(expected) + retry(nil, nil, function() + local _, cmdline = child_session:request( + 'nvim_call_function', 'getcmdline', {}) + eq(expected, cmdline) + end) + end + feed_data('\027:""') -- Enter Cmdline-mode. + feed_data('\027[D') -- <Left> to place cursor between quotes. + wait_for_mode('c') + feed_data('\027[200~stuff 1 ') + expect_cmdline('"stuff 1 "') + -- Discards everything after the first line. + feed_data('more\nstuff 2\nstuff 3\n') + expect_cmdline('"stuff 1 more"') + feed_data('stuff 3') + expect_cmdline('"stuff 1 more"') + -- End the paste sequence. + feed_data('\027[201~') + feed_data(' typed') + expect_cmdline('"stuff 1 more typed"') + end) + + -- TODO + it('paste: other modes', function() + -- Other modes act like CTRL-C + paste. + end) + + it("paste: in 'nomodifiable' buffer", function() end) it('paste: exactly 64 bytes #10311', function() local expected = string.rep('z', 64) feed_data('i') - -- Wait for Insert-mode (avoid "typeahead race" #10826). - retry(nil, nil, function() - local _, m = child_session:request('nvim_get_mode') - eq('i', m.mode) - end) + wait_for_mode('i') -- "bracketed paste" feed_data('\027[200~'..expected..'\027[201~') feed_data(' end') expected = expected..' end' - retry(nil, nil, function() - local _, buflines = child_session:request( - 'nvim_buf_get_lines', 0, 0, -1, false) - eq({expected}, buflines) - end) + expect_child_buf_lines({expected}) screen:expect([[ zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz| zzzzzzzzzzzzzz end{1: } | @@ -218,24 +353,16 @@ describe('TUI', function() end) it('paste: big burst of input', function() - feed_data(':set ruler\013') + feed_data(':set ruler\n') local t = {} for i = 1, 3000 do t[i] = 'item ' .. tostring(i) end feed_data('i') - -- Wait for Insert-mode (avoid "typeahead race" #10826). - retry(nil, nil, function() - local _, m = child_session:request('nvim_get_mode') - eq('i', m.mode) - end) + wait_for_mode('i') -- "bracketed paste" feed_data('\027[200~'..table.concat(t, '\n')..'\027[201~') - retry(nil, nil, function() - local _, buflines = child_session:request( - 'nvim_buf_get_lines', 0, 0, -1, false) - eq(t, buflines) - end) + expect_child_buf_lines(t) feed_data(' end') screen:expect([[ item 2997 | @@ -246,9 +373,22 @@ describe('TUI', function() {3:-- INSERT --} | {3:-- TERMINAL --} | ]]) + feed_data('\027\000') -- ESC: go to Normal mode. + wait_for_mode('n') + -- Dot-repeat/redo. + feed_data('.') + screen:expect([[ + item 2997 | + item 2998 | + item 2999 | + item 3000 en{1:d}d | + {5:[No Name] [+] 5999,13 Bot}| + | + {3:-- TERMINAL --} | + ]]) end) - it('forwards spurious "start paste" sequence', function() + it('paste: forwards spurious "start paste" code', function() -- If multiple "start paste" sequences are sent without a corresponding -- "stop paste" sequence, only the first occurrence should be consumed. @@ -280,7 +420,7 @@ describe('TUI', function() }} end) - it('ignores spurious "stop paste" sequence', function() + it('paste: ignores spurious "stop paste" code', function() -- If "stop paste" sequence is received without a preceding "start paste" -- sequence, it should be ignored. feed_data('i') @@ -298,28 +438,7 @@ describe('TUI', function() end) -- TODO - it('paste: normal-mode', function() - end) - - -- TODO - it('paste: command-mode inserts 1 line', function() - end) - - -- TODO - it('paste: sets undo-point after consecutive pastes', function() - end) - - it('paste: other modes', function() - -- Other modes act like CTRL-C + paste. - end) - - -- TODO - it('paste: handles missing "stop paste" sequence', function() - end) - - -- TODO: error when pasting into 'nomodifiable' buffer: - -- [error @ do_put:2656] 17043 - Failed to save undo information - it("handles 'nomodifiable' buffer gracefully", function() + it('paste: handles missing "stop paste" code', function() end) it('allows termguicolors to be set at runtime', function() |