diff options
Diffstat (limited to 'test/functional/ui')
-rw-r--r-- | test/functional/ui/bufhl_spec.lua | 234 | ||||
-rw-r--r-- | test/functional/ui/cmdline_highlight_spec.lua | 32 | ||||
-rw-r--r-- | test/functional/ui/cmdline_spec.lua | 645 | ||||
-rw-r--r-- | test/functional/ui/cursor_spec.lua | 52 | ||||
-rw-r--r-- | test/functional/ui/embed_spec.lua | 81 | ||||
-rw-r--r-- | test/functional/ui/fold_spec.lua | 8 | ||||
-rw-r--r-- | test/functional/ui/highlight_spec.lua | 137 | ||||
-rw-r--r-- | test/functional/ui/hlstate_spec.lua | 287 | ||||
-rw-r--r-- | test/functional/ui/inccommand_spec.lua | 71 | ||||
-rw-r--r-- | test/functional/ui/mode_spec.lua | 116 | ||||
-rw-r--r-- | test/functional/ui/mouse_spec.lua | 31 | ||||
-rw-r--r-- | test/functional/ui/options_spec.lua | 111 | ||||
-rw-r--r-- | test/functional/ui/output_spec.lua | 33 | ||||
-rw-r--r-- | test/functional/ui/popupmenu_spec.lua | 260 | ||||
-rw-r--r-- | test/functional/ui/screen.lua | 800 | ||||
-rw-r--r-- | test/functional/ui/screen_basic_spec.lua | 146 | ||||
-rw-r--r-- | test/functional/ui/searchhl_spec.lua | 66 | ||||
-rw-r--r-- | test/functional/ui/sign_spec.lua | 66 | ||||
-rw-r--r-- | test/functional/ui/tabline_spec.lua | 12 | ||||
-rw-r--r-- | test/functional/ui/wildmode_spec.lua | 81 |
20 files changed, 2444 insertions, 825 deletions
diff --git a/test/functional/ui/bufhl_spec.lua b/test/functional/ui/bufhl_spec.lua index 5b38921e50..95c9427399 100644 --- a/test/functional/ui/bufhl_spec.lua +++ b/test/functional/ui/bufhl_spec.lua @@ -3,7 +3,7 @@ local Screen = require('test.functional.ui.screen') local clear, feed, insert = helpers.clear, helpers.feed, helpers.insert local command, neq = helpers.command, helpers.neq -local curbufmeths = helpers.curbufmeths +local curbufmeths, eq = helpers.curbufmeths, helpers.eq describe('Buffer highlighting', function() local screen @@ -23,7 +23,13 @@ describe('Buffer highlighting', function() [7] = {bold = true}, [8] = {underline = true, bold = true, foreground = Screen.colors.SlateBlue}, [9] = {foreground = Screen.colors.SlateBlue, underline = true}, - [10] = {foreground = Screen.colors.Red} + [10] = {foreground = Screen.colors.Red}, + [11] = {foreground = Screen.colors.Grey100, background = Screen.colors.Red}, + [12] = {foreground = Screen.colors.Blue1}, + [13] = {background = Screen.colors.LightGrey}, + [14] = {background = Screen.colors.Gray90}, + [15] = {background = Screen.colors.Gray90, bold = true, foreground = Screen.colors.Brown}, + [16] = {foreground = Screen.colors.Magenta, background = Screen.colors.Gray90}, }) end) @@ -77,7 +83,7 @@ describe('Buffer highlighting', function() | ]]) - clear_hl(-1, 0 , -1) + clear_hl(-1, 0, -1) screen:expect([[ these are some lines | ^ | @@ -275,4 +281,226 @@ describe('Buffer highlighting', function() | ]]) end) + + describe('virtual text annotations', function() + local set_virtual_text = curbufmeths.set_virtual_text + local id1, id2 + before_each(function() + insert([[ + 1 + 2 + 3 + + x = 4]]) + feed('O<esc>20A5, <esc>gg') + screen:expect([[ + ^1 + 2 | + 3 + | + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5| + , 5, 5, 5, 5, 5, 5, | + x = 4 | + {1:~ }| + {1:~ }| + | + ]]) + + id1 = set_virtual_text(0, 0, {{"=", "Statement"}, {" 3", "Number"}}, {}) + set_virtual_text(id1, 1, {{"ERROR:", "ErrorMsg"}, {" invalid syntax"}}, {}) + id2 = set_virtual_text(0, 2, {{"Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua."}}, {}) + neq(id2, id1) + + end) + + it('works', function() + screen:expect([[ + ^1 + 2 {3:=}{2: 3} | + 3 + {11:ERROR:} invalid syntax | + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5| + , 5, 5, 5, 5, 5, 5, Lorem ipsum dolor s| + x = 4 | + {1:~ }| + {1:~ }| + | + ]]) + + clear_hl(id1, 0, -1) + screen:expect([[ + ^1 + 2 | + 3 + | + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5| + , 5, 5, 5, 5, 5, 5, Lorem ipsum dolor s| + x = 4 | + {1:~ }| + {1:~ }| + | + ]]) + + -- Handles doublewidth chars, leaving a space if truncating + -- in the middle of a char + eq(-1, set_virtual_text(-1, 1, {{"暗x事zz速野谷質結育副住新覚丸活解終事", "Comment"}}, {})) + screen:expect([[ + ^1 + 2 | + 3 + {12:暗x事zz速野谷質結育副住新覚丸活解終 }| + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5| + , 5, 5, 5, 5, 5, 5, Lorem ipsum dolor s| + x = 4 | + {1:~ }| + {1:~ }| + | + ]]) + + feed("2Gx") + screen:expect([[ + 1 + 2 | + ^ + {12:暗x事zz速野谷質結育副住新覚丸活解終事}| + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5| + , 5, 5, 5, 5, 5, 5, Lorem ipsum dolor s| + x = 4 | + {1:~ }| + {1:~ }| + | + ]]) + + feed("2Gdd") + screen:expect([[ + 1 + 2 | + ^5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5| + , 5, 5, 5, 5, 5, 5, Lorem ipsum dolor s| + x = 4 | + {1:~ }| + {1:~ }| + {1:~ }| + | + ]]) + end) + + it('is not highlighted by visual selection', function() + feed("ggVG") + screen:expect([[ + {13:1 + 2} {3:=}{2: 3} | + {13:3 +} {11:ERROR:} invalid syntax | + {13:5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5}| + {13:, 5, 5, 5, 5, 5, 5, } Lorem ipsum dolor s| + ^x{13: = 4} | + {1:~ }| + {1:~ }| + {7:-- VISUAL LINE --} | + ]]) + + feed("<esc>") + screen:expect([[ + 1 + 2 {3:=}{2: 3} | + 3 + {11:ERROR:} invalid syntax | + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5| + , 5, 5, 5, 5, 5, 5, Lorem ipsum dolor s| + ^x = 4 | + {1:~ }| + {1:~ }| + | + ]]) + + -- special case: empty line has extra eol highlight + feed("ggd$") + screen:expect([[ + ^ {3:=}{2: 3} | + 3 + {11:ERROR:} invalid syntax | + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5| + , 5, 5, 5, 5, 5, 5, Lorem ipsum dolor s| + x = 4 | + {1:~ }| + {1:~ }| + | + ]]) + + feed("jvk") + screen:expect([[ + ^ {3:=}{2: 3} | + {13:3} + {11:ERROR:} invalid syntax | + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5| + , 5, 5, 5, 5, 5, 5, Lorem ipsum dolor s| + x = 4 | + {1:~ }| + {1:~ }| + {7:-- VISUAL --} | + ]]) + + feed("o") + screen:expect([[ + {13: }{3:=}{2: 3} | + ^3 + {11:ERROR:} invalid syntax | + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5| + , 5, 5, 5, 5, 5, 5, Lorem ipsum dolor s| + x = 4 | + {1:~ }| + {1:~ }| + {7:-- VISUAL --} | + ]]) + end) + + + it('works with listchars', function() + command("set list listchars+=eol:$") + screen:expect([[ + ^1 + 2{1:$}{3:=}{2: 3} | + 3 +{1:$}{11:ERROR:} invalid syntax | + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5| + , 5, 5, 5, 5, 5, 5,{1:-$}Lorem ipsum dolor s| + x = 4{1:$} | + {1:~ }| + {1:~ }| + | + ]]) + + clear_hl(-1, 0, -1) + screen:expect([[ + ^1 + 2{1:$} | + 3 +{1:$} | + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5| + , 5, 5, 5, 5, 5, 5,{1:-$} | + x = 4{1:$} | + {1:~ }| + {1:~ }| + | + ]]) + end) + + it('works with cursorline', function() + command("set cursorline") + + screen:expect([[ + {14:^1 + 2 }{15:=}{16: 3}{14: }| + 3 + {11:ERROR:} invalid syntax | + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5| + , 5, 5, 5, 5, 5, 5, Lorem ipsum dolor s| + x = 4 | + {1:~ }| + {1:~ }| + | + ]]) + + feed('j') + screen:expect([[ + 1 + 2 {3:=}{2: 3} | + {14:^3 + }{11:ERROR:}{14: invalid syntax }| + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5| + , 5, 5, 5, 5, 5, 5, Lorem ipsum dolor s| + x = 4 | + {1:~ }| + {1:~ }| + | + ]]) + + + feed('j') + screen:expect([[ + 1 + 2 {3:=}{2: 3} | + 3 + {11:ERROR:} invalid syntax | + {14:^5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5}| + {14:, 5, 5, 5, 5, 5, 5, Lorem ipsum dolor s}| + x = 4 | + {1:~ }| + {1:~ }| + | + ]]) + end) + end) + end) diff --git a/test/functional/ui/cmdline_highlight_spec.lua b/test/functional/ui/cmdline_highlight_spec.lua index 5d9fffdf23..1568b7816e 100644 --- a/test/functional/ui/cmdline_highlight_spec.lua +++ b/test/functional/ui/cmdline_highlight_spec.lua @@ -32,7 +32,7 @@ before_each(function() highlight RBP4 guibg=Blue let g:NUM_LVLS = 4 function Redraw() - redraw! + mode return '' endfunction let g:id = '' @@ -267,7 +267,7 @@ describe('Command-line coloring', function() :echo {RBP1:(}{RBP2:(}42{RBP2:)}^ | ]]) redraw_input() - screen:expect([[ + screen:expect{grid=[[ | {EOB:~ }| {EOB:~ }| @@ -276,7 +276,7 @@ describe('Command-line coloring', function() {EOB:~ }| {EOB:~ }| :echo {RBP1:(}{RBP2:(}42{RBP2:)}^ | - ]]) + ]], reset=true} end) for _, func_part in ipairs({'', 'n', 'msg'}) do it('disables :echo' .. func_part .. ' messages', function() @@ -755,7 +755,7 @@ describe('Command-line coloring', function() eq(1, meths.eval('1')) end) end) -describe('Ex commands coloring support', function() +describe('Ex commands coloring', function() it('works', function() meths.set_var('Nvim_color_cmdline', 'RainBowParens') feed(':echo (((1)))') @@ -831,7 +831,7 @@ describe('Ex commands coloring support', function() | ]]) end) - it('does not prevent mapping error from cancelling prompt', function() + it('mapping error does not cancel prompt', function() command("cnoremap <expr> x execute('throw 42')[-1]") feed(':#x') screen:expect([[ @@ -846,27 +846,17 @@ describe('Ex commands coloring support', function() ]]) feed('<CR>') screen:expect([[ - ^ | - {EOB:~ }| - {EOB:~ }| {EOB:~ }| {EOB:~ }| {EOB:~ }| - {EOB:~ }| - | + :# | + {ERR:Error detected while processing :} | + {ERR:E605: Exception not caught: 42} | + {ERR:E749: empty buffer} | + {PE:Press ENTER or type command to continue}^ | ]]) feed('<CR>') - screen:expect([[ - ^ | - {EOB:~ }| - {EOB:~ }| - {EOB:~ }| - {EOB:~ }| - {EOB:~ }| - {EOB:~ }| - | - ]]) - eq('Error detected while processing :\nE605: Exception not caught: 42', + eq('Error detected while processing :\nE605: Exception not caught: 42\nE749: empty buffer', meths.command_output('messages')) end) it('errors out when failing to get callback', function() diff --git a/test/functional/ui/cmdline_spec.lua b/test/functional/ui/cmdline_spec.lua index 5ce49822e5..0ebb62f78f 100644 --- a/test/functional/ui/cmdline_spec.lua +++ b/test/functional/ui/cmdline_spec.lua @@ -1,23 +1,16 @@ local helpers = require('test.functional.helpers')(after_each) local Screen = require('test.functional.ui.screen') -local clear, feed, eq = helpers.clear, helpers.feed, helpers.eq +local clear, feed = helpers.clear, helpers.feed local source = helpers.source -local ok = helpers.ok local command = helpers.command -describe('external cmdline', function() +local function test_cmdline(linegrid) local screen - local last_level = 0 - local cmdline = {} - local block = nil - local wild_items = nil - local wild_selected = nil before_each(function() clear() - cmdline, block = {}, nil screen = Screen.new(25, 5) - screen:attach({rgb=true, ext_cmdline=true}) + screen:attach({rgb=true, ext_cmdline=true, ext_linegrid=linegrid}) screen:set_default_attr_ids({ [1] = {bold = true, foreground = Screen.colors.Blue1}, [2] = {reverse = true}, @@ -25,138 +18,73 @@ describe('external cmdline', function() [4] = {foreground = Screen.colors.Grey100, background = Screen.colors.Red}, [5] = {bold = true, foreground = Screen.colors.SeaGreen4}, }) - screen:set_on_event_handler(function(name, data) - if name == "cmdline_show" then - local content, pos, firstc, prompt, indent, level = unpack(data) - ok(level > 0) - cmdline[level] = {content=content, pos=pos, firstc=firstc, - prompt=prompt, indent=indent} - last_level = level - elseif name == "cmdline_hide" then - local level = data[1] - cmdline[level] = nil - elseif name == "cmdline_special_char" then - local char, shift, level = unpack(data) - cmdline[level].special = {char, shift} - elseif name == "cmdline_pos" then - local pos, level = unpack(data) - cmdline[level].pos = pos - elseif name == "cmdline_block_show" then - block = data[1] - elseif name == "cmdline_block_append" then - block[#block+1] = data[1] - elseif name == "cmdline_block_hide" then - block = nil - elseif name == "wildmenu_show" then - wild_items = data[1] - elseif name == "wildmenu_select" then - wild_selected = data[1] - elseif name == "wildmenu_hide" then - wild_items, wild_selected = nil, nil - end - end) end) after_each(function() screen:detach() end) - local function expect_cmdline(level, expected) - local attr_ids = screen._default_attr_ids - local attr_ignore = screen._default_attr_ignore - local actual = '' - for _, chunk in ipairs(cmdline[level] and cmdline[level].content or {}) do - local attrs, text = chunk[1], chunk[2] - if screen:_equal_attrs(attrs, {}) then - actual = actual..text - else - local attr_id = screen:_get_attr_id(attr_ids, attr_ignore, attrs) - actual = actual..'{' .. attr_id .. ':' .. text .. '}' - end - end - eq(expected, actual) - end - it('works', function() feed(':') - screen:expect([[ + screen:expect{grid=[[ ^ | {1:~ }| {1:~ }| {1:~ }| | - ]], nil, nil, function() - eq(1, last_level) - eq({{ - content = { { {}, "" } }, - firstc = ":", - indent = 0, - pos = 0, - prompt = "" - }}, cmdline) - end) + ]], cmdline={{ + firstc = ":", + content = {{""}}, + pos = 0, + }}} feed('sign') - screen:expect([[ + screen:expect{grid=[[ ^ | {1:~ }| {1:~ }| {1:~ }| | - ]], nil, nil, function() - eq({{ - content = { { {}, "sign" } }, - firstc = ":", - indent = 0, - pos = 4, - prompt = "" - }}, cmdline) - end) + ]], cmdline={{ + firstc = ":", + content = {{"sign"}}, + pos = 4, + }}} feed('<Left>') - screen:expect([[ + screen:expect{grid=[[ ^ | {1:~ }| {1:~ }| {1:~ }| | - ]], nil, nil, function() - eq({{ - content = { { {}, "sign" } }, - firstc = ":", - indent = 0, - pos = 3, - prompt = "" - }}, cmdline) - end) + ]], cmdline={{ + firstc = ":", + content = {{"sign"}}, + pos = 3, + }}} feed('<bs>') - screen:expect([[ + screen:expect{grid=[[ ^ | {1:~ }| {1:~ }| {1:~ }| | - ]], nil, nil, function() - eq({{ - content = { { {}, "sin" } }, - firstc = ":", - indent = 0, - pos = 2, - prompt = "" - }}, cmdline) - end) + ]], cmdline={{ + firstc = ":", + content = {{"sin"}}, + pos = 2, + }}} feed('<Esc>') - screen:expect([[ + screen:expect{grid=[[ ^ | {1:~ }| {1:~ }| {1:~ }| | - ]], nil, nil, function() - eq({}, cmdline) - end) + ]]} end) describe("redraws statusline on entering", function() @@ -166,28 +94,32 @@ describe('external cmdline', function() end) it('from normal mode', function() - feed(':') - screen:expect([[ + screen:expect{grid=[[ + ^ | + {1:~ }| + {1:~ }| + {3:n }| | + ]]} + + feed(':') + screen:expect{grid=[[ + ^ | {1:~ }| {1:~ }| - {3:c^ }| + {3:c }| | - ]], nil, nil, function() - eq({{ - content = { { {}, "" } }, - firstc = ":", - indent = 0, - pos = 0, - prompt = "" - }}, cmdline) - end) + ]], cmdline={{ + firstc = ":", + content = {{""}}, + pos = 0, + }}} end) it('but not with scrolled messages', function() screen:try_resize(50,10) feed(':echoerr doesnotexist<cr>') - screen:expect([[ + screen:expect{grid=[[ | {1:~ }| {1:~ }| @@ -198,9 +130,9 @@ describe('external cmdline', function() {4:E121: Undefined variable: doesnotexist} | {4:E15: Invalid expression: doesnotexist} | {5:Press ENTER or type command to continue}^ | - ]]) + ]]} feed(':echoerr doesnotexist<cr>') - screen:expect([[ + screen:expect{grid=[[ | {1:~ }| {1:~ }| @@ -211,10 +143,10 @@ describe('external cmdline', function() {4:E121: Undefined variable: doesnotexist} | {4:E15: Invalid expression: doesnotexist} | {5:Press ENTER or type command to continue}^ | - ]]) + ]]} feed(':echoerr doesnotexist<cr>') - screen:expect([[ + screen:expect{grid=[[ | {1:~ }| {3: }| @@ -225,10 +157,10 @@ describe('external cmdline', function() {4:E121: Undefined variable: doesnotexist} | {4:E15: Invalid expression: doesnotexist} | {5:Press ENTER or type command to continue}^ | - ]]) + ]]} feed('<cr>') - screen:expect([[ + screen:expect{grid=[[ ^ | {1:~ }| {1:~ }| @@ -239,372 +171,308 @@ describe('external cmdline', function() {1:~ }| {3:n }| | - ]]) + ]]} end) end) it("works with input()", function() feed(':call input("input", "default")<cr>') - screen:expect([[ + screen:expect{grid=[[ ^ | {1:~ }| {1:~ }| {1:~ }| | - ]], nil, nil, function() - eq({{ - content = { { {}, "default" } }, - firstc = "", - indent = 0, - pos = 7, - prompt = "input" - }}, cmdline) - end) + ]], cmdline={{ + prompt = "input", + content = {{"default"}}, + pos = 7, + }}} + feed('<cr>') - screen:expect([[ + screen:expect{grid=[[ ^ | {1:~ }| {1:~ }| {1:~ }| | - ]], nil, nil, function() - eq({}, cmdline) - end) - + ]]} end) it("works with special chars and nested cmdline", function() feed(':xx<c-r>') - screen:expect([[ + screen:expect{grid=[[ ^ | {1:~ }| {1:~ }| {1:~ }| | - ]], nil, nil, function() - eq({{ - content = { { {}, "xx" } }, - firstc = ":", - indent = 0, - pos = 2, - prompt = "", - special = {'"', true}, - }}, cmdline) - end) + ]], cmdline={{ + firstc = ":", + content = {{"xx"}}, + pos = 2, + special = {'"', true}, + }}} feed('=') - screen:expect([[ + screen:expect{grid=[[ ^ | {1:~ }| {1:~ }| {1:~ }| | - ]], nil, nil, function() - eq({{ - content = { { {}, "xx" } }, - firstc = ":", - indent = 0, - pos = 2, - prompt = "", - special = {'"', true}, - },{ - content = { { {}, "" } }, - firstc = "=", - indent = 0, - pos = 0, - prompt = "", - }}, cmdline) - end) + ]], cmdline={{ + firstc = ":", + content = {{"xx"}}, + pos = 2, + special = {'"', true}, + }, { + firstc = "=", + content = {{""}}, + pos = 0, + }}} feed('1+2') local expectation = {{ - content = { { {}, "xx" } }, - firstc = ":", - indent = 0, - pos = 2, - prompt = "", - special = {'"', true}, - },{ - content = { - { {}, "1" }, - { {}, "+" }, - { {}, "2" }, - }, - firstc = "=", - indent = 0, - pos = 3, - prompt = "", - }} - screen:expect([[ + firstc = ":", + content = {{"xx"}}, + pos = 2, + special = {'"', true}, + }, { + firstc = "=", + content = {{"1"}, {"+"}, {"2"}}, + pos = 3, + }} + + screen:expect{grid=[[ ^ | {1:~ }| {1:~ }| {1:~ }| | - ]], nil, nil, function() - eq(expectation, cmdline) - end) + ]], cmdline=expectation} -- erase information, so we check if it is retransmitted - cmdline = {} - command("redraw!") - -- redraw! forgets cursor position. Be OK with that, as UI should indicate - -- focus is at external cmdline anyway. - screen:expect([[ - | + command("mode") + screen:expect{grid=[[ + ^ | {1:~ }| {1:~ }| {1:~ }| - ^ | - ]], nil, nil, function() - eq(expectation, cmdline) - end) + | + ]], cmdline=expectation, reset=true} feed('<cr>') - screen:expect([[ - | + screen:expect{grid=[[ + ^ | {1:~ }| {1:~ }| {1:~ }| - ^ | - ]], nil, nil, function() - eq({{ - content = { { {}, "xx3" } }, - firstc = ":", - indent = 0, - pos = 3, - prompt = "", - }}, cmdline) - end) + | + ]], cmdline={{ + firstc = ":", + content = {{"xx3"}}, + pos = 3, + }}} feed('<esc>') - screen:expect([[ + screen:expect{grid=[[ ^ | {1:~ }| {1:~ }| {1:~ }| | - ]], nil, nil, function() - eq({}, cmdline) - end) + ]]} end) it("works with function definitions", function() feed(':function Foo()<cr>') - screen:expect([[ + screen:expect{grid=[[ ^ | {1:~ }| {1:~ }| {1:~ }| | - ]], nil, nil, function() - eq({{ - content = { { {}, "" } }, - firstc = ":", - indent = 2, - pos = 0, - prompt = "", - }}, cmdline) - eq({ { { {}, 'function Foo()'} } }, block) - end) + ]], cmdline={{ + indent = 2, + firstc = ":", + content = {{""}}, + pos = 0, + }}, cmdline_block = { + {{'function Foo()'}}, + }} feed('line1<cr>') - screen:expect([[ + screen:expect{grid=[[ ^ | {1:~ }| {1:~ }| {1:~ }| | - ]], nil, nil, function() - eq({ { { {}, 'function Foo()'} }, - { { {}, ' line1'} } }, block) - end) + ]], cmdline={{ + indent = 2, + firstc = ":", + content = {{""}}, + pos = 0, + }}, cmdline_block = { + {{'function Foo()'}}, + {{' line1'}}, + }} - block = {} - command("redraw!") - screen:expect([[ - | + command("mode") + screen:expect{grid=[[ + ^ | {1:~ }| {1:~ }| {1:~ }| - ^ | - ]], nil, nil, function() - eq({ { { {}, 'function Foo()'} }, - { { {}, ' line1'} } }, block) - end) + | + ]], cmdline={{ + indent = 2, + firstc = ":", + content = {{""}}, + pos = 0, + }}, cmdline_block = { + {{'function Foo()'}}, + {{' line1'}}, + }, reset=true} feed('endfunction<cr>') - screen:expect([[ + screen:expect{grid=[[ ^ | {1:~ }| {1:~ }| {1:~ }| | - ]], nil, nil, function() - eq(nil, block) - end) + ]]} -- Try once more, to check buffer is reinitialized. #8007 feed(':function Bar()<cr>') - screen:expect([[ + screen:expect{grid=[[ ^ | {1:~ }| {1:~ }| {1:~ }| | - ]], nil, nil, function() - eq({{ - content = { { {}, "" } }, - firstc = ":", - indent = 2, - pos = 0, - prompt = "", - }}, cmdline) - eq({ { { {}, 'function Bar()'} } }, block) - end) + ]], cmdline={{ + indent = 2, + firstc = ":", + content = {{""}}, + pos = 0, + }}, cmdline_block = { + {{'function Bar()'}}, + }} feed('endfunction<cr>') - screen:expect([[ + screen:expect{grid=[[ ^ | {1:~ }| {1:~ }| {1:~ }| | - ]], nil, nil, function() - eq(nil, block) - end) + ]]} + end) it("works with cmdline window", function() feed(':make') - screen:expect([[ + screen:expect{grid=[[ ^ | {1:~ }| {1:~ }| {1:~ }| | - ]], nil, nil, function() - eq({{ - content = { { {}, "make" } }, - firstc = ":", - indent = 0, - pos = 4, - prompt = "" - }}, cmdline) - end) + ]], cmdline={{ + firstc = ":", + content = {{"make"}}, + pos = 4, + }}} feed('<c-f>') - screen:expect([[ + screen:expect{grid=[[ | {2:[No Name] }| {1::}make^ | {3:[Command Line] }| | - ]], nil, nil, function() - eq({}, cmdline) - end) + ]]} -- nested cmdline feed(':yank') - screen:expect([[ + screen:expect{grid=[[ | {2:[No Name] }| {1::}make^ | {3:[Command Line] }| | - ]], nil, nil, function() - eq({nil, { - content = { { {}, "yank" } }, - firstc = ":", - indent = 0, - pos = 4, - prompt = "" - }}, cmdline) - end) + ]], cmdline={nil, { + firstc = ":", + content = {{"yank"}}, + pos = 4, + }}} - cmdline = {} - command("redraw!") - screen:expect([[ + command("mode") + screen:expect{grid=[[ | {2:[No Name] }| - {1::}make | + {1::}make^ | {3:[Command Line] }| - ^ | - ]], nil, nil, function() - eq({nil, { - content = { { {}, "yank" } }, - firstc = ":", - indent = 0, - pos = 4, - prompt = "" - }}, cmdline) - end) + | + ]], cmdline={nil, { + firstc = ":", + content = {{"yank"}}, + pos = 4, + }}, reset=true} feed("<c-c>") - screen:expect([[ + screen:expect{grid=[[ | {2:[No Name] }| {1::}make^ | {3:[Command Line] }| | - ]], nil, nil, function() - eq({}, cmdline) - end) + ]]} feed("<c-c>") - screen:expect([[ - | + screen:expect{grid=[[ + ^ | {2:[No Name] }| - {1::}make^ | + {1::}make | {3:[Command Line] }| | - ]], nil, nil, function() - eq({{ - content = { { {}, "make" } }, - firstc = ":", - indent = 0, - pos = 4, - prompt = "" - }}, cmdline) - end) + ]], cmdline={{ + firstc = ":", + content = {{"make"}}, + pos = 4, + }}} - cmdline = {} command("redraw!") - screen:expect([[ - | + screen:expect{grid=[[ + ^ | {1:~ }| {1:~ }| {1:~ }| - ^ | - ]], nil, nil, function() - eq({{ - content = { { {}, "make" } }, - firstc = ":", - indent = 0, - pos = 4, - prompt = "" - }}, cmdline) - end) + | + ]], cmdline={{ + firstc = ":", + content = {{"make"}}, + pos = 4, + }}} end) it('works with inputsecret()', function() feed(":call inputsecret('secret:')<cr>abc123") - screen:expect([[ + screen:expect{grid=[[ ^ | {1:~ }| {1:~ }| {1:~ }| | - ]], nil, nil, function() - eq({{ - content = { { {}, "******" } }, - firstc = "", - indent = 0, - pos = 6, - prompt = "secret:" - }}, cmdline) - end) + ]], cmdline={{ + prompt = "secret:", + content = {{"******"}}, + pos = 6, + }}} end) it('works with highlighted cmdline', function() @@ -636,23 +504,21 @@ describe('external cmdline', function() screen:set_default_attr_ids({ RBP1={background = Screen.colors.Red}, RBP2={background = Screen.colors.Yellow}, - RBP3={background = Screen.colors.Green}, - RBP4={background = Screen.colors.Blue}, EOB={bold = true, foreground = Screen.colors.Blue1}, - ERR={foreground = Screen.colors.Grey100, background = Screen.colors.Red}, - SK={foreground = Screen.colors.Blue}, - PE={bold = true, foreground = Screen.colors.SeaGreen4} }) feed('<f5>(a(b)a)') - screen:expect([[ + screen:expect{grid=[[ ^ | {EOB:~ }| {EOB:~ }| {EOB:~ }| | - ]], nil, nil, function() - expect_cmdline(1, '{RBP1:(}a{RBP2:(}b{RBP2:)}a{RBP1:)}') - end) + ]], cmdline={{ + prompt = '>', + content = {{'(', 'RBP1'}, {'a'}, {'(', 'RBP2'}, {'b'}, + { ')', 'RBP2'}, {'a'}, {')', 'RBP1'}}, + pos = 7, + }}} end) it('works together with ext_wildmenu', function() @@ -670,98 +536,73 @@ describe('external cmdline', function() screen:set_option('ext_wildmenu', true) feed(':sign <tab>') - screen:expect([[ + screen:expect{grid=[[ ^ | {1:~ }| {1:~ }| {1:~ }| | - ]], nil, nil, function() - eq({{ - content = { { {}, "sign define"} }, - firstc = ":", - indent = 0, - pos = 11, - prompt = "" - }}, cmdline) - eq(expected, wild_items) - eq(0, wild_selected) - end) + ]], cmdline={{ + firstc = ":", + content = {{"sign define"}}, + pos = 11, + }}, wildmenu_items=expected, wildmenu_pos=0} feed('<tab>') - screen:expect([[ + screen:expect{grid=[[ ^ | {1:~ }| {1:~ }| {1:~ }| | - ]], nil, nil, function() - eq({{ - content = { { {}, "sign jump"} }, - firstc = ":", - indent = 0, - pos = 9, - prompt = "" - }}, cmdline) - eq(expected, wild_items) - eq(1, wild_selected) - end) + ]], cmdline={{ + firstc = ":", + content = {{"sign jump"}}, + pos = 9, + }}, wildmenu_items=expected, wildmenu_pos=1} feed('<left><left>') - screen:expect([[ + screen:expect{grid=[[ ^ | {1:~ }| {1:~ }| {1:~ }| | - ]], nil, nil, function() - eq({{ - content = { { {}, "sign "} }, - firstc = ":", - indent = 0, - pos = 5, - prompt = "" - }}, cmdline) - eq(expected, wild_items) - eq(-1, wild_selected) - end) + ]], cmdline={{ + firstc = ":", + content = {{"sign "}}, + pos = 5, + }}, wildmenu_items=expected, wildmenu_pos=-1} feed('<right>') - screen:expect([[ + screen:expect{grid=[[ ^ | {1:~ }| {1:~ }| {1:~ }| | - ]], nil, nil, function() - eq({{ - content = { { {}, "sign define"} }, - firstc = ":", - indent = 0, - pos = 11, - prompt = "" - }}, cmdline) - eq(expected, wild_items) - eq(0, wild_selected) - end) + ]], cmdline={{ + firstc = ":", + content = {{"sign define"}}, + pos = 11, + }}, wildmenu_items=expected, wildmenu_pos=0} feed('a') - screen:expect([[ + screen:expect{grid=[[ ^ | {1:~ }| {1:~ }| {1:~ }| | - ]], nil, nil, function() - eq({{ - content = { { {}, "sign definea"} }, - firstc = ":", - indent = 0, - pos = 12, - prompt = "" - }}, cmdline) - eq(nil, wild_items) - eq(nil, wild_selected) - end) + ]], cmdline={{ + firstc = ":", + content = {{"sign definea"}}, + pos = 12, + }}} end) -end) +end + +-- the representation of cmdline and cmdline_block contents changed with ext_linegrid +-- (which uses indexed highlights) so make sure to test both +describe('ui/ext_cmdline', function() test_cmdline(true) end) +describe('ui/ext_cmdline (legacy highlights)', function() test_cmdline(false) end) diff --git a/test/functional/ui/cursor_spec.lua b/test/functional/ui/cursor_spec.lua index 812c095add..3e0370db14 100644 --- a/test/functional/ui/cursor_spec.lua +++ b/test/functional/ui/cursor_spec.lua @@ -28,6 +28,8 @@ describe('ui/cursor', function() name = 'normal', hl_id = 0, id_lm = 0, + attr = {}, + attr_lm = {}, mouse_shape = 0, short_name = 'n' }, [2] = { @@ -39,6 +41,8 @@ describe('ui/cursor', function() name = 'visual', hl_id = 0, id_lm = 0, + attr = {}, + attr_lm = {}, mouse_shape = 0, short_name = 'v' }, [3] = { @@ -50,6 +54,8 @@ describe('ui/cursor', function() name = 'insert', hl_id = 0, id_lm = 0, + attr = {}, + attr_lm = {}, mouse_shape = 0, short_name = 'i' }, [4] = { @@ -61,6 +67,8 @@ describe('ui/cursor', function() name = 'replace', hl_id = 0, id_lm = 0, + attr = {}, + attr_lm = {}, mouse_shape = 0, short_name = 'r' }, [5] = { @@ -72,6 +80,8 @@ describe('ui/cursor', function() name = 'cmdline_normal', hl_id = 0, id_lm = 0, + attr = {}, + attr_lm = {}, mouse_shape = 0, short_name = 'c' }, [6] = { @@ -83,6 +93,8 @@ describe('ui/cursor', function() name = 'cmdline_insert', hl_id = 0, id_lm = 0, + attr = {}, + attr_lm = {}, mouse_shape = 0, short_name = 'ci' }, [7] = { @@ -94,6 +106,8 @@ describe('ui/cursor', function() name = 'cmdline_replace', hl_id = 0, id_lm = 0, + attr = {}, + attr_lm = {}, mouse_shape = 0, short_name = 'cr' }, [8] = { @@ -105,6 +119,8 @@ describe('ui/cursor', function() name = 'operator', hl_id = 0, id_lm = 0, + attr = {}, + attr_lm = {}, mouse_shape = 0, short_name = 'o' }, [9] = { @@ -116,6 +132,8 @@ describe('ui/cursor', function() name = 'visual_select', hl_id = 0, id_lm = 0, + attr = {}, + attr_lm = {}, mouse_shape = 0, short_name = 've' }, [10] = { @@ -155,6 +173,8 @@ describe('ui/cursor', function() name = 'showmatch', hl_id = 0, id_lm = 0, + attr = {}, + attr_lm = {}, short_name = 'sm' }, } @@ -168,17 +188,18 @@ describe('ui/cursor', function() -- Event is published ONLY if the cursor style changed. screen._mode_info = nil command("echo 'test'") - screen:expect([[ + screen:expect{grid=[[ ^ | ~ | ~ | ~ | test | - ]], nil, nil, function() + ]], condition=function() eq(nil, screen._mode_info) - end) + end} -- Change the cursor style. + helpers.command('hi Cursor guibg=DarkGray') helpers.command('set guicursor=n-v-c:block,i-ci-ve:ver25,r-cr-o:hor20' ..',a:blinkwait700-blinkoff400-blinkon250-Cursor/lCursor' ..',sm:block-blinkwait175-blinkoff150-blinkon175') @@ -194,7 +215,10 @@ describe('ui/cursor', function() if m.blinkoff then m.blinkoff = 400 end if m.blinkwait then m.blinkwait = 700 end end - if m.hl_id then m.hl_id = 49 end + if m.hl_id then + m.hl_id = 49 + m.attr = {background = Screen.colors.DarkGray} + end if m.id_lm then m.id_lm = 50 end end @@ -205,6 +229,26 @@ describe('ui/cursor', function() eq('normal', screen.mode) end) + -- Change hl groups only, should update the styles + helpers.command('hi Cursor guibg=Red') + helpers.command('hi lCursor guibg=Green') + + -- Update the expected values. + for _, m in ipairs(expected_mode_info) do + if m.hl_id then + m.attr = {background = Screen.colors.Red} + end + if m.id_lm then + m.attr_lm = {background = Screen.colors.Green} + end + end + -- Assert the new expectation. + screen:expect(function() + eq(expected_mode_info, screen._mode_info) + eq(true, screen._cursor_style_enabled) + eq('normal', screen.mode) + end) + -- Another cursor style. meths.set_option('guicursor', 'n-v-c:ver35-blinkwait171-blinkoff172-blinkon173' ..',ve:hor35,o:ver50,i-ci:block,r-cr:hor90,sm:ver42') diff --git a/test/functional/ui/embed_spec.lua b/test/functional/ui/embed_spec.lua new file mode 100644 index 0000000000..4fc93c3b63 --- /dev/null +++ b/test/functional/ui/embed_spec.lua @@ -0,0 +1,81 @@ +local helpers = require('test.functional.helpers')(after_each) +local Screen = require('test.functional.ui.screen') + +local feed = helpers.feed +local eq = helpers.eq +local clear = helpers.clear + +local function test_embed(ext_linegrid) + local screen + local function startup(...) + clear{headless=false, args={...}} + + -- attach immediately after startup, for early UI + screen = Screen.new(60, 8) + screen:attach{ext_linegrid=ext_linegrid} + screen:set_default_attr_ids({ + [1] = {foreground = Screen.colors.Grey100, background = Screen.colors.Red}, + [2] = {bold = true, foreground = Screen.colors.SeaGreen4}, + [3] = {bold = true, foreground = Screen.colors.Blue1}, + }) + end + + it('can display errors', function() + startup('--cmd', 'echoerr invalid+') + screen:expect([[ + | + | + | + | + Error detected while processing pre-vimrc command line: | + E121: Undefined variable: invalid | + E15: Invalid expression: invalid+ | + Press ENTER or type command to continue^ | + ]]) + + feed('<cr>') + screen:expect([[ + ^ | + {3:~ }| + {3:~ }| + {3:~ }| + {3:~ }| + {3:~ }| + {3:~ }| + | + ]]) + end) + + it("doesn't erase output when setting color scheme", function() + startup('--cmd', 'echoerr "foo"', '--cmd', 'color default', '--cmd', 'echoerr "bar"') + screen:expect([[ + | + | + | + | + Error detected while processing pre-vimrc command line: | + foo | + {1:bar} | + {2:Press ENTER or type command to continue}^ | + ]]) + end) + + it("doesn't erase output when setting Normal colors", function() + startup('--cmd', 'echoerr "foo"', '--cmd', 'hi Normal guibg=Green', '--cmd', 'echoerr "bar"') + screen:expect{grid=[[ + | + | + | + | + Error detected while processing pre-vimrc command line: | + foo | + bar | + Press ENTER or type command to continue^ | + ]], condition=function() + eq(Screen.colors.Green, screen.default_colors.rgb_bg) + end} + end) +end + +describe('--embed UI on startup (ext_linegrid=true)', function() test_embed(true) end) +describe('--embed UI on startup (ext_linegrid=false)', function() test_embed(false) end) diff --git a/test/functional/ui/fold_spec.lua b/test/functional/ui/fold_spec.lua index 9c5a59b58d..39a5c10bb7 100644 --- a/test/functional/ui/fold_spec.lua +++ b/test/functional/ui/fold_spec.lua @@ -65,7 +65,7 @@ describe("folded lines", function() {1:~ }| {1:~ }| {1:~ }| - | + :set noarabicshape | ]]) feed_command("set number foldcolumn=2") @@ -114,7 +114,7 @@ describe("folded lines", function() {1: ~}| {1: ~}| {1: ~}| - | + :set arabicshape | ]]) feed('zo') @@ -126,7 +126,7 @@ describe("folded lines", function() {1: ~}| {1: ~}| {1: ~}| - | + :set arabicshape | ]]) feed_command('set noarabicshape') @@ -138,7 +138,7 @@ describe("folded lines", function() {1: ~}| {1: ~}| {1: ~}| - | + :set noarabicshape | ]]) end) diff --git a/test/functional/ui/highlight_spec.lua b/test/functional/ui/highlight_spec.lua index b46a6c1e46..55fc343e4c 100644 --- a/test/functional/ui/highlight_spec.lua +++ b/test/functional/ui/highlight_spec.lua @@ -40,24 +40,24 @@ describe('highlight: `:syntax manual`', function() end) it("works with buffer switch and 'hidden'", function() - feed_command('e tmp1.vim') - feed_command('e Xtest-functional-ui-highlight.tmp.vim') - feed_command('filetype on') - feed_command('syntax manual') - feed_command('set ft=vim') - feed_command('set syntax=ON') + command('e tmp1.vim') + command('e Xtest-functional-ui-highlight.tmp.vim') + command('filetype on') + command('syntax manual') + command('set ft=vim') + command('set syntax=ON') feed('iecho 1<esc>0') - feed_command('set hidden') - feed_command('w') - feed_command('bn') + command('set hidden') + command('w') + command('bn') feed_command('bp') screen:expect([[ {1:^echo} 1 | {0:~ }| {0:~ }| {0:~ }| - <f 1 --100%-- col 1 | + :bp | ]]) end) @@ -122,7 +122,7 @@ describe('highlight defaults', function() {0:~ }| {0:~ }| {2:[No Name] }| - | + :vsp | ]]) -- navigate to verify that the attributes are properly moved feed('<c-w>j') @@ -140,7 +140,7 @@ describe('highlight defaults', function() {0:~ }| {0:~ }| {1:[No Name] }| - | + :vsp | ]]) -- note that when moving to a window with small width nvim will increase -- the width of the new active window at the expense of a inactive window @@ -160,7 +160,7 @@ describe('highlight defaults', function() {0:~ }| {0:~ }| {2:[No Name] }| - | + :vsp | ]]) feed('<c-w>l') screen:expect([[ @@ -177,7 +177,7 @@ describe('highlight defaults', function() {0:~ }| {0:~ }| {2:[No Name] }| - | + :vsp | ]]) feed('<c-w>h<c-w>h') screen:expect([[ @@ -194,7 +194,7 @@ describe('highlight defaults', function() {0:~ }| {0:~ }| {2:[No Name] }| - | + :vsp | ]]) end) @@ -677,6 +677,7 @@ end) describe('CursorLine highlight', function() before_each(clear) + it('overridden by Error, ColorColumn if fg not set', function() local screen = Screen.new(50,5) screen:set_default_attr_ids({ @@ -690,9 +691,9 @@ describe('CursorLine highlight', function() }) screen:attach() - feed_command('filetype on') - feed_command('syntax on') - feed_command('set cursorline ft=json') + command('filetype on') + command('syntax on') + command('set cursorline ft=json') feed('i{<cr>"a" : abc // 10;<cr>}<cr><esc>') screen:expect([[ {1:{} | @@ -702,7 +703,7 @@ describe('CursorLine highlight', function() | ]]) - feed_command('set colorcolumn=3') + command('set colorcolumn=3') feed('i <esc>') screen:expect([[ {1:{} {7: } | @@ -712,6 +713,62 @@ describe('CursorLine highlight', function() | ]]) end) + + it('with split-windows in diff-mode', function() + local screen = Screen.new(50,12) + screen:set_default_attr_ids({ + [1] = {foreground = Screen.colors.DarkBlue, background = Screen.colors.WebGray}, + [2] = {bold = true, background = Screen.colors.Red}, + [3] = {background = Screen.colors.LightMagenta}, + [4] = {reverse = true}, + [5] = {background = Screen.colors.LightBlue}, + [6] = {background = Screen.colors.LightCyan1, bold = true, foreground = Screen.colors.Blue1}, + [7] = {background = Screen.colors.Red, foreground = Screen.colors.White}, + [8] = {bold = true, foreground = Screen.colors.Blue1}, + [9] = {bold = true, reverse = true}, + [10] = {bold = true}, + }) + screen:attach() + + command('hi CursorLine ctermbg=red ctermfg=white guibg=red guifg=white') + command('set cursorline') + feed('iline 1 some text<cr>line 2 more text<cr>extra line!<cr>extra line!<cr>last line ...<cr>') + feed('<esc>gg') + command('vsplit') + command('enew') + feed('iline 1 some text<cr>line 2 moRe text!<cr>extra line!<cr>extra line!<cr>extra line!<cr>last line ...<cr>') + feed('<esc>gg') + command('windo diffthis') + screen:expect([[ + {1: }{7:line 1 some text }{4:│}{1: }{7:^line 1 some text }| + {1: }{3:line 2 mo}{2:Re text!}{3: }{4:│}{1: }{3:line 2 mo}{2:re text}{3: }| + {1: }{5:extra line! }{4:│}{1: }{6:----------------------}| + {1: }extra line! {4:│}{1: }extra line! | + {1: }extra line! {4:│}{1: }extra line! | + {1: }last line ... {4:│}{1: }last line ... | + {1: } {4:│}{1: } | + {1: }{8:~ }{4:│}{1: }{8:~ }| + {1: }{8:~ }{4:│}{1: }{8:~ }| + {1: }{8:~ }{4:│}{1: }{8:~ }| + {4:[No Name] [+] }{9:[No Name] [+] }| + | + ]]) + feed('jjjjj') + screen:expect([[ + {1: }line 1 some text {4:│}{1: }line 1 some text | + {1: }{3:line 2 mo}{2:Re text!}{3: }{4:│}{1: }{3:line 2 mo}{2:re text}{3: }| + {1: }{5:extra line! }{4:│}{1: }{6:----------------------}| + {1: }extra line! {4:│}{1: }extra line! | + {1: }extra line! {4:│}{1: }extra line! | + {1: }last line ... {4:│}{1: }last line ... | + {1: }{7: }{4:│}{1: }{7:^ }| + {1: }{8:~ }{4:│}{1: }{8:~ }| + {1: }{8:~ }{4:│}{1: }{8:~ }| + {1: }{8:~ }{4:│}{1: }{8:~ }| + {4:[No Name] [+] }{9:[No Name] [+] }| + | + ]]) + end) end) @@ -834,7 +891,7 @@ describe("'winhighlight' highlight", function() {1:a^a }| {2:~ }| {2:~ }| - {11:[No Name] [+] }| + {3:[No Name] [+] }| aa | {0:~ }| {4:[No Name] [+] }| @@ -846,7 +903,7 @@ describe("'winhighlight' highlight", function() {1:^ }| {2:~ }| {2:~ }| - {11:[No Name] }| + {3:[No Name] }| aa | {0:~ }| {4:[No Name] [+] }| @@ -870,7 +927,7 @@ describe("'winhighlight' highlight", function() eq('Vim(set):E474: Invalid argument: winhl=xxx:yyy', exc_exec("set winhl=xxx:yyy")) eq('Normal:Background1', eval('&winhl')) - screen:expect([[ + screen:expect{grid=[[ {1:^ }| {2:~ }| {2:~ }| @@ -879,7 +936,7 @@ describe("'winhighlight' highlight", function() {2:~ }| {2:~ }| | - ]]) + ]], unchanged=true} end) @@ -891,7 +948,7 @@ describe("'winhighlight' highlight", function() {1:a^a }| {2:~ }| {2:~ }| - {11:[No Name] [+] }| + {3:[No Name] [+] }| aa | {0:~ }| {4:[No Name] [+] }| @@ -915,11 +972,11 @@ describe("'winhighlight' highlight", function() {1:^aa }| {2:~ }| {2:~ }| - {11:[No Name] [+] }| + {3:[No Name] [+] }| aa | {0:~ }| {4:[No Name] [+] }| - <f 1 --100%-- col 1 | + | ]]) end) @@ -931,10 +988,10 @@ describe("'winhighlight' highlight", function() {1:^ }| {2:~ }| {2:~ }| - {11:[No Name] }| + {3:[No Name] }| {5: }| {6:~ }| - {12:[No Name] }| + {4:[No Name] }| | ]]) @@ -943,10 +1000,10 @@ describe("'winhighlight' highlight", function() {5: }| {6:~ }| {6:~ }| - {12:[No Name] }| + {4:[No Name] }| {1:^ }| {2:~ }| - {11:[No Name] }| + {3:[No Name] }| | ]]) @@ -955,10 +1012,10 @@ describe("'winhighlight' highlight", function() {1:^ }| {2:~ }| {2:~ }| - {11:[No Name] }| + {3:[No Name] }| {5: }| {6:~ }| - {12:[No Name] }| + {4:[No Name] }| | ]]) end) @@ -974,7 +1031,7 @@ describe("'winhighlight' highlight", function() {3:[No Name] }| {7: }| {8:~ }| - {13:[No Name] }| + {4:[No Name] }| | ]]) @@ -983,7 +1040,7 @@ describe("'winhighlight' highlight", function() {7: }| {8:~ }| {8:~ }| - {13:[No Name] }| + {4:[No Name] }| ^ | {0:~ }| {3:[No Name] }| @@ -997,10 +1054,10 @@ describe("'winhighlight' highlight", function() {7: }| {8:~ }| {8:~ }| - {13:[No Name] }| + {4:[No Name] }| {1:^ }| {2:~ }| - {11:[No Name] }| + {3:[No Name] }| | ]]) @@ -1012,7 +1069,7 @@ describe("'winhighlight' highlight", function() {3:[No Name] }| {1: }| {2:~ }| - {14:[No Name] }| + {4:[No Name] }| | ]]) @@ -1022,10 +1079,10 @@ describe("'winhighlight' highlight", function() {7: }| {8:~ }| {8:~ }| - {13:[No Name] }| + {4:[No Name] }| {1:^ }| {2:~ }| - {11:[No Name] }| + {3:[No Name] }| | ]]) @@ -1037,7 +1094,7 @@ describe("'winhighlight' highlight", function() {3:[No Name] }| {5: }| {6:~ }| - {12:[No Name] }| + {4:[No Name] }| | ]]) end) diff --git a/test/functional/ui/hlstate_spec.lua b/test/functional/ui/hlstate_spec.lua new file mode 100644 index 0000000000..672af5fb22 --- /dev/null +++ b/test/functional/ui/hlstate_spec.lua @@ -0,0 +1,287 @@ +local helpers = require('test.functional.helpers')(after_each) +local Screen = require('test.functional.ui.screen') + +local clear, insert = helpers.clear, helpers.insert +local command = helpers.command +local meths = helpers.meths +local iswin = helpers.iswin +local nvim_dir = helpers.nvim_dir +local thelpers = require('test.functional.terminal.helpers') + +describe('ext_hlstate detailed highlights', function() + local screen + + before_each(function() + clear() + command('syntax on') + screen = Screen.new(40, 8) + screen:attach({ext_hlstate=true}) + end) + + after_each(function() + screen:detach() + end) + + + it('work with combined UI and syntax highlights', function() + insert([[ + these are some lines + with colorful text]]) + meths.buf_add_highlight(0, -1, "String", 0 , 10, 14) + meths.buf_add_highlight(0, -1, "Statement", 1 , 5, -1) + command("/th co") + + screen:expect([[ + these are {1:some} lines | + ^wi{2:th }{4:co}{3:lorful text} | + {5:~ }| + {5:~ }| + {5:~ }| + {5:~ }| + {5:~ }| + {6:search hit BOTTOM, continuing at TOP} | + ]], { + [1] = {{foreground = Screen.colors.Magenta}, + {{hi_name = "Constant", kind = "syntax"}}}, + [2] = {{background = Screen.colors.Yellow}, + {{hi_name = "Search", ui_name = "Search", kind = "ui"}}}, + [3] = {{bold = true, foreground = Screen.colors.Brown}, + {{hi_name = "Statement", kind = "syntax"}}}, + [4] = {{bold = true, background = Screen.colors.Yellow, foreground = Screen.colors.Brown}, {3, 2}}, + [5] = {{bold = true, foreground = Screen.colors.Blue1}, + {{hi_name = "NonText", ui_name = "EndOfBuffer", kind = "ui"}}}, + [6] = {{foreground = Screen.colors.Red}, + {{hi_name = "WarningMsg", ui_name = "WarningMsg", kind = "ui"}}}, + }) + end) + + it('work with cleared UI highlights', function() + screen:set_default_attr_ids({ + [1] = {{}, {{hi_name = "VertSplit", ui_name = "VertSplit", kind = "ui"}}}, + [2] = {{bold = true, foreground = Screen.colors.Blue1}, + {{hi_name = "NonText", ui_name = "EndOfBuffer", kind = "ui"}}}, + [3] = {{bold = true, reverse = true}, + {{hi_name = "StatusLine", ui_name = "StatusLine", kind = "ui"}}} , + [4] = {{reverse = true}, + {{hi_name = "StatusLineNC", ui_name = "StatusLineNC" , kind = "ui"}}}, + [5] = {{}, {{hi_name = "StatusLine", ui_name = "StatusLine", kind = "ui"}}}, + [6] = {{}, {{hi_name = "StatusLineNC", ui_name = "StatusLineNC", kind = "ui"}}}, + }) + command("hi clear VertSplit") + command("vsplit") + + screen:expect([[ + ^ {1:│} | + {2:~ }{1:│}{2:~ }| + {2:~ }{1:│}{2:~ }| + {2:~ }{1:│}{2:~ }| + {2:~ }{1:│}{2:~ }| + {2:~ }{1:│}{2:~ }| + {3:[No Name] }{4:[No Name] }| + | + ]]) + + command("hi clear StatusLine | hi clear StatuslineNC") + screen:expect([[ + ^ {1:│} | + {2:~ }{1:│}{2:~ }| + {2:~ }{1:│}{2:~ }| + {2:~ }{1:│}{2:~ }| + {2:~ }{1:│}{2:~ }| + {2:~ }{1:│}{2:~ }| + {5:[No Name] }{6:[No Name] }| + | + ]]) + + -- redrawing is done even if visible highlights didn't change + command("wincmd w") + screen:expect([[ + {1:│}^ | + {2:~ }{1:│}{2:~ }| + {2:~ }{1:│}{2:~ }| + {2:~ }{1:│}{2:~ }| + {2:~ }{1:│}{2:~ }| + {2:~ }{1:│}{2:~ }| + {6:[No Name] }{5:[No Name] }| + | + ]]) + + end) + + it("work with window-local highlights", function() + screen:set_default_attr_ids({ + [1] = {{foreground = Screen.colors.Brown}, {{hi_name = "LineNr", ui_name = "LineNr", kind = "ui"}}}, + [2] = {{bold = true, foreground = Screen.colors.Blue1}, {{hi_name = "NonText", ui_name = "EndOfBuffer", kind = "ui"}}}, + [3] = {{bold = true, reverse = true}, {{hi_name = "StatusLine", ui_name = "StatusLine", kind = "ui"}}}, + [4] = {{reverse = true}, {{hi_name = "StatusLineNC", ui_name = "StatusLineNC", kind = "ui"}}}, + [5] = {{background = Screen.colors.Red, foreground = Screen.colors.Grey100}, {{hi_name = "ErrorMsg", ui_name = "LineNr", kind = "ui"}}}, + [6] = {{bold = true, reverse = true}, {{hi_name = "MsgSeparator", ui_name = "Normal", kind = "ui"}}}, + [7] = {{foreground = Screen.colors.Brown, bold = true, reverse = true}, {6, 1}}, + [8] = {{foreground = Screen.colors.Blue1, bold = true, reverse = true}, {6, 2}}, + [9] = {{bold = true, foreground = Screen.colors.Brown}, {{hi_name = "Statement", ui_name = "NormalNC", kind = "ui"}}}, + [10] = {{bold = true, foreground = Screen.colors.Brown}, {9, 1}}, + [11] = {{bold = true, foreground = Screen.colors.Blue1}, {9, 2}} + }) + + command("set number") + command("split") + -- NormalNC is not applied if not set, to avoid spurious redraws + screen:expect([[ + {1: 1 }^ | + {2:~ }| + {2:~ }| + {3:[No Name] }| + {1: 1 } | + {2:~ }| + {4:[No Name] }| + | + ]]) + + command("set winhl=LineNr:ErrorMsg") + screen:expect([[ + {5: 1 }^ | + {2:~ }| + {2:~ }| + {3:[No Name] }| + {1: 1 } | + {2:~ }| + {4:[No Name] }| + | + ]]) + + command("set winhl=Normal:MsgSeparator,NormalNC:Statement") + screen:expect([[ + {7: 1 }{6:^ }| + {8:~ }| + {8:~ }| + {3:[No Name] }| + {1: 1 } | + {2:~ }| + {4:[No Name] }| + | + ]]) + + command("wincmd w") + screen:expect([[ + {10: 1 }{9: }| + {11:~ }| + {11:~ }| + {4:[No Name] }| + {1: 1 }^ | + {2:~ }| + {3:[No Name] }| + | + ]]) + end) + + it("work with :terminal", function() + screen:set_default_attr_ids({ + [1] = {{}, {{hi_name = "TermCursorNC", ui_name = "TermCursorNC", kind = "ui"}}}, + [2] = {{special = Screen.colors.Grey0, foreground = 52479}, {{kind = "term"}}}, + [3] = {{special = Screen.colors.Grey0, bold = true, foreground = 52479}, {{kind = "term"}}}, + [4] = {{special = Screen.colors.Grey0, foreground = 52479}, {2, 1}}, + [5] = {{special = Screen.colors.Grey0, foreground = 4259839}, {{kind = "term"}}}, + [6] = {{special = Screen.colors.Grey0, foreground = 4259839}, {5, 1}}, + }) + command('enew | call termopen(["'..nvim_dir..'/tty-test"])') + screen:expect([[ + ^tty ready | + {1: } | + | + | + | + | + | + | + ]]) + + thelpers.feed_data('x ') + thelpers.set_fg(45) + thelpers.feed_data('y ') + thelpers.set_bold() + thelpers.feed_data('z\n') + -- TODO(bfredl): check if this distinction makes sense + if iswin() then + screen:expect([[ + ^tty ready | + x {5:y z} | + {1: } | + | + | + | + | + | + ]]) + else + screen:expect([[ + ^tty ready | + x {2:y }{3:z} | + {1: } | + | + | + | + | + | + ]]) + end + + thelpers.feed_termcode("[A") + thelpers.feed_termcode("[2C") + if iswin() then + screen:expect([[ + ^tty ready | + x {6:y}{5: z} | + | + | + | + | + | + | + ]]) + else + screen:expect([[ + ^tty ready | + x {4:y}{2: }{3:z} | + | + | + | + | + | + | + ]]) + end + end) + + it("can use independent cterm and rgb colors", function() + -- tell test module to save all attributes (doesn't change nvim options) + screen:set_hlstate_cterm(true) + + screen:set_default_attr_ids({ + [1] = {{bold = true, foreground = Screen.colors.Blue1}, {foreground = 12}, {{hi_name = "NonText", ui_name = "EndOfBuffer", kind = "ui"}}}, + [2] = {{reverse = true, foreground = Screen.colors.Red}, {foreground = 10, italic=true}, {{hi_name = "NonText", ui_name = "EndOfBuffer", kind = "ui"}}}, + }) + screen:expect([[ + ^ | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + | + ]]) + + command("hi NonText guifg=Red gui=reverse ctermfg=Green cterm=italic") + screen:expect([[ + ^ | + {2:~ }| + {2:~ }| + {2:~ }| + {2:~ }| + {2:~ }| + {2:~ }| + | + ]]) + + end) +end) diff --git a/test/functional/ui/inccommand_spec.lua b/test/functional/ui/inccommand_spec.lua index 9cc697a4b6..bb6cb543ed 100644 --- a/test/functional/ui/inccommand_spec.lua +++ b/test/functional/ui/inccommand_spec.lua @@ -495,6 +495,18 @@ describe(":substitute, 'inccommand' preserves undo", function() for _, case in pairs(cases) do clear() common_setup(screen, case, default_text) + screen:expect([[ + Inc substitution on | + two lines | + ^ | + {15:~ }| + {15:~ }| + {15:~ }| + {15:~ }| + {15:~ }| + {15:~ }| + | + ]]) feed_command("set undolevels=1") feed("1G0") @@ -745,20 +757,35 @@ describe(":substitute, inccommand=split", function() it("shows preview when cmd modifiers are present", function() -- one modifier feed(':keeppatterns %s/tw/to') - screen:expect([[{12:to}o lines]], nil, nil, nil, true) + screen:expect{any=[[{12:to}o lines]]} feed('<Esc>') - screen:expect([[two lines]], nil, nil, nil, true) + screen:expect{any=[[two lines]]} -- multiple modifiers feed(':keeppatterns silent %s/tw/to') - screen:expect([[{12:to}o lines]], nil, nil, nil, true) + screen:expect{any=[[{12:to}o lines]]} feed('<Esc>') - screen:expect([[two lines]], nil, nil, nil, true) + screen:expect{any=[[two lines]]} -- non-modifier prefix feed(':silent tabedit %s/tw/to') - screen:expect([[two lines]], nil, nil, nil, true) - feed('<Esc>') + screen:expect([[ + Inc substitution on | + two lines | + Inc substitution on | + two lines | + | + {15:~ }| + {15:~ }| + {15:~ }| + {15:~ }| + {15:~ }| + {15:~ }| + {15:~ }| + {15:~ }| + {15:~ }| + :silent tabedit %s/tw/to^ | + ]]) end) it('shows split window when typing the pattern', function() @@ -866,7 +893,6 @@ describe(":substitute, inccommand=split", function() it('does not show split window for :s/', function() feed("2gg") feed(":s/tw") - screen:sleep(1) screen:expect([[ Inc substitution on | {12:tw}o lines | @@ -1222,20 +1248,30 @@ describe("inccommand=nosplit", function() it("shows preview when cmd modifiers are present", function() -- one modifier feed(':keeppatterns %s/tw/to') - screen:expect([[{12:to}o lines]], nil, nil, nil, true) + screen:expect{any=[[{12:to}o lines]]} feed('<Esc>') - screen:expect([[two lines]], nil, nil, nil, true) + screen:expect{any=[[two lines]]} -- multiple modifiers feed(':keeppatterns silent %s/tw/to') - screen:expect([[{12:to}o lines]], nil, nil, nil, true) + screen:expect{any=[[{12:to}o lines]]} feed('<Esc>') - screen:expect([[two lines]], nil, nil, nil, true) + screen:expect{any=[[two lines]]} -- non-modifier prefix feed(':silent tabedit %s/tw/to') - screen:expect([[two lines]], nil, nil, nil, true) - feed('<Esc>') + screen:expect([[ + two lines | + Inc substitution on | + two lines | + | + {15:~ }| + {15:~ }| + {15:~ }| + {15:~ }| + :silent tabedit %s/t| + w/to^ | + ]]) end) it("does not show window after toggling :set inccommand", function() @@ -1503,7 +1539,7 @@ describe("'inccommand' and :cnoremap", function() end end) - it('does not work with a failing mapping', function() + it('still works with a broken mapping', function() for _, case in pairs(cases) do refresh(case) feed_command("cnoremap <expr> x execute('bwipeout!')[-1].'x'") @@ -1512,7 +1548,10 @@ describe("'inccommand' and :cnoremap", function() -- error thrown b/c of the mapping neq(nil, eval('v:errmsg'):find('^E523:')) - expect(default_text) + expect([[ + Inc substitution on + toxo lines + ]]) end end) @@ -2473,7 +2512,7 @@ describe(":substitute", function() end) it(':substitute with inccommand during :terminal activity', function() - retry(2, nil, function() + retry(2, 40000, function() local screen = Screen.new(30,15) clear() diff --git a/test/functional/ui/mode_spec.lua b/test/functional/ui/mode_spec.lua index f0cedfeeb5..f6b3c1c3c9 100644 --- a/test/functional/ui/mode_spec.lua +++ b/test/functional/ui/mode_spec.lua @@ -21,207 +21,169 @@ describe('ui mode_change event', function() end) it('works in normal mode', function() - screen:expect([[ + screen:expect{grid=[[ ^ | {0:~ }| {0:~ }| | - ]],nil,nil,function () - eq("normal", screen.mode) - end) + ]], mode="normal"} feed('d') - screen:expect([[ + screen:expect{grid=[[ ^ | {0:~ }| {0:~ }| | - ]],nil,nil,function () - eq("operator", screen.mode) - end) + ]], mode="operator"} feed('<esc>') - screen:expect([[ + screen:expect{grid=[[ ^ | {0:~ }| {0:~ }| | - ]],nil,nil,function () - eq("normal", screen.mode) - end) + ]], mode="normal"} end) it('works in insert mode', function() feed('i') - screen:expect([[ + screen:expect{grid=[[ ^ | {0:~ }| {0:~ }| {2:-- INSERT --} | - ]],nil,nil,function () - eq("insert", screen.mode) - end) + ]], mode="insert"} feed('word<esc>') - screen:expect([[ + screen:expect{grid=[[ wor^d | {0:~ }| {0:~ }| | - ]], nil, nil, function () - eq("normal", screen.mode) - end) + ]], mode="normal"} command("set showmatch") eq(eval('&matchtime'), 5) -- tenths of seconds feed('a(stuff') - screen:expect([[ + screen:expect{grid=[[ word(stuff^ | {0:~ }| {0:~ }| {2:-- INSERT --} | - ]], nil, nil, function () - eq("insert", screen.mode) - end) + ]], mode="insert"} feed(')') - screen:expect([[ + screen:expect{grid=[[ word^(stuff) | {0:~ }| {0:~ }| {2:-- INSERT --} | - ]], nil, nil, function () - eq("showmatch", screen.mode) - end) + ]], mode="showmatch"} screen:sleep(400) - screen:expect([[ + screen:expect{grid=[[ word(stuff)^ | {0:~ }| {0:~ }| {2:-- INSERT --} | - ]], nil, nil, function () - eq("insert", screen.mode) - end) + ]], mode="insert"} end) it('works in replace mode', function() feed('R') - screen:expect([[ + screen:expect{grid=[[ ^ | {0:~ }| {0:~ }| {2:-- REPLACE --} | - ]], nil, nil, function () - eq("replace", screen.mode) - end) + ]], mode="replace"} feed('word<esc>') - screen:expect([[ + screen:expect{grid=[[ wor^d | {0:~ }| {0:~ }| | - ]], nil, nil, function () - eq("normal", screen.mode) - end) + ]], mode="normal"} end) it('works in cmdline mode', function() feed(':') - screen:expect([[ + screen:expect{grid=[[ | {0:~ }| {0:~ }| :^ | - ]],nil,nil,function () - eq("cmdline_normal", screen.mode) - end) + ]], mode="cmdline_normal"} feed('x<left>') - screen:expect([[ + screen:expect{grid=[[ | {0:~ }| {0:~ }| :^x | - ]],nil,nil,function () - eq("cmdline_insert", screen.mode) - end) + ]], mode="cmdline_insert"} feed('<insert>') - screen:expect([[ + screen:expect{grid=[[ | {0:~ }| {0:~ }| :^x | - ]],nil,nil,function () - eq("cmdline_replace", screen.mode) - end) + ]], mode="cmdline_replace"} feed('<right>') - screen:expect([[ + screen:expect{grid=[[ | {0:~ }| {0:~ }| :x^ | - ]],nil,nil,function () - eq("cmdline_normal", screen.mode) - end) + ]], mode="cmdline_normal"} feed('<esc>') - screen:expect([[ + screen:expect{grid=[[ ^ | {0:~ }| {0:~ }| | - ]],nil,nil,function () - eq("normal", screen.mode) - end) + ]], mode="normal"} end) - it('works in visal mode', function() + it('works in visual mode', function() insert("text") feed('v') - screen:expect([[ + screen:expect{grid=[[ tex^t | {0:~ }| {0:~ }| {2:-- VISUAL --} | - ]],nil,nil,function () - eq("visual", screen.mode) - end) + ]], mode="visual"} feed('<esc>') - screen:expect([[ + screen:expect{grid=[[ tex^t | {0:~ }| {0:~ }| | - ]],nil,nil,function () - eq("normal", screen.mode) - end) + ]], mode="normal"} command('set selection=exclusive') feed('v') - screen:expect([[ + screen:expect{grid=[[ tex^t | {0:~ }| {0:~ }| {2:-- VISUAL --} | - ]],nil,nil,function () - eq("visual_select", screen.mode) - end) + ]], mode="visual_select"} feed('<esc>') - screen:expect([[ + screen:expect{grid=[[ tex^t | {0:~ }| {0:~ }| | - ]],nil,nil,function () - eq("normal", screen.mode) - end) + ]], mode="normal"} end) end) diff --git a/test/functional/ui/mouse_spec.lua b/test/functional/ui/mouse_spec.lua index debd324977..c531f838c1 100644 --- a/test/functional/ui/mouse_spec.lua +++ b/test/functional/ui/mouse_spec.lua @@ -168,13 +168,13 @@ describe('ui/mouse/input', function() | ]]) feed('<LeftMouse><11,0>') - screen:expect([[ + screen:expect{grid=[[ {tab: + foo }{sel: + bar }{fill: }{tab:X}| this is ba^r | {0:~ }| {0:~ }| | - ]]) + ]], unchanged=true} feed('<LeftDrag><6,0>') screen:expect([[ {sel: + bar }{tab: + foo }{fill: }{tab:X}| @@ -236,13 +236,13 @@ describe('ui/mouse/input', function() | ]]) feed('<LeftDrag><4,1>') - screen:expect([[ + screen:expect{grid=[[ {sel: + foo }{tab: + bar }{fill: }{tab:X}| this is fo^o | {0:~ }| {0:~ }| | - ]]) + ]], unchanged=true} feed('<LeftDrag><14,1>') screen:expect([[ {tab: + bar }{sel: + foo }{fill: }{tab:X}| @@ -254,13 +254,6 @@ describe('ui/mouse/input', function() end) it('out of tabline to the left moves tab left', function() - if helpers.skip_fragile(pending, - os.getenv("TRAVIS") and (helpers.os_name() == "osx" - or os.getenv("CLANG_SANITIZER") == "ASAN_UBSAN")) -- #4874 - then - return - end - feed_command('%delete') insert('this is foo') feed_command('silent file foo | tabnew | file bar') @@ -273,21 +266,21 @@ describe('ui/mouse/input', function() | ]]) feed('<LeftMouse><11,0>') - screen:expect([[ + screen:expect{grid=[[ {tab: + foo }{sel: + bar }{fill: }{tab:X}| this is ba^r | {0:~ }| {0:~ }| | - ]]) + ]], unchanged=true} feed('<LeftDrag><11,1>') - screen:expect([[ + screen:expect{grid=[[ {tab: + foo }{sel: + bar }{fill: }{tab:X}| this is ba^r | {0:~ }| {0:~ }| | - ]]) + ]], unchanged=true} feed('<LeftDrag><6,1>') screen:expect([[ {sel: + bar }{tab: + foo }{fill: }{tab:X}| @@ -319,13 +312,13 @@ describe('ui/mouse/input', function() | ]]) feed('<LeftDrag><4,1>') - screen:expect([[ + screen:expect{grid=[[ {sel: + foo }{tab: + bar }{fill: }{tab:X}| this is fo^o | {0:~ }| {0:~ }| | - ]]) + ]], unchanged=true} feed('<LeftDrag><7,1>') screen:expect([[ {tab: + bar }{sel: + foo }{fill: }{tab:X}| @@ -537,7 +530,7 @@ describe('ui/mouse/input', function() mouse | support and selectio^n | {0:~ }| - | + :tabprevious | ]]) feed('<LeftMouse><10,0><LeftRelease>') -- go to second tab helpers.wait() @@ -547,7 +540,7 @@ describe('ui/mouse/input', function() ^this is bar | {0:~ }| {0:~ }| - | + :tabprevious | ]]) feed('<LeftDrag><4,1>') screen:expect([[ diff --git a/test/functional/ui/options_spec.lua b/test/functional/ui/options_spec.lua index 62b08c0967..32e8faf7d3 100644 --- a/test/functional/ui/options_spec.lua +++ b/test/functional/ui/options_spec.lua @@ -1,79 +1,85 @@ +local global_helpers = require('test.helpers') local helpers = require('test.functional.helpers')(after_each) local Screen = require('test.functional.ui.screen') local clear = helpers.clear local command = helpers.command local eq = helpers.eq +local shallowcopy = global_helpers.shallowcopy describe('ui receives option updates', function() local screen - before_each(function() - clear() + local function reset(opts, ...) + local defaults = { + ambiwidth='single', + arabicshape=true, + emoji=true, + guifont='', + guifontset='', + guifontwide='', + linespace=0, + showtabline=1, + termguicolors=false, + ext_cmdline=false, + ext_popupmenu=false, + ext_tabline=false, + ext_wildmenu=false, + ext_linegrid=false, + ext_hlstate=false, + } + + clear(...) screen = Screen.new(20,5) - end) + screen:attach(opts) + -- NB: UI test suite can be run in both "linegrid" and legacy grid mode. + -- In both cases check that the received value is the one requested. + defaults.ext_linegrid = screen._options.ext_linegrid or false + return defaults + end after_each(function() screen:detach() end) - local defaults = { - ambiwidth='single', - arabicshape=true, - emoji=true, - guifont='', - guifontset='', - guifontwide='', - linespace=0, - showtabline=1, - termguicolors=false, - ext_cmdline=false, - ext_popupmenu=false, - ext_tabline=false, - ext_wildmenu=false, - } - it("for defaults", function() - screen:attach() + local expected = reset() screen:expect(function() - eq(defaults, screen.options) + eq(expected, screen.options) end) end) it("when setting options", function() - screen:attach() - local changed = {} - for k,v in pairs(defaults) do - changed[k] = v - end + local expected = reset() + local defaults = shallowcopy(expected) command("set termguicolors") - changed.termguicolors = true + expected.termguicolors = true screen:expect(function() - eq(changed, screen.options) + eq(expected, screen.options) end) command("set guifont=Comic\\ Sans") - changed.guifont = "Comic Sans" + expected.guifont = "Comic Sans" screen:expect(function() - eq(changed, screen.options) + eq(expected, screen.options) end) command("set showtabline=0") - changed.showtabline = 0 + expected.showtabline = 0 screen:expect(function() - eq(changed, screen.options) + eq(expected, screen.options) end) command("set linespace=13") - changed.linespace = 13 + expected.linespace = 13 screen:expect(function() - eq(changed, screen.options) + eq(expected, screen.options) end) command("set linespace=-11") - changed.linespace = -11 + expected.linespace = -11 screen:expect(function() - eq(changed, screen.options) + eq(expected, screen.options) end) command("set all&") @@ -83,28 +89,35 @@ describe('ui receives option updates', function() end) it('with UI extensions', function() - local changed = {} - for k,v in pairs(defaults) do - changed[k] = v - end - - screen:attach({ext_cmdline=true, ext_wildmenu=true}) - changed.ext_cmdline = true - changed.ext_wildmenu = true + local expected = reset({ext_cmdline=true, ext_wildmenu=true}) + + expected.ext_cmdline = true + expected.ext_wildmenu = true screen:expect(function() - eq(changed, screen.options) + eq(expected, screen.options) end) screen:set_option('ext_popupmenu', true) - changed.ext_popupmenu = true + expected.ext_popupmenu = true screen:expect(function() - eq(changed, screen.options) + eq(expected, screen.options) end) screen:set_option('ext_wildmenu', false) - changed.ext_wildmenu = false + expected.ext_wildmenu = false screen:expect(function() - eq(changed, screen.options) + eq(expected, screen.options) end) end) + + local function startup_test(headless) + local expected = reset(nil,{headless=headless,args={'--cmd', 'set guifont=Comic\\ Sans\\ 12'}}) + expected.guifont = "Comic Sans 12" + screen:expect(function() + eq(expected, screen.options) + end) + end + + it('from startup options with --headless', function() startup_test(true) end) + it('from startup options with --embed', function() startup_test(false) end) end) diff --git a/test/functional/ui/output_spec.lua b/test/functional/ui/output_spec.lua index 93d8965cb1..1850d436ac 100644 --- a/test/functional/ui/output_spec.lua +++ b/test/functional/ui/output_spec.lua @@ -51,25 +51,20 @@ describe("shell command :!", function() end) it("throttles shell-command output greater than ~10KB", function() - if os.getenv("TRAVIS") and helpers.os_name() == "osx" then - pending("[Unreliable on Travis macOS.]", function() end) - return - end - - screen.timeout = 20000 -- Avoid false failure on slow systems. child_session.feed_data( - ":!for i in $(seq 2 3000); do echo XXXXXXXXXX $i; done\n") + ":!for i in $(seq 2 30000); do echo XXXXXXXXXX $i; done\n") -- If we observe any line starting with a dot, then throttling occurred. - screen:expect("\n.", nil, nil, nil, true) + -- Avoid false failure on slow systems. + screen:expect{any="\n%.", timeout=20000} -- Final chunk of output should always be displayed, never skipped. -- (Throttling is non-deterministic, this test is merely a sanity check.) screen:expect([[ - XXXXXXXXXX 2997 | - XXXXXXXXXX 2998 | - XXXXXXXXXX 2999 | - XXXXXXXXXX 3000 | + XXXXXXXXXX 29997 | + XXXXXXXXXX 29998 | + XXXXXXXXXX 29999 | + XXXXXXXXXX 30000 | | {10:Press ENTER or type command to continue}{1: } | {3:-- TERMINAL --} | @@ -92,7 +87,7 @@ describe("shell command :!", function() eq(2, eval('1+1')) -- Still alive? end) - it([[handles control codes]], function() + it('handles control codes', function() if iswin() then pending('missing printf', function() end) return @@ -112,14 +107,14 @@ describe("shell command :!", function() -- Print BELL control code. #4338 screen.bell = false feed([[:!printf '\007\007\007\007text'<CR>]]) - screen:expect([[ + screen:expect{grid=[[ ~ | :!printf '\007\007\007\007text' | text | Press ENTER or type command to continue^ | - ]], nil, nil, function() + ]], condition=function() eq(true, screen.bell) - end) + end} feed([[<CR>]]) -- Print BS control code. feed([[:echo system('printf ''\010\n''')<CR>]]) @@ -188,7 +183,7 @@ describe("shell command :!", function() it('handles binary and multibyte data', function() feed_command('!cat test/functional/fixtures/shell_data.txt') screen.bell = false - screen:expect([[ + screen:expect{grid=[[ | {1:~ }| {4: }| @@ -199,9 +194,9 @@ describe("shell command :!", function() t {2:<ff>} | | {3:Press ENTER or type command to continue}^ | - ]], nil, nil, function() + ]], condition=function() eq(true, screen.bell) - end) + end} end) it('handles multibyte sequences split over buffer boundaries', function() diff --git a/test/functional/ui/popupmenu_spec.lua b/test/functional/ui/popupmenu_spec.lua new file mode 100644 index 0000000000..606c7c1e26 --- /dev/null +++ b/test/functional/ui/popupmenu_spec.lua @@ -0,0 +1,260 @@ +local helpers = require('test.functional.helpers')(after_each) +local Screen = require('test.functional.ui.screen') +local clear, feed = helpers.clear, helpers.feed +local source = helpers.source + +describe('ui/ext_popupmenu', function() + local screen + before_each(function() + clear() + screen = Screen.new(60, 8) + screen:attach({rgb=true, ext_popupmenu=true}) + screen:set_default_attr_ids({ + [1] = {bold=true, foreground=Screen.colors.Blue}, + [2] = {bold = true}, + [3] = {reverse = true}, + [4] = {bold = true, reverse = true}, + [5] = {bold = true, foreground = Screen.colors.SeaGreen} + }) + end) + + it('works', function() + source([[ + function! TestComplete() abort + call complete(1, ['foo', 'bar', 'spam']) + return '' + endfunction + ]]) + local expected = { + {'foo', '', '', ''}, + {'bar', '', '', ''}, + {'spam', '', '', ''}, + } + feed('o<C-r>=TestComplete()<CR>') + screen:expect{grid=[[ + | + foo^ | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {2:-- INSERT --} | + ]], popupmenu={ + items=expected, + pos=0, + anchor={1,0}, + }} + + feed('<c-p>') + screen:expect{grid=[[ + | + ^ | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {2:-- INSERT --} | + ]], popupmenu={ + items=expected, + pos=-1, + anchor={1,0}, + }} + + -- down moves the selection in the menu, but does not insert anything + feed('<down><down>') + screen:expect{grid=[[ + | + ^ | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {2:-- INSERT --} | + ]], popupmenu={ + items=expected, + pos=1, + anchor={1,0}, + }} + + feed('<cr>') + screen:expect{grid=[[ + | + bar^ | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {2:-- INSERT --} | + ]]} + end) +end) + +describe('popup placement', function() + local screen + before_each(function() + clear() + screen = Screen.new(32, 20) + screen:attach() + screen:set_default_attr_ids({ + -- popup selected item / scrollbar track + ['s'] = {background = Screen.colors.WebGray}, + -- popup non-selected item + ['n'] = {background = Screen.colors.LightMagenta}, + -- popup scrollbar knob + ['c'] = {background = Screen.colors.Grey0}, + [1] = {bold = true, foreground = Screen.colors.Blue}, + [2] = {bold = true}, + [3] = {reverse = true}, + [4] = {bold = true, reverse = true}, + [5] = {bold = true, foreground = Screen.colors.SeaGreen} + }) + end) + + it('works with preview-window above', function() + feed(':ped<CR><c-w>4+') + feed('iaa bb cc dd ee ff gg hh ii jj<cr>') + feed('<c-x><c-n>') + screen:expect([[ + aa bb cc dd ee ff gg hh ii jj | + aa | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {3:[No Name] [Preview][+] }| + aa bb cc dd ee ff gg hh ii jj | + aa^ | + {s:aa }{c: }{1: }| + {n:bb }{c: }{1: }| + {n:cc }{c: }{1: }| + {n:dd }{c: }{1: }| + {n:ee }{c: }{1: }| + {n:ff }{c: }{1: }| + {n:gg }{s: }{1: }| + {n:hh }{s: }{4: }| + {2:-- }{5:match 1 of 10} | + ]]) + end) + + it('works with preview-window below', function() + feed(':ped<CR><c-w>4+<c-w>r') + feed('iaa bb cc dd ee ff gg hh ii jj<cr>') + feed('<c-x><c-n>') + screen:expect([[ + aa bb cc dd ee ff gg hh ii jj | + aa^ | + {s:aa }{c: }{1: }| + {n:bb }{c: }{1: }| + {n:cc }{c: }{1: }| + {n:dd }{c: }{1: }| + {n:ee }{c: }{1: }| + {n:ff }{c: }{1: }| + {n:gg }{s: }{1: }| + {n:hh }{s: }{4: }| + aa bb cc dd ee ff gg hh ii jj | + aa | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {3:[No Name] [Preview][+] }| + {2:-- }{5:match 1 of 10} | + ]]) + end) + + it('works with preview-window above and tall and inverted', function() + feed(':ped<CR><c-w>8+') + feed('iaa<cr>bb<cr>cc<cr>dd<cr>ee<cr>') + feed('ff<cr>gg<cr>hh<cr>ii<cr>jj<cr>') + feed('kk<cr>ll<cr>mm<cr>nn<cr>oo<cr>') + feed('<c-x><c-n>') + screen:expect([[ + aa | + bb | + cc | + dd | + {s:aa }{c: }{3:ew][+] }| + {n:bb }{c: } | + {n:cc }{c: } | + {n:dd }{c: } | + {n:ee }{c: } | + {n:ff }{c: } | + {n:gg }{c: } | + {n:hh }{c: } | + {n:ii }{c: } | + {n:jj }{c: } | + {n:kk }{c: } | + {n:ll }{s: } | + {n:mm }{s: } | + aa^ | + {4:[No Name] [+] }| + {2:-- }{5:match 1 of 15} | + ]]) + end) + + it('works with preview-window above and short and inverted', function() + feed(':ped<CR><c-w>4+') + feed('iaa<cr>bb<cr>cc<cr>dd<cr>ee<cr>') + feed('ff<cr>gg<cr>hh<cr>ii<cr>jj<cr>') + feed('<c-x><c-n>') + screen:expect([[ + aa | + bb | + cc | + dd | + ee | + ff | + gg | + {s:aa } | + {n:bb }{3:iew][+] }| + {n:cc } | + {n:dd } | + {n:ee } | + {n:ff } | + {n:gg } | + {n:hh } | + {n:ii } | + {n:jj } | + aa^ | + {4:[No Name] [+] }| + {2:-- }{5:match 1 of 10} | + ]]) + end) + + it('works with preview-window below and inverted', function() + feed(':ped<CR><c-w>4+<c-w>r') + feed('iaa<cr>bb<cr>cc<cr>dd<cr>ee<cr>') + feed('ff<cr>gg<cr>hh<cr>ii<cr>jj<cr>') + feed('<c-x><c-n>') + screen:expect([[ + {s:aa }{c: } | + {n:bb }{c: } | + {n:cc }{c: } | + {n:dd }{c: } | + {n:ee }{c: } | + {n:ff }{c: } | + {n:gg }{s: } | + {n:hh }{s: } | + aa^ | + {4:[No Name] [+] }| + aa | + bb | + cc | + dd | + ee | + ff | + gg | + hh | + {3:[No Name] [Preview][+] }| + {2:-- }{5:match 1 of 10} | + ]]) + end) +end) diff --git a/test/functional/ui/screen.lua b/test/functional/ui/screen.lua index 7607131e9b..af036913d8 100644 --- a/test/functional/ui/screen.lua +++ b/test/functional/ui/screen.lua @@ -71,24 +71,35 @@ -- To help write screen tests, see Screen:snapshot_util(). -- To debug screen tests, see Screen:redraw_debug(). +local global_helpers = require('test.helpers') +local shallowcopy = global_helpers.shallowcopy local helpers = require('test.functional.helpers')(nil) local request, run, uimeths = helpers.request, helpers.run, helpers.uimeths +local eq = helpers.eq local dedent = helpers.dedent +local inspect = require('inspect') + +local function isempty(v) + return type(v) == 'table' and next(v) == nil +end + local Screen = {} Screen.__index = Screen local debug_screen -local default_screen_timeout = 3500 +local default_timeout_factor = 1 if os.getenv('VALGRIND') then - default_screen_timeout = default_screen_timeout * 3 + default_timeout_factor = default_timeout_factor * 3 end if os.getenv('CI') then - default_screen_timeout = default_screen_timeout * 3 + default_timeout_factor = default_timeout_factor * 3 end +local default_screen_timeout = default_timeout_factor * 3500 + do local spawn, nvim_prog = helpers.spawn, helpers.nvim_prog local session = spawn({nvim_prog, '-u', 'NONE', '-i', 'NONE', '-N', '--embed'}) @@ -138,16 +149,26 @@ function Screen.new(width, height) suspended = false, mode = 'normal', options = {}, + popupmenu = nil, + cmdline = {}, + cmdline_block = {}, + wildmenu_items = nil, + wildmenu_selected = nil, _default_attr_ids = nil, _default_attr_ignore = nil, _mouse_enabled = true, _attrs = {}, + _hl_info = {}, + _attr_table = {[0]={{},{}}}, + _clear_attrs = {}, + _new_attrs = false, + _width = width, + _height = height, _cursor = { row = 1, col = 1 }, _busy = false }, Screen) - self:_handle_resize(width, height) return self end @@ -159,11 +180,26 @@ function Screen:set_default_attr_ignore(attr_ignore) self._default_attr_ignore = attr_ignore end +function Screen:set_hlstate_cterm(val) + self._hlstate_cterm = val +end + function Screen:attach(options) if options == nil then - options = {rgb=true} + options = {} end + if options.ext_linegrid == nil then + options.ext_linegrid = true + end + self._options = options + self._clear_attrs = (options.ext_linegrid and {{},{}}) or {} + self:_handle_resize(self._width, self._height) uimeths.attach(self._width, self._height, options) + if self._options.rgb == nil then + -- nvim defaults to rgb=true internally, + -- simplify test code by doing the same. + self._options.rgb = true + end end function Screen:detach() @@ -176,41 +212,123 @@ end function Screen:set_option(option, value) uimeths.set_option(option, value) + self._options[option] = value end --- Asserts that `expected` eventually matches the screen state. +-- canonical order of ext keys, used to generate asserts +local ext_keys = { + 'popupmenu', 'cmdline', 'cmdline_block', 'wildmenu_items', 'wildmenu_pos' +} + +-- Asserts that the screen state eventually matches an expected state +-- +-- This function can either be called with the positional forms +-- +-- screen:expect(grid, [attr_ids, attr_ignore]) +-- screen:expect(condition) +-- +-- or to use additional arguments (or grid and condition at the same time) +-- the keyword form has to be used: -- --- expected: Expected screen state (string). Each line represents a screen +-- screen:expect{grid=[[...]], cmdline={...}, condition=function() ... end} +-- +-- +-- grid: Expected screen state (string). Each line represents a screen -- row. Last character of each row (typically "|") is stripped. -- Common indentation is stripped. --- Used as `condition` if NOT a string; must be the ONLY arg then. -- attr_ids: Expected text attributes. Screen rows are transformed according -- to this table, as follows: each substring S composed of -- characters having the same attributes will be substituted by -- "{K:S}", where K is a key in `attr_ids`. Any unexpected -- attributes in the final state are an error. --- attr_ignore: Ignored text attributes, or `true` to ignore all. --- condition: Function asserting some arbitrary condition. --- any: true: Succeed if `expected` matches ANY screen line(s). --- false (default): `expected` must match screen exactly. -function Screen:expect(expected, attr_ids, attr_ignore, condition, any) +-- Use screen:set_default_attr_ids() to define attributes for many +-- expect() calls. +-- attr_ignore: Ignored text attributes, or `true` to ignore all. By default +-- nothing is ignored. +-- condition: Function asserting some arbitrary condition. Return value is +-- ignored, throw an error (use eq() or similar) to signal failure. +-- any: Lua pattern string expected to match a screen line. NB: the +-- following chars are magic characters +-- ( ) . % + - * ? [ ^ $ +-- and must be escaped with a preceding % for a literal match. +-- mode: Expected mode as signaled by "mode_change" event +-- unchanged: Test that the screen state is unchanged since the previous +-- expect(...). Any flush event resulting in a different state is +-- considered an error. Not observing any events until timeout +-- is acceptable. +-- intermediate:Test that the final state is the same as the previous expect, +-- but expect an intermediate state that is different. If possible +-- it is better to use an explicit screen:expect(...) for this +-- intermediate state. +-- reset: Reset the state internal to the test Screen before starting to +-- receive updates. This should be used after command("redraw!") +-- or some other mechanism that will invoke "redraw!", to check +-- that all screen state is transmitted again. This includes +-- state related to ext_ features as mentioned below. +-- timeout: maximum time that will be waited until the expected state is +-- seen (or maximum time to observe an incorrect change when +-- `unchanged` flag is used) +-- +-- The following keys should be used to expect the state of various ext_ +-- features. Note that an absent key will assert that the item is currently +-- NOT present on the screen, also when positional form is used. +-- +-- popupmenu: Expected ext_popupmenu state, +-- cmdline: Expected ext_cmdline state, as an array of cmdlines of +-- different level. +-- cmdline_block: Expected ext_cmdline block (for function definitions) +-- wildmenu_items: Expected items for ext_wildmenu +-- wildmenu_pos: Expected position for ext_wildmenu +function Screen:expect(expected, attr_ids, attr_ignore) + local grid, condition = nil, nil local expected_rows = {} - if type(expected) ~= "string" then - assert(not (attr_ids or attr_ignore or condition or any)) + if type(expected) == "table" then + assert(not (attr_ids ~= nil or attr_ignore ~= nil)) + local is_key = {grid=true, attr_ids=true, attr_ignore=true, condition=true, + any=true, mode=true, unchanged=true, intermediate=true, + reset=true, timeout=true} + for _, v in ipairs(ext_keys) do + is_key[v] = true + end + for k, _ in pairs(expected) do + if not is_key[k] then + error("Screen:expect: Unknown keyword argument '"..k.."'") + end + end + grid = expected.grid + attr_ids = expected.attr_ids + attr_ignore = expected.attr_ignore + condition = expected.condition + assert(not (expected.any ~= nil and grid ~= nil)) + elseif type(expected) == "string" then + grid = expected + expected = {} + elseif type(expected) == "function" then + assert(not (attr_ids ~= nil or attr_ignore ~= nil)) condition = expected - expected = nil + expected = {} else + assert(false) + end + + if grid ~= nil then -- Remove the last line and dedent. Note that gsub returns more then one -- value. - expected = dedent(expected:gsub('\n[ ]+$', ''), 0) - for row in expected:gmatch('[^\n]+') do + grid = dedent(grid:gsub('\n[ ]+$', ''), 0) + for row in grid:gmatch('[^\n]+') do row = row:sub(1, #row - 1) -- Last char must be the screen delimiter. table.insert(expected_rows, row) end end - local ids = attr_ids or self._default_attr_ids - local ignore = attr_ignore or self._default_attr_ignore - self:wait(function() + local attr_state = { + ids = attr_ids or self._default_attr_ids, + ignore = attr_ignore or self._default_attr_ignore, + } + if self._options.ext_hlstate then + attr_state.id_to_index = self:hlstate_check_attrs(attr_state.ids or {}) + end + self._new_attrs = false + self:_wait(function() if condition ~= nil then local status, res = pcall(condition) if not status then @@ -218,28 +336,32 @@ function Screen:expect(expected, attr_ids, attr_ignore, condition, any) end end - if expected and not any and self._height ~= #expected_rows then + if grid ~= nil and self._height ~= #expected_rows then return ("Expected screen state's row count(" .. #expected_rows .. ') differs from configured height(' .. self._height .. ') of Screen.') end + if self._options.ext_hlstate and self._new_attrs then + attr_state.id_to_index = self:hlstate_check_attrs(attr_state.ids or {}) + end + local actual_rows = {} for i = 1, self._height do - actual_rows[i] = self:_row_repr(self._rows[i], ids, ignore) + actual_rows[i] = self:_row_repr(self._rows[i], attr_state) end - if expected == nil then - return - elseif any then - -- Search for `expected` anywhere in the screen lines. + if expected.any ~= nil then + -- Search for `any` anywhere in the screen lines. local actual_screen_str = table.concat(actual_rows, '\n') - if nil == string.find(actual_screen_str, expected) then + if nil == string.find(actual_screen_str, expected.any) then return ( 'Failed to match any screen lines.\n' - .. 'Expected (anywhere): "' .. expected .. '"\n' + .. 'Expected (anywhere): "' .. expected.any .. '"\n' .. 'Actual:\n |' .. table.concat(actual_rows, '|\n |') .. '|\n\n') end - else + end + + if grid ~= nil then -- `expected` must match the screen lines exactly. for i = 1, self._height do if expected_rows[i] ~= actual_rows[i] then @@ -259,21 +381,87 @@ screen:redraw_debug() to show all intermediate screen states. ]]) end end end - end) + + -- Extension features. The default expectations should cover the case of + -- the ext_ feature being disabled, or the feature currently not activated + -- (for instance no external cmdline visible). Some extensions require + -- preprocessing to represent highlights in a reproducible way. + local extstate = self:_extstate_repr(attr_state) + + -- convert assertion errors into invalid screen state descriptions + local status, res = pcall(function() + for _, k in ipairs(ext_keys) do + -- Empty states is considered the default and need not be mentioned + if not (expected[k] == nil and isempty(extstate[k])) then + eq(expected[k], extstate[k], k) + end + end + if expected.mode ~= nil then + eq(expected.mode, self.mode, "mode") + end + end) + if not status then + return tostring(res) + end + end, expected) end -function Screen:wait(check, timeout) - local err, checked = false +function Screen:_wait(check, flags) + local err, checked = false, false local success_seen = false local failure_after_success = false + local did_flush = true + local warn_immediate = not (flags.unchanged or flags.intermediate) + + if flags.intermediate and flags.unchanged then + error("Choose only one of 'intermediate' and 'unchanged', not both") + end + + if flags.reset then + -- throw away all state, we expect it to be retransmitted + self:_reset() + end + + -- Maximum timeout, after which a incorrect state will be regarded as a + -- failure + local timeout = flags.timeout or self.timeout + + -- Minimal timeout before the loop is allowed to be stopped so we + -- always do some check for failure after success. + local minimal_timeout = default_timeout_factor * 2 + + local immediate_seen, intermediate_seen = false, false + if not check() then + minimal_timeout = default_timeout_factor * 20 + immediate_seen = true + end + + -- for an unchanged test, flags.timeout means the time during the state is + -- expected to be unchanged, so always wait this full time. + if (flags.unchanged or flags.intermediate) and flags.timeout ~= nil then + minimal_timeout = timeout + end + + assert(timeout >= minimal_timeout) + local did_miminal_timeout = false + local function notification_cb(method, args) assert(method == 'redraw') - self:_redraw(args) + did_flush = self:_redraw(args) + if not did_flush then + return + end err = check() checked = true + if err and immediate_seen then + intermediate_seen = true + end + if not err then success_seen = true - helpers.stop() + if did_miminal_timeout then + helpers.stop() + end elseif success_seen and #args > 0 then failure_after_success = true --print(require('inspect')(args)) @@ -281,37 +469,88 @@ function Screen:wait(check, timeout) return true end - run(nil, notification_cb, nil, timeout or self.timeout) - if not checked then + run(nil, notification_cb, nil, minimal_timeout) + if not did_flush then + err = "no flush received" + elseif not checked then err = check() + if not err and flags.unchanged then + -- expecting NO screen change: use a shorter timout + success_seen = true + end + end + + if not success_seen then + did_miminal_timeout = true + run(nil, notification_cb, nil, timeout-minimal_timeout) + end + + local did_warn = false + if warn_immediate and immediate_seen then + print([[ + +Warning: A screen test has immediate success. Try to avoid this unless the +purpose of the test really requires it.]]) + if intermediate_seen then + print([[ +There are intermediate states between the two identical expects. +Use screen:snapshot_util() or screen:redraw_debug() to find them, and add them +to the test if they make sense. +]]) + else + print([[If necessary, silence this warning by +supplying the 'unchanged' argument to screen:expect.]]) + end + did_warn = true end if failure_after_success then print([[ Warning: Screen changes were received after the expected state. This indicates -indeterminism in the test. Try adding wait() (or screen:expect(...)) between +indeterminism in the test. Try adding screen:expect(...) (or wait()) between asynchronous (feed(), nvim_input()) and synchronous API calls. - - Use Screen:redraw_debug() to investigate the problem. + - Use Screen:redraw_debug() to investigate the problem. It might find + relevant intermediate states that should be added to the test to make it + more robust. + - If the point of the test is to assert the state after some user input + sent with feed(...), also adding an screen:expect(...) before the feed(...) + will help ensure the input is sent to nvim when nvim is in a predictable + state. This is preferable to using wait(), as it is more closely emulates + real user interaction. - wait() can trigger redraws and consequently generate more indeterminism. In that case try removing every wait(). ]]) + did_warn = true + end + + + if err then + assert(false, err) + elseif did_warn then local tb = debug.traceback() local index = string.find(tb, '\n%s*%[C]') print(string.sub(tb,1,index)) end - if err then - assert(false, err) + if flags.intermediate then + assert(intermediate_seen, "expected intermediate screen state before final screen state") + elseif flags.unchanged then + assert(not intermediate_seen, "expected screen state to be unchanged") end end function Screen:sleep(ms) - pcall(function() self:wait(function() return "error" end, ms) end) + local function notification_cb(method, args) + assert(method == 'redraw') + self:_redraw(args) + end + run(nil, notification_cb, nil, ms) end function Screen:_redraw(updates) - for _, update in ipairs(updates) do + local did_flush = false + for k, update in ipairs(updates) do -- print('--') -- print(require('inspect')(update)) local method = update[1] @@ -326,8 +565,11 @@ function Screen:_redraw(updates) self._on_event(method, update[i]) end end - -- print(self:_current_screen()) + if k == #updates and method == "flush" then + did_flush = true + end end + return did_flush end function Screen:set_on_event_handler(callback) @@ -339,7 +581,7 @@ function Screen:_handle_resize(width, height) for _ = 1, height do local cols = {} for _ = 1, width do - table.insert(cols, {text = ' ', attrs = {}}) + table.insert(cols, {text = ' ', attrs = self._clear_attrs, hl_id = 0}) end table.insert(rows, cols) end @@ -353,14 +595,59 @@ function Screen:_handle_resize(width, height) } end +function Screen:_handle_flush() +end + +function Screen:_handle_grid_resize(grid, width, height) + assert(grid == 1) + self:_handle_resize(width, height) +end + +function Screen:_reset() + -- TODO: generalize to multigrid later + self:_handle_grid_clear(1) + + -- TODO: share with initialization, so it generalizes? + self.popupmenu = nil + self.cmdline = {} + self.cmdline_block = {} + self.wildmenu_items = nil + self.wildmenu_pos = nil +end + + function Screen:_handle_mode_info_set(cursor_style_enabled, mode_info) self._cursor_style_enabled = cursor_style_enabled + for _, item in pairs(mode_info) do + -- attr IDs are not stable, but their value should be + if item.attr_id ~= nil then + item.attr = self._attr_table[item.attr_id][1] + item.attr_id = nil + end + if item.attr_id_lm ~= nil then + item.attr_lm = self._attr_table[item.attr_id_lm][1] + item.attr_id_lm = nil + end + end self._mode_info = mode_info end function Screen:_handle_clear() - self:_clear_block(self._scroll_region.top, self._scroll_region.bot, - self._scroll_region.left, self._scroll_region.right) + -- the first implemented UI protocol clients (python-gui and builitin TUI) + -- allowed the cleared region to be restricted by setting the scroll region. + -- this was never used by nvim tough, and not documented and implemented by + -- newer clients, to check we remain compatible with both kind of clients, + -- ensure the scroll region is in a reset state. + local expected_region = { + top = 1, bot = self._height, left = 1, right = self._width + } + eq(expected_region, self._scroll_region) + self:_clear_block(1, self._height, 1, self._width) +end + +function Screen:_handle_grid_clear(grid) + assert(grid == 1) + self:_clear_block(1, self._height, 1, self._width) end function Screen:_handle_eol_clear() @@ -373,6 +660,12 @@ function Screen:_handle_cursor_goto(row, col) self._cursor.col = col + 1 end +function Screen:_handle_grid_cursor_goto(grid, row, col) + assert(grid == 1) + self._cursor.row = row + 1 + self._cursor.col = col + 1 +end + function Screen:_handle_busy_start() self._busy = true end @@ -406,45 +699,85 @@ function Screen:_handle_scroll(count) local bot = self._scroll_region.bot local left = self._scroll_region.left local right = self._scroll_region.right + self:_handle_grid_scroll(1, top-1, bot, left-1, right, count, 0) +end + +function Screen:_handle_grid_scroll(grid, top, bot, left, right, rows, cols) + top = top+1 + left = left+1 + assert(grid == 1) + assert(cols == 0) local start, stop, step - if count > 0 then + if rows > 0 then start = top - stop = bot - count + stop = bot - rows step = 1 else start = bot - stop = top - count + stop = top - rows step = -1 end -- shift scroll region for i = start, stop, step do local target = self._rows[i] - local source = self._rows[i + count] + local source = self._rows[i + rows] for j = left, right do target[j].text = source[j].text target[j].attrs = source[j].attrs + target[j].hl_id = source[j].hl_id end end -- clear invalid rows - for i = stop + step, stop + count, step do + for i = stop + step, stop + rows, step do self:_clear_row_section(i, left, right) end end +function Screen:_handle_hl_attr_define(id, rgb_attrs, cterm_attrs, info) + self._attr_table[id] = {rgb_attrs, cterm_attrs} + self._hl_info[id] = info + self._new_attrs = true +end + function Screen:_handle_highlight_set(attrs) self._attrs = attrs end function Screen:_handle_put(str) + assert(not self._options.ext_linegrid) local cell = self._rows[self._cursor.row][self._cursor.col] cell.text = str cell.attrs = self._attrs + cell.hl_id = -1 self._cursor.col = self._cursor.col + 1 end +function Screen:_handle_grid_line(grid, row, col, items) + assert(self._options.ext_linegrid) + assert(grid == 1) + local line = self._rows[row+1] + local colpos = col+1 + local hl = self._clear_attrs + local hl_id = 0 + for _,item in ipairs(items) do + local text, hl_id_cell, count = unpack(item) + if hl_id_cell ~= nil then + hl_id = hl_id_cell + hl = self._attr_table[hl_id] + end + for _ = 1, (count or 1) do + local cell = line[colpos] + cell.text = text + cell.hl_id = hl_id + cell.attrs = hl + colpos = colpos+1 + end + end +end + function Screen:_handle_bell() self.bell = true end @@ -453,7 +786,14 @@ function Screen:_handle_visual_bell() self.visual_bell = true end -function Screen:_handle_default_colors_set() +function Screen:_handle_default_colors_set(rgb_fg, rgb_bg, rgb_sp, cterm_fg, cterm_bg) + self.default_colors = { + rgb_fg=rgb_fg, + rgb_bg=rgb_bg, + rgb_sp=rgb_sp, + cterm_fg=cterm_fg, + cterm_bg=cterm_bg + } end function Screen:_handle_update_fg(fg) @@ -488,6 +828,63 @@ function Screen:_handle_option_set(name, value) self.options[name] = value end +function Screen:_handle_popupmenu_show(items, selected, row, col) + self.popupmenu = {items=items,pos=selected, anchor={row, col}} +end + +function Screen:_handle_popupmenu_select(selected) + self.popupmenu.pos = selected +end + +function Screen:_handle_popupmenu_hide() + self.popupmenu = nil +end + +function Screen:_handle_cmdline_show(content, pos, firstc, prompt, indent, level) + if firstc == '' then firstc = nil end + if prompt == '' then prompt = nil end + if indent == 0 then indent = nil end + self.cmdline[level] = {content=content, pos=pos, firstc=firstc, + prompt=prompt, indent=indent} +end + +function Screen:_handle_cmdline_hide(level) + self.cmdline[level] = nil +end + +function Screen:_handle_cmdline_special_char(char, shift, level) + -- cleared by next cmdline_show on the same level + self.cmdline[level].special = {char, shift} +end + +function Screen:_handle_cmdline_pos(pos, level) + self.cmdline[level].pos = pos +end + +function Screen:_handle_cmdline_block_show(block) + self.cmdline_block = block +end + +function Screen:_handle_cmdline_block_append(item) + self.cmdline_block[#self.cmdline_block+1] = item +end + +function Screen:_handle_cmdline_block_hide() + self.cmdline_block = {} +end + +function Screen:_handle_wildmenu_show(items) + self.wildmenu_items = items +end + +function Screen:_handle_wildmenu_select(pos) + self.wildmenu_pos = pos +end + +function Screen:_handle_wildmenu_hide() + self.wildmenu_items, self.wildmenu_pos = nil, nil +end + function Screen:_clear_block(top, bot, left, right) for i = top, bot do self:_clear_row_section(i, left, right) @@ -498,15 +895,19 @@ function Screen:_clear_row_section(rownum, startcol, stopcol) local row = self._rows[rownum] for i = startcol, stopcol do row[i].text = ' ' - row[i].attrs = {} + row[i].attrs = self._clear_attrs end end -function Screen:_row_repr(row, attr_ids, attr_ignore) +function Screen:_row_repr(row, attr_state) local rv = {} local current_attr_id for i = 1, self._width do - local attr_id = self:_get_attr_id(attr_ids, attr_ignore, row[i].attrs) + local attrs = row[i].attrs + if self._options.ext_linegrid then + attrs = attrs[(self._options.rgb and 1) or 2] + end + local attr_id = self:_get_attr_id(attr_state, attrs, row[i].hl_id) if current_attr_id and attr_id ~= current_attr_id then -- close current attribute bracket, add it before any whitespace -- up to the current cell @@ -532,14 +933,42 @@ function Screen:_row_repr(row, attr_ids, attr_ignore) return table.concat(rv, '')--:gsub('%s+$', '') end +function Screen:_extstate_repr(attr_state) + local cmdline = {} + for i, entry in pairs(self.cmdline) do + entry = shallowcopy(entry) + entry.content = self:_chunks_repr(entry.content, attr_state) + cmdline[i] = entry + end + + local cmdline_block = {} + for i, entry in ipairs(self.cmdline_block) do + cmdline_block[i] = self:_chunks_repr(entry, attr_state) + end -function Screen:_current_screen() - -- get a string that represents the current screen state(debugging helper) - local rv = {} - for i = 1, self._height do - table.insert(rv, "'"..self:_row_repr(self._rows[i]).."'") + return { + popupmenu=self.popupmenu, + cmdline=cmdline, + cmdline_block=cmdline_block, + wildmenu_items=self.wildmenu_items, + wildmenu_pos=self.wildmenu_pos, + } +end + +function Screen:_chunks_repr(chunks, attr_state) + local repr_chunks = {} + for i, chunk in ipairs(chunks) do + local hl, text = unpack(chunk) + local attrs + if self._options.ext_linegrid then + attrs = self._attr_table[hl][1] + else + attrs = hl + end + local attr_id = self:_get_attr_id(attr_state, attrs, hl) + repr_chunks[i] = {text, attr_id} end - return table.concat(rv, '\n') + return repr_chunks end -- Generates tests. Call it where Screen:expect() would be. Waits briefly, then @@ -570,56 +999,179 @@ function Screen:redraw_debug(attrs, ignore, timeout) end function Screen:print_snapshot(attrs, ignore) + attrs = attrs or self._default_attr_ids if ignore == nil then ignore = self._default_attr_ignore end - if attrs == nil then - attrs = {} - if self._default_attr_ids ~= nil then - for i, a in pairs(self._default_attr_ids) do - attrs[i] = a + local attr_state = { + ids = {}, + ignore = ignore, + mutable = true, -- allow _row_repr to add missing highlights + } + + if attrs ~= nil then + for i, a in pairs(attrs) do + attr_state.ids[i] = a + end + end + if self._options.ext_hlstate then + attr_state.id_to_index = self:hlstate_check_attrs(attr_state.ids) + end + + local lines = {} + for i = 1, self._height do + table.insert(lines, " "..self:_row_repr(self._rows[i], attr_state).."|") + end + + local ext_state = self:_extstate_repr(attr_state) + local keys = false + for k, v in pairs(ext_state) do + if isempty(v) then + ext_state[k] = nil -- deleting keys while iterating is ok + else + keys = true + end + end + + local attrstr = "" + if attr_state.modified then + local attrstrs = {} + for i, a in pairs(attr_state.ids) do + local dict + if self._options.ext_hlstate then + dict = self:_pprint_hlstate(a) + else + dict = "{"..self:_pprint_attrs(a).."}" end + local keyval = (type(i) == "number") and "["..tostring(i).."]" or i + table.insert(attrstrs, " "..keyval.." = "..dict..",") end + attrstr = (", "..(keys and "attr_ids=" or "") + .."{\n"..table.concat(attrstrs, "\n").."\n}") + end + print( "\nscreen:expect"..(keys and "{grid=" or "(").."[[") + print( table.concat(lines, '\n')) + io.stdout:write( "]]"..attrstr) + for _, k in ipairs(ext_keys) do + if ext_state[k] ~= nil then + io.stdout:write(", "..k.."="..inspect(ext_state[k])) + end + end + print((keys and "}" or ")").."\n") + io.stdout:flush() +end - if ignore ~= true then - for i = 1, self._height do - local row = self._rows[i] - for j = 1, self._width do - local attr = row[j].attrs - if self:_attr_index(attrs, attr) == nil and self:_attr_index(ignore, attr) == nil then - if not self:_equal_attrs(attr, {}) then - table.insert(attrs, attr) +function Screen:_insert_hl_id(attr_state, hl_id) + if attr_state.id_to_index[hl_id] ~= nil then + return attr_state.id_to_index[hl_id] + end + local raw_info = self._hl_info[hl_id] + local info = {} + if #raw_info > 1 then + for i, item in ipairs(raw_info) do + info[i] = self:_insert_hl_id(attr_state, item.id) + end + else + info[1] = {} + for k, v in pairs(raw_info[1]) do + if k ~= "id" then + info[1][k] = v + end + end + end + + local entry = self._attr_table[hl_id] + local attrval + if self._hlstate_cterm then + attrval = {entry[1], entry[2], info} -- unpack() doesn't work + else + attrval = {entry[1], info} + end + + + table.insert(attr_state.ids, attrval) + attr_state.id_to_index[hl_id] = #attr_state.ids + return #attr_state.ids +end + +function Screen:hlstate_check_attrs(attrs) + local id_to_index = {} + for i = 1,#self._attr_table do + local iinfo = self._hl_info[i] + local matchinfo = {} + if #iinfo > 1 then + for k,item in ipairs(iinfo) do + matchinfo[k] = id_to_index[item.id] + end + else + matchinfo = iinfo + end + for k,v in pairs(attrs) do + local attr, info, attr_rgb, attr_cterm + if self._hlstate_cterm then + attr_rgb, attr_cterm, info = unpack(v) + attr = {attr_rgb, attr_cterm} + else + attr, info = unpack(v) + end + if self:_equal_attr_def(attr, self._attr_table[i]) then + if #info == #matchinfo then + local match = false + if #info == 1 then + if self:_equal_info(info[1],matchinfo[1]) then + match = true + end + else + match = true + for j = 1,#info do + if info[j] ~= matchinfo[j] then + match = false + end end end + if match then + id_to_index[i] = k + end end end end end + return id_to_index +end - local rv = {} - for i = 1, self._height do - table.insert(rv, " "..self:_row_repr(self._rows[i],attrs, ignore).."|") - end - local attrstrs = {} - local alldefault = true - for i, a in ipairs(attrs) do - if self._default_attr_ids == nil or self._default_attr_ids[i] ~= a then - alldefault = false - end - local dict = "{"..self:_pprint_attrs(a).."}" - table.insert(attrstrs, "["..tostring(i).."] = "..dict) - end - local attrstr = "{"..table.concat(attrstrs, ", ").."}" - print( "\nscreen:expect([[") - print( table.concat(rv, '\n')) - if alldefault then - print( "]])\n") + +function Screen:_pprint_hlstate(item) + --print(require('inspect')(item)) + local attrdict = "{"..self:_pprint_attrs(item[1]).."}, " + local attrdict2, hlinfo + if self._hlstate_cterm then + attrdict2 = "{"..self:_pprint_attrs(item[2]).."}, " + hlinfo = item[3] + else + attrdict2 = "" + hlinfo = item[2] + end + local descdict = "{"..self:_pprint_hlinfo(hlinfo).."}" + return "{"..attrdict..attrdict2..descdict.."}" +end + +function Screen:_pprint_hlinfo(states) + if #states == 1 then + local items = {} + for f, v in pairs(states[1]) do + local desc = tostring(v) + if type(v) == type("") then + desc = '"'..desc..'"' + end + table.insert(items, f.." = "..desc) + end + return "{"..table.concat(items, ", ").."}" else - print( "]], "..attrstr..")\n") + return table.concat(states, ", ") end - io.stdout:flush() end + function Screen:_pprint_attrs(attrs) local items = {} for f, v in pairs(attrs) do @@ -643,32 +1195,64 @@ local function backward_find_meaningful(tbl, from) -- luacheck: no unused return from end -function Screen:_get_attr_id(attr_ids, ignore, attrs) - if not attr_ids then +function Screen:_get_attr_id(attr_state, attrs, hl_id) + if not attr_state.ids then return end - for id, a in pairs(attr_ids) do - if self:_equal_attrs(a, attrs) then - return id - end + + if self._options.ext_hlstate then + local id = attr_state.id_to_index[hl_id] + if id ~= nil or hl_id == 0 then + return id + end + if attr_state.mutable then + id = self:_insert_hl_id(attr_state, hl_id) + attr_state.modified = true + return id + end + return "UNEXPECTED "..self:_pprint_attrs(self._attr_table[hl_id][1]) + else + for id, a in pairs(attr_state.ids) do + if self:_equal_attrs(a, attrs) then + return id + end + end + if self:_equal_attrs(attrs, {}) or + attr_state.ignore == true or + self:_attr_index(attr_state.ignore, attrs) ~= nil then + -- ignore this attrs + return nil + end + if attr_state.mutable then + table.insert(attr_state.ids, attrs) + attr_state.modified = true + return #attr_state.ids + end + return "UNEXPECTED "..self:_pprint_attrs(attrs) end - if self:_equal_attrs(attrs, {}) or - ignore == true or self:_attr_index(ignore, attrs) ~= nil then - -- ignore this attrs - return nil +end + +function Screen:_equal_attr_def(a, b) + if self._hlstate_cterm then + return self:_equal_attrs(a[1],b[1]) and self:_equal_attrs(a[2],b[2]) + else + return self:_equal_attrs(a,b[1]) end - return "UNEXPECTED "..self:_pprint_attrs(attrs) end function Screen:_equal_attrs(a, b) return a.bold == b.bold and a.standout == b.standout and a.underline == b.underline and a.undercurl == b.undercurl and a.italic == b.italic and a.reverse == b.reverse and - a.foreground == b.foreground and - a.background == b.background and + a.foreground == b.foreground and a.background == b.background and a.special == b.special end +function Screen:_equal_info(a, b) + return a.kind == b.kind and a.hi_name == b.hi_name and + a.ui_name == b.ui_name +end + function Screen:_attr_index(attrs, attr) if not attrs then return nil diff --git a/test/functional/ui/screen_basic_spec.lua b/test/functional/ui/screen_basic_spec.lua index 6f04cde4d4..1a8b7d543a 100644 --- a/test/functional/ui/screen_basic_spec.lua +++ b/test/functional/ui/screen_basic_spec.lua @@ -48,13 +48,13 @@ describe('screen', function() end) end) -describe('Screen', function() +local function screen_tests(linegrid) local screen before_each(function() clear() screen = Screen.new() - screen:attach() + screen:attach({rgb=true,ext_linegrid=linegrid}) screen:set_default_attr_ids( { [0] = {bold=true, foreground=255}, [1] = {bold=true, reverse=true}, @@ -354,6 +354,140 @@ describe('Screen', function() {0:~ }| | ]]) + + feed(':echo "'..string.rep('x\\n', 12)..'"<cr>') + screen:expect([[ + x | + x | + x | + x | + x | + x | + x | + x | + x | + x | + x | + x | + | + {7:Press ENTER or type command to continue}^ | + ]]) + + feed('<cr>') + screen:expect([[ + {4: [No Name] }{2: [No Name] }{3: }{4:X}| + ^ | + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + | + ]]) + + end) + + it('redraws properly with :tab split right after scroll', function() + feed('15Ofoo<esc>15Obar<esc>gg') + + command('vsplit') + screen:expect([[ + ^foo {3:│}foo | + foo {3:│}foo | + foo {3:│}foo | + foo {3:│}foo | + foo {3:│}foo | + foo {3:│}foo | + foo {3:│}foo | + foo {3:│}foo | + foo {3:│}foo | + foo {3:│}foo | + foo {3:│}foo | + foo {3:│}foo | + {1:[No Name] [+] }{3:[No Name] [+] }| + | + ]]) + + feed('<PageDown>') + screen:expect([[ + ^foo {3:│}foo | + foo {3:│}foo | + foo {3:│}foo | + foo {3:│}foo | + bar {3:│}foo | + bar {3:│}foo | + bar {3:│}foo | + bar {3:│}foo | + bar {3:│}foo | + bar {3:│}foo | + bar {3:│}foo | + bar {3:│}foo | + {1:[No Name] [+] }{3:[No Name] [+] }| + | + ]]) + command('tab split') + screen:expect([[ + {4: }{5:2}{4:+ [No Name] }{2: + [No Name] }{3: }{4:X}| + ^foo | + foo | + foo | + foo | + bar | + bar | + bar | + bar | + bar | + bar | + bar | + bar | + | + ]]) + end) + + it('redraws unvisited tab #9152', function() + insert('hello') + -- create a tab without visiting it + command('tabnew|tabnext') + screen:expect([[ + {2: + [No Name] }{4: [No Name] }{3: }{4:X}| + hell^o | + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + | + ]]) + + feed('gT') + screen:expect([[ + {4: + [No Name] }{2: [No Name] }{3: }{4:X}| + ^ | + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + | + ]]) end) end) @@ -741,4 +875,12 @@ describe('Screen', function() | ]]) end) +end + +describe("Screen (char-based)", function() + screen_tests(false) +end) + +describe("Screen (line-based)", function() + screen_tests(true) end) diff --git a/test/functional/ui/searchhl_spec.lua b/test/functional/ui/searchhl_spec.lua index 168080a092..a46670d8a2 100644 --- a/test/functional/ui/searchhl_spec.lua +++ b/test/functional/ui/searchhl_spec.lua @@ -1,6 +1,7 @@ local helpers = require('test.functional.helpers')(after_each) local Screen = require('test.functional.ui.screen') local clear, feed, insert = helpers.clear, helpers.feed, helpers.insert +local command = helpers.command local feed_command = helpers.feed_command local eq = helpers.eq local eval = helpers.eval @@ -93,6 +94,59 @@ describe('search highlighting', function() ]]) end) + it('highlights after EOL', function() + insert("\n\n\n\n\n\n") + + feed("gg/^<cr>") + screen:expect([[ + {2: } | + {2:^ } | + {2: } | + {2: } | + {2: } | + {2: } | + /^ | + ]]) + + -- Test that highlights are preserved after moving the cursor. + feed("j") + screen:expect([[ + {2: } | + {2: } | + {2:^ } | + {2: } | + {2: } | + {2: } | + /^ | + ]]) + + -- Repeat the test in rightleft mode. + command("nohlsearch") + command("set rightleft") + feed("gg/^<cr>") + + screen:expect([[ + {2: }| + {2:^ }| + {2: }| + {2: }| + {2: }| + {2: }| + ^/ | + ]]) + + feed("j") + screen:expect([[ + {2: }| + {2: }| + {2:^ }| + {2: }| + {2: }| + {2: }| + ^/ | + ]]) + end) + it('is preserved during :terminal activity', function() if iswin() then feed([[:terminal for /L \%I in (1,1,5000) do @(echo xxx & echo xxx & echo xxx)<cr>]]) @@ -270,7 +324,17 @@ describe('search highlighting', function() ]]) -- same, for C-t - feed('<ESC>/<C-t>') + feed('<ESC>') + screen:expect([[ + the first line | + in a ^little file | + | + {1:~ }| + {1:~ }| + {1:~ }| + | + ]]) + feed('/<C-t>') screen:expect([[ the first line | in a little file | diff --git a/test/functional/ui/sign_spec.lua b/test/functional/ui/sign_spec.lua index c00d99cf90..6abeb0b2f4 100644 --- a/test/functional/ui/sign_spec.lua +++ b/test/functional/ui/sign_spec.lua @@ -1,6 +1,7 @@ local helpers = require('test.functional.helpers')(after_each) local Screen = require('test.functional.ui.screen') local clear, feed, command = helpers.clear, helpers.feed, helpers.command +local source = helpers.source describe('Signs', function() local screen @@ -13,6 +14,12 @@ describe('Signs', function() [0] = {bold=true, foreground=255}, [1] = {background = Screen.colors.Yellow}, [2] = {foreground = Screen.colors.DarkBlue, background = Screen.colors.Grey}, + [3] = {background = Screen.colors.Gray90}, + [4] = {bold = true, reverse = true}, + [5] = {reverse = true}, + [6] = {foreground = Screen.colors.Brown}, + [7] = {foreground = Screen.colors.DarkBlue, background = Screen.colors.LightGrey}, + [8] = {foreground = Screen.colors.Grey100, background = Screen.colors.Red}, } ) end) @@ -45,5 +52,64 @@ describe('Signs', function() | ]]) end) + + it('can be called right after :split', function() + feed('ia<cr>b<cr>c<cr><esc>gg') + -- This used to cause a crash due to :sign using a special redraw + -- (not updating nvim's specific highlight data structures) + -- without proper redraw first, as split just flags for redraw later. + source([[ + set cursorline + sign define piet text=>> texthl=Search + split + sign place 3 line=2 name=piet buffer=1 + ]]) + screen:expect([[ + {2: }{3:^a }| + {1:>>}b | + {2: }c | + {2: } | + {2: }{0:~ }| + {2: }{0:~ }| + {4:[No Name] [+] }| + {2: }{3:a }| + {1:>>}b | + {2: }c | + {2: } | + {2: }{0:~ }| + {5:[No Name] [+] }| + | + ]]) + end) + + it('can combine text, linehl and numhl', function() + feed('ia<cr>b<cr>c<cr><esc>') + command('set number') + command('sign define piet text=>> texthl=Search') + command('sign define pietx linehl=ErrorMsg') + command('sign define pietxx numhl=Folded') + command('sign place 1 line=1 name=piet buffer=1') + command('sign place 2 line=2 name=pietx buffer=1') + command('sign place 3 line=3 name=pietxx buffer=1') + command('sign place 4 line=4 name=piet buffer=1') + command('sign place 5 line=4 name=pietx buffer=1') + command('sign place 6 line=4 name=pietxx buffer=1') + screen:expect([[ + {1:>>}{6: 1 }a | + {2: }{6: 2 }{8:b }| + {2: }{7: 3 }c | + {1:>>}{7: 4 }{8:^ }| + {2: }{0:~ }| + {2: }{0:~ }| + {2: }{0:~ }| + {2: }{0:~ }| + {2: }{0:~ }| + {2: }{0:~ }| + {2: }{0:~ }| + {2: }{0:~ }| + {2: }{0:~ }| + | + ]]) + end) end) end) diff --git a/test/functional/ui/tabline_spec.lua b/test/functional/ui/tabline_spec.lua index e8271de0bf..dcab9f7ef4 100644 --- a/test/functional/ui/tabline_spec.lua +++ b/test/functional/ui/tabline_spec.lua @@ -28,27 +28,27 @@ describe('ui/ext_tabline', function() {tab = { id = 1 }, name = '[No Name]'}, {tab = { id = 2 }, name = 'another-tab'}, } - screen:expect([[ + screen:expect{grid=[[ ^ | ~ | ~ | ~ | | - ]], nil, nil, function() + ]], condition=function() eq({ id = 2 }, event_curtab) eq(expected_tabs, event_tabs) - end) + end} command("tabNext") - screen:expect([[ + screen:expect{grid=[[ ^ | ~ | ~ | ~ | | - ]], nil, nil, function() + ]], condition=function() eq({ id = 1 }, event_curtab) eq(expected_tabs, event_tabs) - end) + end} end) end) diff --git a/test/functional/ui/wildmode_spec.lua b/test/functional/ui/wildmode_spec.lua index b60d520ca0..8931d9245b 100644 --- a/test/functional/ui/wildmode_spec.lua +++ b/test/functional/ui/wildmode_spec.lua @@ -1,3 +1,5 @@ +local global_helpers = require('test.helpers') +local shallowcopy = global_helpers.shallowcopy local helpers = require('test.functional.helpers')(after_each) local Screen = require('test.functional.ui.screen') local clear, feed, command = helpers.clear, helpers.feed, helpers.command @@ -18,6 +20,14 @@ describe("'wildmenu'", function() screen:detach() end) + -- expect the screen stayed unchanged some time after first seen success + local function expect_stay_unchanged(args) + screen:expect(args) + args = shallowcopy(args) + args.unchanged = true + screen:expect(args) + end + it(':sign <tab> shows wildmenu completions', function() command('set wildmode=full') command('set wildmenu') @@ -76,10 +86,6 @@ describe("'wildmenu'", function() end) it('is preserved during :terminal activity', function() - -- Because this test verifies a _lack_ of activity after screen:sleep(), we - -- must wait the full timeout. So make it reasonable. - screen.timeout = 1000 - command('set wildmenu wildmode=full') command('set scrollback=4') if iswin() then @@ -90,26 +96,24 @@ describe("'wildmenu'", function() feed([[<C-\><C-N>gg]]) feed([[:sign <Tab>]]) -- Invoke wildmenu. - screen:sleep(50) -- Allow some terminal output. - screen:expect([[ + expect_stay_unchanged{grid=[[ foo | foo | foo | define jump list > | :sign define^ | - ]]) + ]]} -- cmdline CTRL-D display should also be preserved. feed([[<C-\><C-N>]]) feed([[:sign <C-D>]]) -- Invoke cmdline CTRL-D. - screen:sleep(50) -- Allow some terminal output. - screen:expect([[ + expect_stay_unchanged{grid=[[ :sign | define place | jump undefine | list unplace | :sign ^ | - ]]) + ]]} -- Exiting cmdline should show the buffer. feed([[<C-\><C-N>]]) @@ -123,22 +127,17 @@ describe("'wildmenu'", function() end) it('ignores :redrawstatus called from a timer #7108', function() - -- Because this test verifies a _lack_ of activity after screen:sleep(), we - -- must wait the full timeout. So make it reasonable. - screen.timeout = 1000 - command('set wildmenu wildmode=full') command([[call timer_start(10, {->execute('redrawstatus')}, {'repeat':-1})]]) feed([[<C-\><C-N>]]) feed([[:sign <Tab>]]) -- Invoke wildmenu. - screen:sleep(30) -- Allow some timer activity. - screen:expect([[ + expect_stay_unchanged{grid=[[ | ~ | ~ | define jump list > | :sign define^ | - ]]) + ]]} end) it('with laststatus=0, :vsplit, :term #2255', function() @@ -164,10 +163,9 @@ describe("'wildmenu'", function() feed([[<C-\><C-N>]]) feed([[:<Tab>]]) -- Invoke wildmenu. - screen:sleep(10) -- Flush -- Check only the last 2 lines, because the shell output is -- system-dependent. - screen:expect('! # & < = > @ > \n:!^', nil, nil, nil, true) + expect_stay_unchanged{any='! # & < = > @ > \n:!^'} end) end) @@ -204,21 +202,11 @@ end) describe('ui/ext_wildmenu', function() local screen - local items, selected = nil, nil before_each(function() clear() screen = Screen.new(25, 5) screen:attach({rgb=true, ext_wildmenu=true}) - screen:set_on_event_handler(function(name, data) - if name == "wildmenu_show" then - items = data[1] - elseif name == "wildmenu_select" then - selected = data[1] - elseif name == "wildmenu_hide" then - items, selected = nil, nil - end - end) end) after_each(function() @@ -238,63 +226,48 @@ describe('ui/ext_wildmenu', function() command('set wildmode=full') command('set wildmenu') feed(':sign <tab>') - screen:expect([[ + screen:expect{grid=[[ | ~ | ~ | ~ | :sign define^ | - ]], nil, nil, function() - eq(expected, items) - eq(0, selected) - end) + ]], wildmenu_items=expected, wildmenu_pos=0} feed('<tab>') - screen:expect([[ + screen:expect{grid=[[ | ~ | ~ | ~ | :sign jump^ | - ]], nil, nil, function() - eq(expected, items) - eq(1, selected) - end) + ]], wildmenu_items=expected, wildmenu_pos=1} feed('<left><left>') - screen:expect([[ + screen:expect{grid=[[ | ~ | ~ | ~ | :sign ^ | - ]], nil, nil, function() - eq(expected, items) - eq(-1, selected) - end) + ]], wildmenu_items=expected, wildmenu_pos=-1} feed('<right>') - screen:expect([[ + screen:expect{grid=[[ | ~ | ~ | ~ | :sign define^ | - ]], nil, nil, function() - eq(expected, items) - eq(0, selected) - end) + ]], wildmenu_items=expected, wildmenu_pos=0} feed('a') - screen:expect([[ + screen:expect{grid=[[ | ~ | ~ | ~ | :sign definea^ | - ]], nil, nil, function() - eq(nil, items) - eq(nil, selected) - end) + ]]} end) end) |