diff options
Diffstat (limited to 'test/functional/terminal/tui_spec.lua')
-rw-r--r-- | test/functional/terminal/tui_spec.lua | 366 |
1 files changed, 338 insertions, 28 deletions
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({ |