diff options
Diffstat (limited to 'test')
-rw-r--r-- | test/functional/api/vim_spec.lua | 129 | ||||
-rw-r--r-- | test/functional/terminal/helpers.lua | 3 | ||||
-rw-r--r-- | test/functional/terminal/tui_spec.lua | 366 | ||||
-rw-r--r-- | test/functional/ui/input_spec.lua | 71 |
4 files changed, 466 insertions, 103 deletions
diff --git a/test/functional/api/vim_spec.lua b/test/functional/api/vim_spec.lua index 0cd81619c1..647fab5c43 100644 --- a/test/functional/api/vim_spec.lua +++ b/test/functional/api/vim_spec.lua @@ -5,6 +5,7 @@ local NIL = helpers.NIL local clear, nvim, eq, neq = helpers.clear, helpers.nvim, helpers.eq, helpers.neq local command = helpers.command local eval = helpers.eval +local expect = helpers.expect local funcs = helpers.funcs local iswin = helpers.iswin local meth_pcall = helpers.meth_pcall @@ -365,6 +366,126 @@ describe('API', function() end) end) + describe('nvim_paste', function() + it('validates args', function() + expect_err('Invalid phase: %-2', request, + 'nvim_paste', 'foo', -2) + expect_err('Invalid phase: 4', request, + 'nvim_paste', 'foo', 4) + end) + it('non-streaming', function() + -- With final "\n". + nvim('paste', 'line 1\nline 2\nline 3\n', -1) + expect([[ + line 1 + line 2 + line 3 + ]]) + -- Cursor follows the paste. + eq({0,4,1,0}, funcs.getpos('.')) + eq(false, nvim('get_option', 'paste')) + command('%delete _') + -- Without final "\n". + nvim('paste', 'line 1\nline 2\nline 3', -1) + expect([[ + line 1 + line 2 + line 3]]) + -- Cursor follows the paste. + eq({0,3,6,0}, funcs.getpos('.')) + eq(false, nvim('get_option', 'paste')) + end) + it('vim.paste() failure', function() + nvim('execute_lua', 'vim.paste = (function(lines, phase) error("fake fail") end)', {}) + expect_err([[Error executing lua: %[string "%<nvim>"]:1: fake fail]], + request, 'nvim_paste', 'line 1\nline 2\nline 3', 1) + end) + end) + + describe('nvim_put', function() + it('validates args', function() + expect_err('Invalid lines %(expected array of strings%)', request, + 'nvim_put', {42}, 'l', false, false) + expect_err("Invalid type: 'x'", request, + 'nvim_put', {'foo'}, 'x', false, false) + end) + it("fails if 'nomodifiable'", function() + command('set nomodifiable') + expect_err([[Vim:E21: Cannot make changes, 'modifiable' is off]], request, + 'nvim_put', {'a','b'}, 'l', true, true) + end) + it('inserts text', function() + -- linewise + nvim('put', {'line 1','line 2','line 3'}, 'l', true, true) + expect([[ + + line 1 + line 2 + line 3]]) + eq({0,4,1,0}, funcs.getpos('.')) + command('%delete _') + -- charwise + nvim('put', {'line 1','line 2','line 3'}, 'c', true, false) + expect([[ + line 1 + line 2 + line 3]]) + eq({0,1,1,0}, funcs.getpos('.')) -- follow=false + -- blockwise + nvim('put', {'AA','BB'}, 'b', true, true) + expect([[ + lAAine 1 + lBBine 2 + line 3]]) + eq({0,2,4,0}, funcs.getpos('.')) + command('%delete _') + -- Empty lines list. + nvim('put', {}, 'c', true, true) + eq({0,1,1,0}, funcs.getpos('.')) + expect([[]]) + -- Single empty line. + nvim('put', {''}, 'c', true, true) + eq({0,1,1,0}, funcs.getpos('.')) + expect([[ + ]]) + nvim('put', {'AB'}, 'c', true, true) + -- after=false, follow=true + nvim('put', {'line 1','line 2'}, 'c', false, true) + expect([[ + Aline 1 + line 2B]]) + eq({0,2,7,0}, funcs.getpos('.')) + command('%delete _') + nvim('put', {'AB'}, 'c', true, true) + -- after=false, follow=false + nvim('put', {'line 1','line 2'}, 'c', false, false) + expect([[ + Aline 1 + line 2B]]) + eq({0,1,2,0}, funcs.getpos('.')) + eq('', nvim('eval', 'v:errmsg')) + end) + + it('detects charwise/linewise text (empty {type})', function() + -- linewise (final item is empty string) + nvim('put', {'line 1','line 2','line 3',''}, '', true, true) + expect([[ + + line 1 + line 2 + line 3]]) + eq({0,4,1,0}, funcs.getpos('.')) + command('%delete _') + -- charwise (final item is non-empty) + nvim('put', {'line 1','line 2','line 3'}, '', true, true) + expect([[ + line 1 + line 2 + line 3]]) + eq({0,3,6,0}, funcs.getpos('.')) + end) + end) + describe('nvim_strwidth', function() it('works', function() eq(3, nvim('strwidth', 'abc')) @@ -626,12 +747,12 @@ describe('API', function() -- Make any RPC request (can be non-async: op-pending does not block). nvim('get_current_buf') -- Buffer should not change. - helpers.expect([[ + expect([[ FIRST LINE SECOND LINE]]) -- Now send input to complete the operator. nvim('input', 'j') - helpers.expect([[ + expect([[ first line second line]]) end) @@ -664,7 +785,7 @@ describe('API', function() nvim('get_api_info') -- Send input to complete the mapping. nvim('input', 'd') - helpers.expect([[ + expect([[ FIRST LINE SECOND LINE]]) eq('it worked...', helpers.eval('g:foo')) @@ -680,7 +801,7 @@ describe('API', function() nvim('get_api_info') -- Send input to complete the mapping. nvim('input', 'x') - helpers.expect([[ + expect([[ FIRST LINE SECOND LINfooE]]) end) diff --git a/test/functional/terminal/helpers.lua b/test/functional/terminal/helpers.lua index 18f0b9e4c1..2d99a08614 100644 --- a/test/functional/terminal/helpers.lua +++ b/test/functional/terminal/helpers.lua @@ -1,3 +1,6 @@ +-- To test tui/input.c, this module spawns `nvim` inside :terminal and sends +-- bytes via jobsend(). Note: the functional/helpers.lua test-session methods +-- operate on the _host_ session, _not_ the child session. local helpers = require('test.functional.helpers')(nil) local Screen = require('test.functional.ui.screen') local nvim_dir = helpers.nvim_dir diff --git a/test/functional/terminal/tui_spec.lua b/test/functional/terminal/tui_spec.lua index af55ec1555..5445ff0127 100644 --- a/test/functional/terminal/tui_spec.lua +++ b/test/functional/terminal/tui_spec.lua @@ -1,5 +1,9 @@ -- TUI acceptance tests. -- Uses :terminal as a way to send keys and assert screen state. +-- +-- "bracketed paste" terminal feature: +-- http://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-Bracketed-Paste-Mode + local helpers = require('test.functional.helpers')(after_each) local uname = helpers.uname local thelpers = require('test.functional.terminal.helpers') @@ -21,11 +25,14 @@ if helpers.pending_win32(pending) then return end describe('TUI', function() local screen + local child_session before_each(function() clear() - screen = thelpers.screen_setup(0, '["'..nvim_prog - ..'", "-u", "NONE", "-i", "NONE", "--cmd", "set noswapfile noshowcmd noruler undodir=. directory=. viewdir=. backupdir=."]') + local child_server = helpers.new_pipename() + screen = thelpers.screen_setup(0, + string.format([=[["%s", "--listen", "%s", "-u", "NONE", "-i", "NONE", "--cmd", "%s laststatus=2 background=dark"]]=], + nvim_prog, child_server, nvim_set)) screen:expect([[ {1: } | {4:~ }| @@ -35,12 +42,31 @@ describe('TUI', function() | {3:-- TERMINAL --} | ]]) + child_session = helpers.connect(child_server) end) after_each(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:") @@ -128,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 @@ -146,49 +172,264 @@ describe('TUI', function() ]], attrs) end) - it('automatically sends <Paste> for bracketed paste sequences', function() - -- Pasting can be really slow in the TUI, specially in ASAN. - -- This will be fixed later but for now we require a high timeout. - screen.timeout = 60000 - feed_data('i\027[200~') + it('paste: Insert mode', function() + -- "bracketed paste" + feed_data('i""\027i\027[200~') screen:expect([[ - {1: } | + "{1:"} | {4:~ }| {4:~ }| {4:~ }| - {5:[No Name] }| - {3:-- INSERT (paste) --} | + {5:[No Name] [+] }| + {3:-- INSERT --} | {3:-- TERMINAL --} | ]]) feed_data('pasted from terminal') + expect_child_buf_lines({'"pasted from terminal"'}) screen:expect([[ - pasted from terminal{1: } | + "pasted from terminal{1:"} | {4:~ }| {4:~ }| {4:~ }| {5:[No Name] [+] }| - {3:-- INSERT (paste) --} | + {3:-- INSERT --} | {3:-- TERMINAL --} | ]]) - feed_data('\027[201~') + feed_data('\027[201~') -- End paste. + feed_data('\027\000') -- ESC: go to Normal mode. + wait_for_mode('n') screen:expect([[ - pasted from terminal{1: } | + "pasted from termina{1:l}" | {4:~ }| {4:~ }| {4:~ }| {5:[No Name] [+] }| - {3:-- INSERT --} | + | + {3:-- TERMINAL --} | + ]]) + -- Dot-repeat/redo. + feed_data('2.') + expect_child_buf_lines( + {'"pasted from terminapasted from terminalpasted from terminall"'}) + 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) + + it('paste: recovers from vim.paste() failure', function() + child_session:request('nvim_execute_lua', [[ + _G.save_paste_fn = vim.paste + vim.paste = function(lines, phase) error("fake fail") end + ]], {}) + -- Prepare something for dot-repeat/redo. + feed_data('ifoo\n\027\000') + wait_for_mode('n') + screen:expect{grid=[[ + foo | + {1: } | + {4:~ }| + {4:~ }| + {5:[No Name] [+] }| + | + {3:-- TERMINAL --} | + ]]} + -- Start pasting... + feed_data('\027[200~line 1\nline 2\n') + wait_for_mode('n') + screen:expect{any='paste: Error executing lua'} + -- Remaining chunks are discarded after vim.paste() failure. + feed_data('line 3\nline 4\n') + feed_data('line 5\nline 6\n') + feed_data('line 7\nline 8\n') + -- Stop paste. + feed_data('\027[201~') + feed_data('\n') -- <Enter> + --Dot-repeat/redo is not modified by failed paste. + feed_data('.') + screen:expect{grid=[[ + foo | + foo | + {1: } | + {4:~ }| + {5:[No Name] [+] }| + | + {3:-- TERMINAL --} | + ]]} + -- Editor should still work after failed/drained paste. + feed_data('ityped input...\027\000') + screen:expect{grid=[[ + foo | + foo | + typed input..{1:.} | + {4:~ }| + {5:[No Name] [+] }| + | + {3:-- TERMINAL --} | + ]]} + -- Paste works if vim.paste() succeeds. + child_session:request('nvim_execute_lua', [[ + vim.paste = _G.save_paste_fn + ]], {}) + feed_data('\027[200~line A\nline B\n\027[201~') + feed_data('\n') -- <Enter> + screen:expect{grid=[[ + foo | + typed input...line A | + line B | + {1: } | + {5:[No Name] [+] }| + | + {3:-- TERMINAL --} | + ]]} end) - it('handles pasting a specific amount of text', function() - -- Need extra time for this test, specially in ASAN. - screen.timeout = 60000 - feed_data('i\027[200~'..string.rep('z', 64)..'\027[201~') + it("paste: 'nomodifiable' buffer", function() + child_session:request('nvim_command', 'set nomodifiable') + feed_data('\027[200~fail 1\nfail 2\n\027[201~') + screen:expect{any='Vim:E21'} + feed_data('\n') -- <Enter> + child_session:request('nvim_command', 'set modifiable') + feed_data('\027[200~success 1\nsuccess 2\n\027[201~') + screen:expect{grid=[[ + success 1 | + success 2 | + {1: } | + {4:~ }| + {5:[No Name] [+] }| + | + {3:-- TERMINAL --} | + ]]} + end) + + -- TODO + it('paste: other modes', function() + -- Other modes act like CTRL-C + paste. + end) + + it('paste: exactly 64 bytes #10311', function() + local expected = string.rep('z', 64) + feed_data('i') + wait_for_mode('i') + -- "bracketed paste" + feed_data('\027[200~'..expected..'\027[201~') + feed_data(' end') + expected = expected..' end' + expect_child_buf_lines({expected}) screen:expect([[ zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz| - zzzzzzzzzzzzzz{1: } | + zzzzzzzzzzzzzz end{1: } | {4:~ }| {4:~ }| {5:[No Name] [+] }| @@ -197,26 +438,95 @@ describe('TUI', function() ]]) end) - it('can handle arbitrarily long bursts of input', function() - -- Need extra time for this test, specially in ASAN. - screen.timeout = 60000 - feed_command('set ruler') + it('paste: big burst of input', function() + feed_data(':set ruler\n') local t = {} for i = 1, 3000 do t[i] = 'item ' .. tostring(i) end - feed_data('i\027[200~'..table.concat(t, '\n')..'\027[201~') + feed_data('i') + wait_for_mode('i') + -- "bracketed paste" + feed_data('\027[200~'..table.concat(t, '\n')..'\027[201~') + expect_child_buf_lines(t) + feed_data(' end') + screen:expect([[ + item 2997 | + item 2998 | + item 2999 | + item 3000 end{1: } | + {5:[No Name] [+] 3000,14 Bot}| + {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{1: } | - {5:[No Name] [+] 3000,10 Bot}| + item 3000 en{1:d}d | + {5:[No Name] [+] 5999,13 Bot}| + | + {3:-- TERMINAL --} | + ]]) + end) + + 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. + + -- Send the "start paste" sequence. + feed_data('i\027[200~') + feed_data('\npasted from terminal (1)\n') + -- Send spurious "start paste" sequence. + feed_data('\027[200~') + feed_data('\n') + -- Send the "stop paste" sequence. + feed_data('\027[201~') + + screen:expect{grid=[[ + | + pasted from terminal (1) | + {6:^[}[200~ | + {1: } | + {5:[No Name] [+] }| + {3:-- INSERT --} | + {3:-- TERMINAL --} | + ]], + attr_ids={ + [1] = {reverse = true}, + [2] = {background = tonumber('0x00000b')}, + [3] = {bold = true}, + [4] = {foreground = tonumber('0x00000c')}, + [5] = {bold = true, reverse = true}, + [6] = {foreground = tonumber('0x000051')}, + }} + end) + + 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') + -- Send "stop paste" sequence. + feed_data('\027[201~') + screen:expect([[ + {1: } | + {4:~ }| + {4:~ }| + {4:~ }| + {5:[No Name] }| {3:-- INSERT --} | {3:-- TERMINAL --} | ]]) end) + -- TODO + it('paste: handles missing "stop paste" code', function() + end) + it('allows termguicolors to be set at runtime', function() screen:set_option('rgb', true) screen:set_default_attr_ids({ diff --git a/test/functional/ui/input_spec.lua b/test/functional/ui/input_spec.lua index 0009f2c31b..12d0e4f40b 100644 --- a/test/functional/ui/input_spec.lua +++ b/test/functional/ui/input_spec.lua @@ -110,77 +110,6 @@ 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() - feed_command('set ruler') - end) - - it('ok', function() - if helpers.skip_fragile(pending) then - return - end - 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() |