aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJustin M. Keyes <justinkz@gmail.com>2019-08-25 10:20:38 +0200
committerJustin M. Keyes <justinkz@gmail.com>2019-08-27 22:13:45 +0200
commit5b41070c639f979023178042bea8e5fcc8a898fe (patch)
treec7e3c39c63e325950b8b123ed567e38898f406a1
parentbfc5a18f4b6cb4bc2335440254c346d731063b46 (diff)
downloadrneovim-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.c23
-rw-r--r--src/nvim/lua/vim.lua30
-rw-r--r--src/nvim/tui/input.c4
-rw-r--r--src/nvim/tui/input.h3
-rw-r--r--test/functional/terminal/tui_spec.lua211
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()