diff options
Diffstat (limited to 'test/functional/terminal/tui_spec.lua')
-rw-r--r-- | test/functional/terminal/tui_spec.lua | 781 |
1 files changed, 675 insertions, 106 deletions
diff --git a/test/functional/terminal/tui_spec.lua b/test/functional/terminal/tui_spec.lua index 99f69ef556..b28728057f 100644 --- a/test/functional/terminal/tui_spec.lua +++ b/test/functional/terminal/tui_spec.lua @@ -5,15 +5,15 @@ -- 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') local Screen = require('test.functional.ui.screen') -local assert_alive = helpers.assert_alive local eq = helpers.eq local feed_command = helpers.feed_command local feed_data = thelpers.feed_data local clear = helpers.clear local command = helpers.command +local dedent = helpers.dedent +local exec = helpers.exec local testprg = helpers.testprg local retry = helpers.retry local nvim_prog = helpers.nvim_prog @@ -21,16 +21,25 @@ local nvim_set = helpers.nvim_set local ok = helpers.ok local read_file = helpers.read_file local funcs = helpers.funcs +local meths = helpers.meths +local is_ci = helpers.is_ci +local is_os = helpers.is_os +local new_pipename = helpers.new_pipename +local spawn_argv = helpers.spawn_argv +local set_session = helpers.set_session +local feed = helpers.feed +local eval = helpers.eval -if helpers.pending_win32(pending) then return end +if helpers.skip(helpers.is_os('win')) then return end describe('TUI', function() local screen local child_session + local child_exec_lua before_each(function() clear() - local child_server = helpers.new_pipename() + local child_server = 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)) @@ -44,6 +53,7 @@ describe('TUI', function() {3:-- TERMINAL --} | ]]) child_session = helpers.connect(child_server) + child_exec_lua = thelpers.make_lua_executor(child_session) end) -- Wait for mode in the child Nvim (avoid "typeahead race" #10826). @@ -66,7 +76,16 @@ describe('TUI', function() it('rapid resize #7572 #7628', function() -- Need buffer rows to provoke the behavior. - feed_data(":edit test/functional/fixtures/bigfile.txt:") + feed_data(":edit test/functional/fixtures/bigfile.txt\n") + screen:expect([[ + {1:0}000;<control>;Cc;0;BN;;;;;N;NULL;;;; | + 0001;<control>;Cc;0;BN;;;;;N;START OF HEADING;;;; | + 0002;<control>;Cc;0;BN;;;;;N;START OF TEXT;;;; | + 0003;<control>;Cc;0;BN;;;;;N;END OF TEXT;;;; | + {5:test/functional/fixtures/bigfile.txt }| + :edit test/functional/fixtures/bigfile.txt | + {3:-- TERMINAL --} | + ]]) command('call jobresize(b:terminal_job_id, 58, 9)') command('call jobresize(b:terminal_job_id, 62, 13)') command('call jobresize(b:terminal_job_id, 100, 42)') @@ -83,25 +102,9 @@ describe('TUI', function() command('call jobresize(b:terminal_job_id, 1, 4)') screen:try_resize(57, 17) command('call jobresize(b:terminal_job_id, 57, 17)') - assert_alive() - end) - - it('resize at startup', function() - -- Issues: #17285 #15044 #11330 - screen:try_resize(50, 10) - feed_command([[call termopen([v:progpath, '--clean', '--cmd', 'let start = reltime() | while v:true | if reltimefloat(reltime(start)) > 2 | break | endif | endwhile']) | sleep 500m | vs new]]) - screen:expect([[ - {1: } │ | - {4:~ }│{4:~ }| - {4:~ }│{4:~ }| - {4:~ }│{4:~ }| - {4:~ }│{4:~ }| - {4:~ }│{5:[No Name] 0,0-1 All}| - {4:~ }│ | - {5:new }{MATCH:<.*[/\]nvim }| - | - {3:-- TERMINAL --} | - ]]) + retry(nil, nil, function() + eq({true, 57}, {child_session:request('nvim_win_get_width', 0)}) + end) end) it('accepts resize while pager is active', function() @@ -297,6 +300,199 @@ describe('TUI', function() ]], attrs) end) + it('accepts mouse wheel events #19992', function() + child_session:request('nvim_command', [[ + set number nostartofline nowrap mousescroll=hor:1,ver:1 + call setline(1, repeat([join(range(10), '----')], 10)) + vsplit + ]]) + screen:expect([[ + {11: 1 }{1:0}----1----2----3----4│{11: 1 }0----1----2----3----| + {11: 2 }0----1----2----3----4│{11: 2 }0----1----2----3----| + {11: 3 }0----1----2----3----4│{11: 3 }0----1----2----3----| + {11: 4 }0----1----2----3----4│{11: 4 }0----1----2----3----| + {5:[No Name] [+] }{1:[No Name] [+] }| + | + {3:-- TERMINAL --} | + ]]) + -- <ScrollWheelDown> in active window + feed_data('\027[<65;8;1M') + screen:expect([[ + {11: 2 }{1:0}----1----2----3----4│{11: 1 }0----1----2----3----| + {11: 3 }0----1----2----3----4│{11: 2 }0----1----2----3----| + {11: 4 }0----1----2----3----4│{11: 3 }0----1----2----3----| + {11: 5 }0----1----2----3----4│{11: 4 }0----1----2----3----| + {5:[No Name] [+] }{1:[No Name] [+] }| + | + {3:-- TERMINAL --} | + ]]) + -- <ScrollWheelDown> in inactive window + feed_data('\027[<65;48;1M') + screen:expect([[ + {11: 2 }{1:0}----1----2----3----4│{11: 2 }0----1----2----3----| + {11: 3 }0----1----2----3----4│{11: 3 }0----1----2----3----| + {11: 4 }0----1----2----3----4│{11: 4 }0----1----2----3----| + {11: 5 }0----1----2----3----4│{11: 5 }0----1----2----3----| + {5:[No Name] [+] }{1:[No Name] [+] }| + | + {3:-- TERMINAL --} | + ]]) + -- <ScrollWheelRight> in active window + feed_data('\027[<67;8;1M') + screen:expect([[ + {11: 2 }{1:-}---1----2----3----4-│{11: 2 }0----1----2----3----| + {11: 3 }----1----2----3----4-│{11: 3 }0----1----2----3----| + {11: 4 }----1----2----3----4-│{11: 4 }0----1----2----3----| + {11: 5 }----1----2----3----4-│{11: 5 }0----1----2----3----| + {5:[No Name] [+] }{1:[No Name] [+] }| + | + {3:-- TERMINAL --} | + ]]) + -- <ScrollWheelRight> in inactive window + feed_data('\027[<67;48;1M') + screen:expect([[ + {11: 2 }{1:-}---1----2----3----4-│{11: 2 }----1----2----3----4| + {11: 3 }----1----2----3----4-│{11: 3 }----1----2----3----4| + {11: 4 }----1----2----3----4-│{11: 4 }----1----2----3----4| + {11: 5 }----1----2----3----4-│{11: 5 }----1----2----3----4| + {5:[No Name] [+] }{1:[No Name] [+] }| + | + {3:-- TERMINAL --} | + ]]) + -- <S-ScrollWheelDown> in active window + feed_data('\027[<69;8;1M') + screen:expect([[ + {11: 5 }{1:-}---1----2----3----4-│{11: 2 }----1----2----3----4| + {11: 6 }----1----2----3----4-│{11: 3 }----1----2----3----4| + {11: 7 }----1----2----3----4-│{11: 4 }----1----2----3----4| + {11: 8 }----1----2----3----4-│{11: 5 }----1----2----3----4| + {5:[No Name] [+] }{1:[No Name] [+] }| + | + {3:-- TERMINAL --} | + ]]) + -- <S-ScrollWheelDown> in inactive window + feed_data('\027[<69;48;1M') + screen:expect([[ + {11: 5 }{1:-}---1----2----3----4-│{11: 5 }----1----2----3----4| + {11: 6 }----1----2----3----4-│{11: 6 }----1----2----3----4| + {11: 7 }----1----2----3----4-│{11: 7 }----1----2----3----4| + {11: 8 }----1----2----3----4-│{11: 8 }----1----2----3----4| + {5:[No Name] [+] }{1:[No Name] [+] }| + | + {3:-- TERMINAL --} | + ]]) + -- <S-ScrollWheelRight> in active window + feed_data('\027[<71;8;1M') + screen:expect([[ + {11: 5 }{1:-}---6----7----8----9 │{11: 5 }----1----2----3----4| + {11: 6 }----6----7----8----9 │{11: 6 }----1----2----3----4| + {11: 7 }----6----7----8----9 │{11: 7 }----1----2----3----4| + {11: 8 }----6----7----8----9 │{11: 8 }----1----2----3----4| + {5:[No Name] [+] }{1:[No Name] [+] }| + | + {3:-- TERMINAL --} | + ]]) + -- <S-ScrollWheelRight> in inactive window + feed_data('\027[<71;48;1M') + screen:expect([[ + {11: 5 }{1:-}---6----7----8----9 │{11: 5 }5----6----7----8----| + {11: 6 }----6----7----8----9 │{11: 6 }5----6----7----8----| + {11: 7 }----6----7----8----9 │{11: 7 }5----6----7----8----| + {11: 8 }----6----7----8----9 │{11: 8 }5----6----7----8----| + {5:[No Name] [+] }{1:[No Name] [+] }| + | + {3:-- TERMINAL --} | + ]]) + -- <ScrollWheelUp> in active window + feed_data('\027[<64;8;1M') + screen:expect([[ + {11: 4 }----6----7----8----9 │{11: 5 }5----6----7----8----| + {11: 5 }{1:-}---6----7----8----9 │{11: 6 }5----6----7----8----| + {11: 6 }----6----7----8----9 │{11: 7 }5----6----7----8----| + {11: 7 }----6----7----8----9 │{11: 8 }5----6----7----8----| + {5:[No Name] [+] }{1:[No Name] [+] }| + | + {3:-- TERMINAL --} | + ]]) + -- <ScrollWheelUp> in inactive window + feed_data('\027[<64;48;1M') + screen:expect([[ + {11: 4 }----6----7----8----9 │{11: 4 }5----6----7----8----| + {11: 5 }{1:-}---6----7----8----9 │{11: 5 }5----6----7----8----| + {11: 6 }----6----7----8----9 │{11: 6 }5----6----7----8----| + {11: 7 }----6----7----8----9 │{11: 7 }5----6----7----8----| + {5:[No Name] [+] }{1:[No Name] [+] }| + | + {3:-- TERMINAL --} | + ]]) + -- <ScrollWheelLeft> in active window + feed_data('\027[<66;8;1M') + screen:expect([[ + {11: 4 }5----6----7----8----9│{11: 4 }5----6----7----8----| + {11: 5 }5{1:-}---6----7----8----9│{11: 5 }5----6----7----8----| + {11: 6 }5----6----7----8----9│{11: 6 }5----6----7----8----| + {11: 7 }5----6----7----8----9│{11: 7 }5----6----7----8----| + {5:[No Name] [+] }{1:[No Name] [+] }| + | + {3:-- TERMINAL --} | + ]]) + -- <ScrollWheelLeft> in inactive window + feed_data('\027[<66;48;1M') + screen:expect([[ + {11: 4 }5----6----7----8----9│{11: 4 }-5----6----7----8---| + {11: 5 }5{1:-}---6----7----8----9│{11: 5 }-5----6----7----8---| + {11: 6 }5----6----7----8----9│{11: 6 }-5----6----7----8---| + {11: 7 }5----6----7----8----9│{11: 7 }-5----6----7----8---| + {5:[No Name] [+] }{1:[No Name] [+] }| + | + {3:-- TERMINAL --} | + ]]) + -- <S-ScrollWheelUp> in active window + feed_data('\027[<68;8;1M') + screen:expect([[ + {11: 1 }5----6----7----8----9│{11: 4 }-5----6----7----8---| + {11: 2 }5----6----7----8----9│{11: 5 }-5----6----7----8---| + {11: 3 }5----6----7----8----9│{11: 6 }-5----6----7----8---| + {11: 4 }5{1:-}---6----7----8----9│{11: 7 }-5----6----7----8---| + {5:[No Name] [+] }{1:[No Name] [+] }| + | + {3:-- TERMINAL --} | + ]]) + -- <S-ScrollWheelUp> in inactive window + feed_data('\027[<68;48;1M') + screen:expect([[ + {11: 1 }5----6----7----8----9│{11: 1 }-5----6----7----8---| + {11: 2 }5----6----7----8----9│{11: 2 }-5----6----7----8---| + {11: 3 }5----6----7----8----9│{11: 3 }-5----6----7----8---| + {11: 4 }5{1:-}---6----7----8----9│{11: 4 }-5----6----7----8---| + {5:[No Name] [+] }{1:[No Name] [+] }| + | + {3:-- TERMINAL --} | + ]]) + -- <S-ScrollWheelLeft> in active window + feed_data('\027[<70;8;1M') + screen:expect([[ + {11: 1 }0----1----2----3----4│{11: 1 }-5----6----7----8---| + {11: 2 }0----1----2----3----4│{11: 2 }-5----6----7----8---| + {11: 3 }0----1----2----3----4│{11: 3 }-5----6----7----8---| + {11: 4 }0----1----2----3----{1:4}│{11: 4 }-5----6----7----8---| + {5:[No Name] [+] }{1:[No Name] [+] }| + | + {3:-- TERMINAL --} | + ]]) + -- <S-ScrollWheelLeft> in inactive window + feed_data('\027[<70;48;1M') + screen:expect([[ + {11: 1 }0----1----2----3----4│{11: 1 }0----1----2----3----| + {11: 2 }0----1----2----3----4│{11: 2 }0----1----2----3----| + {11: 3 }0----1----2----3----4│{11: 3 }0----1----2----3----| + {11: 4 }0----1----2----3----{1:4}│{11: 4 }0----1----2----3----| + {5:[No Name] [+] }{1:[No Name] [+] }| + | + {3:-- TERMINAL --} | + ]]) + end) + it('accepts keypad keys from kitty keyboard protocol #19180', function() feed_data('i') feed_data(funcs.nr2char(57399)) -- KP_0 @@ -440,37 +636,83 @@ describe('TUI', function() tabnew highlight Tabline ctermbg=NONE ctermfg=NONE cterm=underline ]]) - local attrs = screen:get_default_attr_ids() - attrs[11] = {underline = true} screen:expect([[ - {11: + [No Name] + [No Name] }{3: [No Name] }{1: }{11:X}| + {12: + [No Name] + [No Name] }{3: [No Name] }{1: }{12:X}| {1: } | {4:~ }| {4:~ }| {5:[No Name] }| | {3:-- TERMINAL --} | - ]], attrs) + ]]) feed_data('\027[57421;5u') -- CTRL + KP_PAGE_UP screen:expect([[ - {11: + [No Name] }{3: + [No Name] }{11: [No Name] }{1: }{11:X}| + {12: + [No Name] }{3: + [No Name] }{12: [No Name] }{1: }{12:X}| 0123456789/*-{1:+} | = | {4:~ }| {5:[No Name] [+] }| | {3:-- TERMINAL --} | - ]], attrs) + ]]) feed_data('\027[57422;5u') -- CTRL + KP_PAGE_DOWN screen:expect([[ - {11: + [No Name] + [No Name] }{3: [No Name] }{1: }{11:X}| + {12: + [No Name] + [No Name] }{3: [No Name] }{1: }{12:X}| {1: } | {4:~ }| {4:~ }| {5:[No Name] }| | {3:-- TERMINAL --} | - ]], attrs) + ]]) + end) + + it('mouse events work with right-click menu', function() + child_session:request('nvim_command', [[ + call setline(1, 'popup menu test') + set mouse=a mousemodel=popup + + aunmenu PopUp + menu PopUp.foo :let g:menustr = 'foo'<CR> + menu PopUp.bar :let g:menustr = 'bar'<CR> + menu PopUp.baz :let g:menustr = 'baz'<CR> + highlight Pmenu ctermbg=NONE ctermfg=NONE cterm=underline,reverse + highlight PmenuSel ctermbg=NONE ctermfg=NONE cterm=underline,reverse,bold + ]]) + meths.input_mouse('right', 'press', '', 0, 0, 4) + screen:expect([[ + {1:p}opup menu test | + {4:~ }{13: foo }{4: }| + {4:~ }{13: bar }{4: }| + {4:~ }{13: baz }{4: }| + {5:[No Name] [+] }| + | + {3:-- TERMINAL --} | + ]]) + meths.input_mouse('right', 'release', '', 0, 0, 4) + screen:expect_unchanged() + meths.input_mouse('move', '', '', 0, 3, 6) + screen:expect([[ + {1:p}opup menu test | + {4:~ }{13: foo }{4: }| + {4:~ }{13: bar }{4: }| + {4:~ }{14: baz }{4: }| + {5:[No Name] [+] }| + | + {3:-- TERMINAL --} | + ]]) + meths.input_mouse('left', 'press', '', 0, 2, 6) + screen:expect([[ + {1:p}opup menu test | + {4:~ }| + {4:~ }| + {4:~ }| + {5:[No Name] [+] }| + :let g:menustr = 'bar' | + {3:-- TERMINAL --} | + ]]) + meths.input_mouse('left', 'release', '', 0, 2, 6) + screen:expect_unchanged() end) it('paste: Insert mode', function() @@ -574,12 +816,11 @@ describe('TUI', function() end) it('paste: terminal mode', function() - if os.getenv('GITHUB_ACTIONS') ~= nil then + if is_ci('github') then pending("tty-test complains about not owning the terminal -- actions/runner#241") - return end - feed_data(':set statusline=^^^^^^^\n') - feed_data(':terminal '..testprg('tty-test')..'\n') + child_exec_lua('vim.o.statusline="^^^^^^^"') + child_exec_lua('vim.cmd.terminal(...)', testprg('tty-test')) feed_data('i') screen:expect{grid=[[ tty ready | @@ -841,8 +1082,7 @@ describe('TUI', function() wait_for_mode('i') -- "bracketed paste" feed_data('\027[200~'..expected..'\027[201~') - -- FIXME: Data race between the two feeds - if uname() == 'freebsd' then screen:sleep(1) end + expect_child_buf_lines({expected}) feed_data(' end') expected = expected..' end' screen:expect([[ @@ -961,6 +1201,15 @@ describe('TUI', function() it('paste: split "start paste" code', function() feed_data('i') + screen:expect{grid=[[ + {1: } | + {4:~ }| + {4:~ }| + {4:~ }| + {5:[No Name] }| + {3:-- INSERT --} | + {3:-- TERMINAL --} | + ]]} -- Send split "start paste" sequence. feed_data('\027[2') feed_data('00~pasted from terminal\027[201~') @@ -977,6 +1226,15 @@ describe('TUI', function() it('paste: split "stop paste" code', function() feed_data('i') + screen:expect{grid=[[ + {1: } | + {4:~ }| + {4:~ }| + {4:~ }| + {5:[No Name] }| + {3:-- INSERT --} | + {3:-- TERMINAL --} | + ]]} -- Send split "stop paste" sequence. feed_data('\027[200~pasted from terminal\027[20') feed_data('1~') @@ -1002,6 +1260,15 @@ describe('TUI', function() end)(vim.paste) ]], {}) feed_data('i') + screen:expect{grid=[[ + {1: } | + {4:~ }| + {4:~ }| + {4:~ }| + {5:[No Name] }| + {3:-- INSERT --} | + {3:-- TERMINAL --} | + ]]} feed_data('\027[200~pasted') -- phase 1 screen:expect([[ pasted{1: } | @@ -1041,6 +1308,7 @@ describe('TUI', function() [7] = {reverse = true, foreground = Screen.colors.SeaGreen4}, [8] = {foreground = Screen.colors.SeaGreen4}, [9] = {bold = true, foreground = Screen.colors.Blue1}, + [10] = {foreground = Screen.colors.Blue}, }) feed_data(':hi SpecialKey ctermfg=3 guifg=SeaGreen\n') @@ -1061,9 +1329,9 @@ describe('TUI', function() feed_data(':set termguicolors\n') screen:expect([[ {7:^}{8:G} | - {9:~ }| - {9:~ }| - {9:~ }| + {9:~}{10: }| + {9:~}{10: }| + {9:~}{10: }| {3:[No Name] [+] }| :set termguicolors | {4:-- TERMINAL --} | @@ -1082,9 +1350,8 @@ describe('TUI', function() end) it('forwards :term palette colors with termguicolors', function() - if os.getenv('GITHUB_ACTIONS') ~= nil then + if is_ci('github') then pending("tty-test complains about not owning the terminal -- actions/runner#241") - return end screen:set_rgb_cterm(true) screen:set_default_attr_ids({ @@ -1095,12 +1362,9 @@ describe('TUI', function() [5] = {{foreground = tonumber('0xff8000')}, {}}, }) - feed_data(':set statusline=^^^^^^^\n') - feed_data(':set termguicolors\n') - feed_data(':terminal '..testprg('tty-test')..'\n') - -- Depending on platform the above might or might not fit in the cmdline - -- so clear it for consistent behavior. - feed_data(':\027') + child_exec_lua('vim.o.statusline="^^^^^^^"') + child_exec_lua('vim.o.termguicolors=true') + child_exec_lua('vim.cmd.terminal(...)', testprg('tty-test')) screen:expect{grid=[[ {1:t}ty ready | | @@ -1139,7 +1403,7 @@ describe('TUI', function() | {4:~ }| {5: }| - [[['chan', 0], ['height', 6], ['override', v:false| + [[['chan', 1], ['height', 6], ['override', v:false| ], ['rgb', v:false], ['width', 50]]] | {10:Press ENTER or type command to continue}{1: } | {3:-- TERMINAL --} | @@ -1147,18 +1411,14 @@ describe('TUI', function() end) it('allows grid to assume wider ambiguous-width characters than host terminal #19686', function() - child_session:request('nvim_buf_set_lines', 0, 0, 0, true, { ('℃'):rep(60), ('℃'):rep(60) }) + child_session:request('nvim_buf_set_lines', 0, 0, -1, true, { ('℃'):rep(60), ('℃'):rep(60) }) child_session:request('nvim_win_set_option', 0, 'cursorline', true) child_session:request('nvim_win_set_option', 0, 'list', true) child_session:request('nvim_win_set_option', 0, 'listchars', 'eol:$') - local attrs = screen:get_default_attr_ids() - attrs[11] = {underline = true} -- CursorLine - attrs[12] = {underline = true, reverse = true} -- CursorLine and TermCursor - attrs[13] = {underline = true, foreground = 12} -- CursorLine and NonText feed_data('gg') local singlewidth_screen = [[ - {12:℃}{11:℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃}| - {11:℃℃℃℃℃℃℃℃℃℃}{13:$}{11: }| + {13:℃}{12:℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃}| + {12:℃℃℃℃℃℃℃℃℃℃}{15:$}{12: }| ℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃| ℃℃℃℃℃℃℃℃℃℃{4:$} | {5:[No Name] [+] }| @@ -1168,23 +1428,83 @@ describe('TUI', function() -- When grid assumes "℃" to be double-width but host terminal assumes it to be single-width, the -- second cell of "℃" is a space and the attributes of the "℃" are applied to it. local doublewidth_screen = [[ - {12:℃}{11: ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ }| - {11:℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ }| - {11:℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ }{13:$}{11: }| + {13:℃}{12: ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ }| + {12:℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ }| + {12:℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ }{15:$}{12: }| ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ >{4:@@@}| {5:[No Name] [+] }| | {3:-- TERMINAL --} | ]] - screen:expect(singlewidth_screen, attrs) + screen:expect(singlewidth_screen) child_session:request('nvim_set_option', 'ambiwidth', 'double') - screen:expect(doublewidth_screen, attrs) + screen:expect(doublewidth_screen) child_session:request('nvim_set_option', 'ambiwidth', 'single') - screen:expect(singlewidth_screen, attrs) + screen:expect(singlewidth_screen) child_session:request('nvim_call_function', 'setcellwidths', {{{0x2103, 0x2103, 2}}}) - screen:expect(doublewidth_screen, attrs) + screen:expect(doublewidth_screen) child_session:request('nvim_call_function', 'setcellwidths', {{{0x2103, 0x2103, 1}}}) - screen:expect(singlewidth_screen, attrs) + screen:expect(singlewidth_screen) + end) + + it('draws correctly when cursor_address overflows #21643', function() + helpers.skip(helpers.is_os('mac'), 'FIXME: crashes/errors on macOS') + screen:try_resize(77, 834) + retry(nil, nil, function() + eq({true, 831}, {child_session:request('nvim_win_get_height', 0)}) + end) + -- Use full screen message so that redrawing afterwards is more deterministic. + child_session:notify('nvim_command', 'intro') + screen:expect({any = 'Nvim'}) + -- Going to top-left corner needs 3 bytes. + -- Setting underline attribute needs 9 bytes. + -- With screen width 77, 63857 characters need 829 full screen lines. + -- Drawing each full screen line needs 77 + 2 = 79 bytes (2 bytes for CR LF). + -- The incomplete screen line needs 24 + 3 = 27 bytes. + -- The whole line needs 3 + 9 + 79 * 829 + 27 = 65530 bytes. + -- The cursor_address that comes after will overflow the 65535-byte buffer. + local line = ('a'):rep(63857) .. '℃' + child_session:notify('nvim_exec_lua', [[ + vim.api.nvim_buf_set_lines(0, 0, -1, true, {...}) + vim.o.cursorline = true + ]], {line, 'b'}) + -- Close the :intro message and redraw the lines. + feed_data('\n') + screen:expect( + '{13:a}{12:' .. ('a'):rep(76) .. '}|\n' + .. ('{12:' .. ('a'):rep(77) .. '}|\n'):rep(828) + .. '{12:' .. ('a'):rep(24) .. '℃' .. (' '):rep(52) .. '}|\n' .. dedent([[ + b | + {5:[No Name] [+] }| + | + {3:-- TERMINAL --} |]])) + end) + + it('visual bell (padding) does not crash #21610', function() + feed_data ':set visualbell\n' + screen:expect{grid=[[ + {1: } | + {4:~ }| + {4:~ }| + {4:~ }| + {5:[No Name] }| + :set visualbell | + {3:-- TERMINAL --} | + ]]} + + -- move left is enough to invoke the bell + feed_data 'h' + -- visual change to show we process events after this + feed_data 'i' + screen:expect{grid=[[ + {1: } | + {4:~ }| + {4:~ }| + {4:~ }| + {5:[No Name] }| + {3:-- INSERT --} | + {3:-- TERMINAL --} | + ]]} end) end) @@ -1194,6 +1514,35 @@ describe('TUI', function() os.remove('testF') end) + it('resize at startup #17285 #15044 #11330', function() + local screen = Screen.new(50, 10) + screen:set_default_attr_ids({ + [1] = {reverse = true}, + [2] = {bold = true, foreground = Screen.colors.Blue}, + [3] = {bold = true}, + [4] = {foreground = tonumber('0x4040ff'), fg_indexed = true}, + [5] = {bold = true, reverse = true}, + }) + screen:attach() + exec([[ + call termopen([v:progpath, '--clean', '--cmd', 'let start = reltime() | while v:true | if reltimefloat(reltime(start)) > 2 | break | endif | endwhile']) + sleep 500m + vs new + ]]) + screen:expect([[ + ^ │ | + {2:~ }│{4:~ }| + {2:~ }│{4:~ }| + {2:~ }│{4:~ }| + {2:~ }│{4:~ }| + {2:~ }│{4:~ }| + {2:~ }│{5:[No Name] 0,0-1 All}| + {2:~ }│ | + {5:new }{MATCH:<.*[/\]nvim }| + | + ]]) + end) + it('with non-tty (pipe) stdout/stderr', function() local screen = thelpers.screen_setup(0, '"'..nvim_prog ..' -u NONE -i NONE --cmd \'set noswapfile noshowcmd noruler\' --cmd \'normal iabc\' > /dev/null 2>&1 && cat testF && rm testF"') @@ -1212,6 +1561,15 @@ describe('TUI', function() it('<C-h> #10134', function() local screen = thelpers.screen_setup(0, '["'..nvim_prog ..[[", "-u", "NONE", "-i", "NONE", "--cmd", "set noruler", "--cmd", ':nnoremap <C-h> :echomsg "\<C-h\>"<CR>']]..']') + screen:expect{grid=[[ + {1: } | + {4:~ }| + {4:~ }| + {4:~ }| + {5:[No Name] }| + | + {3:-- TERMINAL --} | + ]]} command([[call chansend(b:terminal_job_id, "\<C-h>")]]) screen:expect([[ @@ -1238,6 +1596,15 @@ describe('TUI UIEnter/UILeave', function() ..[[, "--cmd", "autocmd VimEnter * :call add(g:evs, 'VimEnter')"]] ..']' ) + screen:expect{grid=[[ + {1: } | + {4:~ }| + {4:~ }| + {4:~ }| + {5:[No Name] }| + | + {3:-- TERMINAL --} | + ]]} feed_data(":echo g:evs\n") screen:expect{grid=[[ {1: } | @@ -1258,61 +1625,88 @@ describe('TUI FocusGained/FocusLost', function() clear() screen = thelpers.screen_setup(0, '["'..nvim_prog ..'", "-u", "NONE", "-i", "NONE", "--cmd", "set noswapfile noshowcmd noruler"]') - feed_data(":autocmd FocusGained * echo 'gained'\n") - feed_data(":autocmd FocusLost * echo 'lost'\n") - feed_data("\034\016") -- CTRL-\ CTRL-N - end) - - it('in normal-mode', function() - retry(2, 3 * screen.timeout, function() - feed_data('\027[I') - screen:expect([[ + screen:expect{grid=[[ {1: } | {4:~ }| {4:~ }| {4:~ }| {5:[No Name] }| - gained | + | {3:-- TERMINAL --} | - ]]) + ]]} + feed_data(":autocmd FocusGained * echo 'gained'\n") + feed_data(":autocmd FocusLost * echo 'lost'\n") + feed_data("\034\016") -- CTRL-\ CTRL-N + end) - feed_data('\027[O') - screen:expect([[ + it('in normal-mode', function() + screen:expect{grid=[[ {1: } | {4:~ }| {4:~ }| {4:~ }| {5:[No Name] }| - lost | + :autocmd FocusLost * echo 'lost' | {3:-- TERMINAL --} | - ]]) + ]]} + retry(2, 3 * screen.timeout, function() + feed_data('\027[I') + screen:expect([[ + {1: } | + {4:~ }| + {4:~ }| + {4:~ }| + {5:[No Name] }| + gained | + {3:-- TERMINAL --} | + ]]) + + feed_data('\027[O') + screen:expect([[ + {1: } | + {4:~ }| + {4:~ }| + {4:~ }| + {5:[No Name] }| + lost | + {3:-- TERMINAL --} | + ]]) end) end) it('in insert-mode', function() feed_command('set noshowmode') feed_data('i') - retry(2, 3 * screen.timeout, function() - feed_data('\027[I') - screen:expect([[ - {1: } | - {4:~ }| - {4:~ }| - {4:~ }| - {5:[No Name] }| - gained | - {3:-- TERMINAL --} | - ]]) - feed_data('\027[O') - screen:expect([[ + screen:expect{grid=[[ {1: } | {4:~ }| {4:~ }| {4:~ }| {5:[No Name] }| - lost | + :set noshowmode | {3:-- TERMINAL --} | - ]]) + ]]} + retry(2, 3 * screen.timeout, function() + feed_data('\027[I') + screen:expect([[ + {1: } | + {4:~ }| + {4:~ }| + {4:~ }| + {5:[No Name] }| + gained | + {3:-- TERMINAL --} | + ]]) + feed_data('\027[O') + screen:expect([[ + {1: } | + {4:~ }| + {4:~ }| + {4:~ }| + {5:[No Name] }| + lost | + {3:-- TERMINAL --} | + ]]) end) end) @@ -1349,6 +1743,15 @@ describe('TUI FocusGained/FocusLost', function() feed_data(":autocmd!\n") feed_data(":autocmd FocusLost * call append(line('$'), 'lost')\n") feed_data(":autocmd FocusGained * call append(line('$'), 'gained')\n") + screen:expect{grid=[[ + {1: } | + {4:~ }| + {4:~ }| + {4:~ }| + {5:[No Name] }| + | + {3:-- TERMINAL --} | + ]]} retry(2, 3 * screen.timeout, function() -- Enter cmdline-mode. feed_data(':') @@ -1407,9 +1810,18 @@ describe('TUI FocusGained/FocusLost', function() feed_data(":echom 'msg1'|echom 'msg2'|echom 'msg3'|echom 'msg4'|echom 'msg5'\n") -- Execute :messages to provoke the press-enter prompt. feed_data(":messages\n") + screen:expect{grid=[[ + msg1 | + msg2 | + msg3 | + msg4 | + msg5 | + {10:Press ENTER or type command to continue}{1: } | + {3:-- TERMINAL --} | + ]]} feed_data('\027[I') feed_data('\027[I') - screen:expect([[ + screen:expect{grid=[[ msg1 | msg2 | msg3 | @@ -1417,7 +1829,7 @@ describe('TUI FocusGained/FocusLost', function() msg5 | {10:Press ENTER or type command to continue}{1: } | {3:-- TERMINAL --} | - ]]) + ]], unchanged=true} end) end) @@ -1425,7 +1837,6 @@ end) -- does not initialize the TUI. describe("TUI 't_Co' (terminal colors)", function() local screen - local is_freebsd = (uname() == 'freebsd') local function assert_term_colors(term, colorterm, maxcolors) helpers.clear({env={TERM=term}, args={}}) @@ -1528,7 +1939,7 @@ describe("TUI 't_Co' (terminal colors)", function() -- which is raised to 16 by COLORTERM. it("TERM=screen no COLORTERM uses 8/256 colors", function() - if is_freebsd then + if is_os('freebsd') then assert_term_colors("screen", nil, 256) else assert_term_colors("screen", nil, 8) @@ -1536,7 +1947,7 @@ describe("TUI 't_Co' (terminal colors)", function() end) it("TERM=screen COLORTERM=screen uses 16/256 colors", function() - if is_freebsd then + if is_os('freebsd') then assert_term_colors("screen", "screen", 256) else assert_term_colors("screen", "screen", 16) @@ -1699,8 +2110,6 @@ end) -- does not initialize the TUI. describe("TUI 'term' option", function() local screen - local is_bsd = not not string.find(uname(), 'bsd') - local is_macos = not not string.find(uname(), 'darwin') local function assert_term(term_envvar, term_expected) clear() @@ -1726,11 +2135,11 @@ describe("TUI 'term' option", function() end) it('gets system-provided term if $TERM is valid', function() - if uname() == "openbsd" then + if is_os('openbsd') then assert_term("xterm", "xterm") - elseif is_bsd then -- BSD lacks terminfo, builtin is always used. + elseif is_os('bsd') then -- BSD lacks terminfo, builtin is always used. assert_term("xterm", "builtin_xterm") - elseif is_macos then + elseif is_os('mac') then local status, _ = pcall(assert_term, "xterm", "xterm") if not status then pending("macOS: unibilium could not find terminfo") @@ -1788,7 +2197,7 @@ describe("TUI", function() retry(nil, 3000, function() -- Wait for log file to be flushed. local log = read_file('Xtest_tui_verbose_log') or '' - eq('--- Terminal info --- {{{\n', string.match(log, '%-%-%- Terminal.-\n')) + eq('--- Terminal info --- {{{\n', string.match(log, '%-%-%- Terminal.-\n')) -- }}} ok(#log > 50) end) end) @@ -1912,3 +2321,163 @@ describe('TUI bg color', function() screen:expect{any='new_bg=dark'} end) end) + +-- These tests require `thelpers` because --headless/--embed +-- does not initialize the TUI. +describe("TUI as a client", function() + + it("connects to remote instance (with its own TUI)", function() + local server_super = spawn_argv(false) -- equivalent to clear() + local client_super = spawn_argv(true) + + set_session(server_super) + local server_pipe = new_pipename() + local screen_server = thelpers.screen_setup(0, + string.format([=[["%s", "--listen", "%s", "-u", "NONE", "-i", "NONE", "--cmd", "%s laststatus=2 background=dark"]]=], + nvim_prog, server_pipe, nvim_set)) + + feed_data("iHello, World") + screen_server:expect{grid=[[ + Hello, World{1: } | + {4:~ }| + {4:~ }| + {4:~ }| + {5:[No Name] [+] }| + {3:-- INSERT --} | + {3:-- TERMINAL --} | + ]]} + feed_data("\027") + screen_server:expect{grid=[[ + Hello, Worl{1:d} | + {4:~ }| + {4:~ }| + {4:~ }| + {5:[No Name] [+] }| + | + {3:-- TERMINAL --} | + ]]} + + set_session(client_super) + local screen_client = thelpers.screen_setup(0, + string.format([=[["%s", "-u", "NONE", "-i", "NONE", "--server", "%s", "--remote-ui"]]=], + nvim_prog, server_pipe)) + + screen_client:expect{grid=[[ + Hello, Worl{1:d} | + {4:~ }| + {4:~ }| + {4:~ }| + {5:[No Name] [+] }| + | + {3:-- TERMINAL --} | + ]]} + + feed_data(":q!\n") + + server_super:close() + client_super:close() + end) + + it("connects to remote instance (--headless)", function() + local server = helpers.spawn_argv(false) -- equivalent to clear() + local client_super = spawn_argv(true) + + set_session(server) + local server_pipe = eval'v:servername' + feed'iHalloj!<esc>' + + set_session(client_super) + local screen = thelpers.screen_setup(0, + string.format([=[["%s", "-u", "NONE", "-i", "NONE", "--server", "%s", "--remote-ui"]]=], + nvim_prog, server_pipe)) + + screen:expect{grid=[[ + Halloj{1:!} | + {4:~ }| + {4:~ }| + {4:~ }| + {4:~ }| + | + {3:-- TERMINAL --} | + ]]} + + client_super:close() + server:close() + end) + + + it("throws error when no server exists", function() + clear() + local screen = thelpers.screen_setup(0, + string.format([=[["%s", "-u", "NONE", "-i", "NONE", "--server", "127.0.0.1:2436546", "--remote-ui"]]=], + nvim_prog), 60) + + screen:expect([[ + Remote ui failed to start: {MATCH:.*}| + | + [Process exited 1]{1: } | + | + | + | + {3:-- TERMINAL --} | + ]]) + end) + + it("exits when server quits", function() + local server_super = spawn_argv(false) -- equivalent to clear() + local client_super = spawn_argv(true) + + set_session(server_super) + local server_pipe = new_pipename() + local screen_server = thelpers.screen_setup(0, + string.format([=[["%s", "--listen", "%s", "-u", "NONE", "-i", "NONE", "--cmd", "%s laststatus=2 background=dark"]]=], + nvim_prog, server_pipe, nvim_set)) + + feed_data("iHello, World") + screen_server:expect{grid=[[ + Hello, World{1: } | + {4:~ }| + {4:~ }| + {4:~ }| + {5:[No Name] [+] }| + {3:-- INSERT --} | + {3:-- TERMINAL --} | + ]]} + feed_data("\027") + screen_server:expect{grid=[[ + Hello, Worl{1:d} | + {4:~ }| + {4:~ }| + {4:~ }| + {5:[No Name] [+] }| + | + {3:-- TERMINAL --} | + ]]} + + set_session(client_super) + local screen_client = thelpers.screen_setup(0, + string.format([=[["%s", "-u", "NONE", "-i", "NONE", "--server", "%s", "--remote-ui"]]=], + nvim_prog, server_pipe)) + + screen_client:expect{grid=[[ + Hello, Worl{1:d} | + {4:~ }| + {4:~ }| + {4:~ }| + {5:[No Name] [+] }| + | + {3:-- TERMINAL --} | + ]]} + + -- quitting the server + set_session(server_super) + feed_data(":q!\n") + screen_server:expect({any="Process exited 0"}) + + -- assert that client has exited + screen_client:expect({any="Process exited 0"}) + + server_super:close() + client_super:close() + end) +end) |