diff options
Diffstat (limited to 'test')
69 files changed, 3350 insertions, 1238 deletions
diff --git a/test/README.md b/test/README.md index 9b1f0d084a..0999f412ac 100644 --- a/test/README.md +++ b/test/README.md @@ -3,7 +3,9 @@ Tests Tests are run by `/cmake/RunTests.cmake` file, using `busted`. -For some failures, `.nvimlog` (or `$NVIM_LOG_FILE`) may provide insight. +For some failures, `.nvimlog` (or `$NVIM_LOG_FILE`) may provide insight. + +Depending on the presence of binaries (e.g., `xclip`) some tests will be ignored. You must compile with libintl to prevent `E319: The command is not available in this version` errors. --- @@ -62,9 +64,9 @@ To run a *specific* functional test: TEST_FILE=test/functional/foo.lua make functionaltest -To *repeat* a test many times: +To *repeat* a test: - .deps/usr/bin/busted --filter 'foo' --repeat 1000 test/functional/ui/foo_spec.lua + .deps/usr/bin/busted --lpath='build/?.lua' --filter 'foo' --repeat 1000 test/functional/ui/foo_spec.lua ### Filter by tag @@ -189,12 +191,12 @@ minutes](http://learnxinyminutes.com/docs/lua/). ## Checklist for migrating legacy tests -**Note:** Only "old style" (`src/testdir/*.in`) legacy tests should be -converted. Please _do not_ convert "new style" Vim tests (`src/testdir/*.vim`). +**Note:** Only "old style" (`src/nvim/testdir/*.in`) legacy tests should be +converted. Please _do not_ convert "new style" Vim tests +(`src/nvim/testdir/*.vim`). The "new style" Vim tests are faster than the old ones, and converting them takes time and effort better spent elsewhere. -- Remove the test from the Makefile (`src/nvim/testdir/Makefile`). - Remove the associated `test.in`, `test.out`, and `test.ok` files from `src/nvim/testdir/`. - Make sure the lua test ends in `_spec.lua`. diff --git a/test/functional/api/buffer_spec.lua b/test/functional/api/buffer_spec.lua index 823c134ebd..d9412f0f13 100644 --- a/test/functional/api/buffer_spec.lua +++ b/test/functional/api/buffer_spec.lua @@ -35,8 +35,37 @@ describe('api/buf', function() -- There's always at least one line eq(1, curbuf_depr('line_count')) end) - end) + it('line_count has defined behaviour for unloaded buffers', function() + -- we'll need to know our bufnr for when it gets unloaded + local bufnr = curbuf('get_number') + -- replace the buffer contents with these three lines + request('nvim_buf_set_lines', bufnr, 0, -1, 1, {"line1", "line2", "line3", "line4"}) + -- check the line count is correct + eq(4, request('nvim_buf_line_count', bufnr)) + -- force unload the buffer (this will discard changes) + command('new') + command('bunload! '..bufnr) + -- line count for an unloaded buffer should always be 0 + eq(0, request('nvim_buf_line_count', bufnr)) + end) + + it('get_lines has defined behaviour for unloaded buffers', function() + -- we'll need to know our bufnr for when it gets unloaded + local bufnr = curbuf('get_number') + -- replace the buffer contents with these three lines + buffer('set_lines', bufnr, 0, -1, 1, {"line1", "line2", "line3", "line4"}) + -- confirm that getting lines works + eq({"line2", "line3"}, buffer('get_lines', bufnr, 1, 3, 1)) + -- force unload the buffer (this will discard changes) + command('new') + command('bunload! '..bufnr) + -- attempting to get lines now always gives empty list + eq({}, buffer('get_lines', bufnr, 1, 3, 1)) + -- it's impossible to get out-of-bounds errors for an unloaded buffer + eq({}, buffer('get_lines', bufnr, 8888, 9999, 1)) + end) + end) describe('{get,set,del}_line', function() it('works', function() @@ -70,7 +99,6 @@ describe('api/buf', function() end) end) - describe('{get,set}_line_slice', function() it('get_line_slice: out-of-bounds returns empty array', function() curbuf_depr('set_line_slice', 0, 0, true, true, {'a', 'b', 'c'}) @@ -273,6 +301,43 @@ describe('api/buf', function() end) end) + describe('get_offset', function() + local get_offset = curbufmeths.get_offset + it('works', function() + curbufmeths.set_lines(0,-1,true,{'Some\r','exa\000mple', '', 'buf\rfer', 'text'}) + eq(5, curbufmeths.line_count()) + eq(0, get_offset(0)) + eq(6, get_offset(1)) + eq(15, get_offset(2)) + eq(16, get_offset(3)) + eq(24, get_offset(4)) + eq(29, get_offset(5)) + eq({false,'Index out of bounds'}, meth_pcall(get_offset, 6)) + eq({false,'Index out of bounds'}, meth_pcall(get_offset, -1)) + + curbufmeths.set_option('eol', false) + curbufmeths.set_option('fixeol', false) + eq(28, get_offset(5)) + + -- fileformat is ignored + curbufmeths.set_option('fileformat', 'dos') + eq(0, get_offset(0)) + eq(6, get_offset(1)) + eq(15, get_offset(2)) + eq(16, get_offset(3)) + eq(24, get_offset(4)) + eq(28, get_offset(5)) + curbufmeths.set_option('eol', true) + eq(29, get_offset(5)) + + command("set hidden") + command("enew") + eq(6, bufmeths.get_offset(1,1)) + command("bunload! 1") + eq(-1, bufmeths.get_offset(1,1)) + end) + end) + describe('{get,set,del}_var', function() it('works', function() curbuf('set_var', 'lua', {1, 2, {['3'] = 1}}) @@ -281,7 +346,7 @@ describe('api/buf', function() eq(1, funcs.exists('b:lua')) curbufmeths.del_var('lua') eq(0, funcs.exists('b:lua')) - eq({false, 'Key does not exist: lua'}, meth_pcall(curbufmeths.del_var, 'lua')) + eq({false, 'Key not found: lua'}, meth_pcall(curbufmeths.del_var, 'lua')) curbufmeths.set_var('lua', 1) command('lockvar b:lua') eq({false, 'Key is locked: lua'}, meth_pcall(curbufmeths.del_var, 'lua')) @@ -345,6 +410,31 @@ describe('api/buf', function() end) end) + describe('is_loaded', function() + it('works', function() + -- record our buffer number for when we unload it + local bufnr = curbuf('get_number') + -- api should report that the buffer is loaded + ok(buffer('is_loaded', bufnr)) + -- hide the current buffer by switching to a new empty buffer + -- Careful! we need to modify the buffer first or vim will just reuse it + buffer('set_lines', bufnr, 0, -1, 1, {'line1'}) + command('hide enew') + -- confirm the buffer is hidden, but still loaded + local infolist = nvim('eval', 'getbufinfo('..bufnr..')') + eq(1, #infolist) + eq(1, infolist[1].hidden) + eq(1, infolist[1].loaded) + -- now force unload the buffer + command('bunload! '..bufnr) + -- confirm the buffer is unloaded + infolist = nvim('eval', 'getbufinfo('..bufnr..')') + eq(0, infolist[1].loaded) + -- nvim_buf_is_loaded() should also report the buffer as unloaded + eq(false, buffer('is_loaded', bufnr)) + end) + end) + describe('is_valid', function() it('works', function() nvim('command', 'new') diff --git a/test/functional/api/buffer_updates_spec.lua b/test/functional/api/buffer_updates_spec.lua index e5cc747f8a..b54d9e1f6e 100644 --- a/test/functional/api/buffer_updates_spec.lua +++ b/test/functional/api/buffer_updates_spec.lua @@ -538,10 +538,6 @@ describe('API: buffer events:', function() end) it('works with :diffput and :diffget', function() - if os.getenv("APPVEYOR") then - pending("Fails on appveyor for some reason.", function() end) - end - local b1, tick1 = editoriginal(true, {"AAA", "BBB"}) local channel = nvim('get_api_info')[1] command('diffthis') diff --git a/test/functional/api/menu_spec.lua b/test/functional/api/menu_spec.lua index d55b7b118a..2cfa0e3e47 100644 --- a/test/functional/api/menu_spec.lua +++ b/test/functional/api/menu_spec.lua @@ -20,15 +20,15 @@ describe("update_menu notification", function() end) local function expect_sent(expected) - screen:wait(function() + screen:expect{condition=function() if screen.update_menu ~= expected then if expected then - return 'update_menu was expected but not sent' + error('update_menu was expected but not sent') else - return 'update_menu was sent unexpectedly' + error('update_menu was sent unexpectedly') end end - end) + end, unchanged=(not expected)} end it("should be sent when adding a menu", function() diff --git a/test/functional/api/server_notifications_spec.lua b/test/functional/api/server_notifications_spec.lua index 1d64ae7103..29cd38ef0d 100644 --- a/test/functional/api/server_notifications_spec.lua +++ b/test/functional/api/server_notifications_spec.lua @@ -65,4 +65,11 @@ describe('notify', function() eq(nest_level, act_nest_level) end) end) + + it('unsubscribe non-existing event #8745', function() + nvim('subscribe', 'event1') + nvim('unsubscribe', 'doesnotexist') + nvim('unsubscribe', 'event1') + eq(2, eval('1+1')) -- Still alive? + end) end) diff --git a/test/functional/api/server_requests_spec.lua b/test/functional/api/server_requests_spec.lua index 856e5ca4d2..4d25ba0819 100644 --- a/test/functional/api/server_requests_spec.lua +++ b/test/functional/api/server_requests_spec.lua @@ -9,7 +9,7 @@ local nvim_prog, command, funcs = helpers.nvim_prog, helpers.command, helpers.fu local source, next_msg = helpers.source, helpers.next_msg local ok = helpers.ok local meths = helpers.meths -local spawn, nvim_argv = helpers.spawn, helpers.nvim_argv +local spawn, merge_args = helpers.spawn, helpers.merge_args local set_session = helpers.set_session local expect_err = helpers.expect_err @@ -23,7 +23,7 @@ describe('server -> client', function() it('handles unexpected closed stream while preparing RPC response', function() source([[ - let g:_nvim_args = [v:progpath, '--embed', '-n', '-u', 'NONE', '-i', 'NONE', ] + let g:_nvim_args = [v:progpath, '--embed', '--headless', '-n', '-u', 'NONE', '-i', 'NONE', ] let ch1 = jobstart(g:_nvim_args, {'rpc': v:true}) let child1_ch = rpcrequest(ch1, "nvim_get_api_info")[0] call rpcnotify(ch1, 'nvim_eval', 'rpcrequest('.child1_ch.', "nvim_get_api_info")') @@ -189,7 +189,7 @@ describe('server -> client', function() end before_each(function() - command("let vim = rpcstart('"..nvim_prog.."', ['-u', 'NONE', '-i', 'NONE', '--cmd', 'set noswapfile', '--embed'])") + command("let vim = rpcstart('"..nvim_prog.."', ['-u', 'NONE', '-i', 'NONE', '--cmd', 'set noswapfile', '--embed', '--headless'])") neq(0, eval('vim')) end) @@ -268,6 +268,7 @@ describe('server -> client', function() end) describe('connecting to another (peer) nvim', function() + local nvim_argv = merge_args(helpers.nvim_argv, {'--headless'}) local function connect_test(server, mode, address) local serverpid = funcs.getpid() local client = spawn(nvim_argv) diff --git a/test/functional/api/tabpage_spec.lua b/test/functional/api/tabpage_spec.lua index 260a91a80c..c49091db02 100644 --- a/test/functional/api/tabpage_spec.lua +++ b/test/functional/api/tabpage_spec.lua @@ -34,7 +34,7 @@ describe('api/tabpage', function() eq(1, funcs.exists('t:lua')) curtabmeths.del_var('lua') eq(0, funcs.exists('t:lua')) - eq({false, 'Key does not exist: lua'}, meth_pcall(curtabmeths.del_var, 'lua')) + eq({false, 'Key not found: lua'}, meth_pcall(curtabmeths.del_var, 'lua')) curtabmeths.set_var('lua', 1) command('lockvar t:lua') eq({false, 'Key is locked: lua'}, meth_pcall(curtabmeths.del_var, 'lua')) diff --git a/test/functional/api/version_spec.lua b/test/functional/api/version_spec.lua index 7bf54c0d1e..b4ae17d963 100644 --- a/test/functional/api/version_spec.lua +++ b/test/functional/api/version_spec.lua @@ -46,12 +46,12 @@ end) describe("api functions", function() before_each(clear) - local function func_table(metadata) - local functions = {} - for _,f in ipairs(metadata.functions) do - functions[f.name] = f + local function name_table(entries) + local by_name = {} + for _,e in ipairs(entries) do + by_name[e.name] = e end - return functions + return by_name end -- Remove metadata that is not essential to backwards-compatibility. @@ -67,6 +67,15 @@ describe("api functions", function() return f end + local function check_ui_event_compatible(old_e, new_e) + -- check types of existing params are the same + -- adding parameters is ok, but removing params is not (gives nil error) + eq(old_e.since, new_e.since, old_e.name) + for i,p in ipairs(old_e.parameters) do + eq(new_e.parameters[i][1], p[1], old_e.name) + end + end + -- Level 0 represents methods from 0.1.5 and earlier, when 'since' was not -- yet defined, and metadata was not filtered of internal keys like 'async'. local function clean_level_0(metadata) @@ -89,8 +98,10 @@ describe("api functions", function() stable = api_level end - local funcs_new = func_table(api) + local funcs_new = name_table(api.functions) + local ui_events_new = name_table(api.ui_events) local funcs_compat = {} + local ui_events_compat = {} for level = compat, stable do local path = ('test/functional/fixtures/api_level_'.. tostring(level)..'.mpack') @@ -119,8 +130,18 @@ describe("api functions", function() filter_function_metadata(funcs_new[f.name])) end end - - funcs_compat[level] = func_table(old_api) + funcs_compat[level] = name_table(old_api.functions) + + -- UI events were formalized in level 3 + if level >= 3 then + for _,e in ipairs(old_api.ui_events) do + local new_e = ui_events_new[e.name] + if new_e ~= nil then + check_ui_event_compatible(e, new_e) + end + end + ui_events_compat[level] = name_table(old_api.ui_events) + end end for _,f in ipairs(api.functions) do @@ -140,9 +161,38 @@ describe("api functions", function() end end elseif f.since > api_level then - error("function "..f.name.." has since value > api_level. ".. - "Please bump NVIM_API_CURRENT and set ".. - "NVIM_API_PRERELEASE to true in CMakeLists.txt.") + if api.version.api_prerelease then + error("New function "..f.name.." should use since value ".. + api_level) + else + error("function "..f.name.." has since value > api_level. ".. + "Bump NVIM_API_CURRENT and set ".. + "NVIM_API_PRERELEASE to true in CMakeLists.txt.") + end + end + end + + for _,e in ipairs(api.ui_events) do + if e.since <= stable then + local e_old = ui_events_compat[e.since][e.name] + if e_old == nil then + local errstr = ("UI event "..e.name.." has too low since value. ".. + "For new events set it to "..(stable+1)..".") + if not api.version.api_prerelease then + errstr = (errstr.." Also bump NVIM_API_CURRENT and set ".. + "NVIM_API_PRERELEASE to true in CMakeLists.txt.") + end + error(errstr) + end + elseif e.since > api_level then + if api.version.api_prerelease then + error("New UI event "..e.name.." should use since value ".. + api_level) + else + error("UI event "..e.name.." has since value > api_level. ".. + "Bump NVIM_API_CURRENT and set ".. + "NVIM_API_PRERELEASE to true in CMakeLists.txt.") + end end end end) @@ -156,6 +206,6 @@ describe("ui_options in metadata", function() local api = helpers.call('api_info') local options = api.ui_options eq({'rgb', 'ext_cmdline', 'ext_popupmenu', - 'ext_tabline', 'ext_wildmenu'}, options) + 'ext_tabline', 'ext_wildmenu', 'ext_linegrid', 'ext_hlstate'}, options) end) end) diff --git a/test/functional/api/vim_spec.lua b/test/functional/api/vim_spec.lua index e4b343c123..a9d137391e 100644 --- a/test/functional/api/vim_spec.lua +++ b/test/functional/api/vim_spec.lua @@ -24,13 +24,25 @@ describe('API', function() before_each(clear) it('validates requests', function() - expect_err('Invalid method: bogus', + -- RPC + expect_err('Invalid method: bogus$', request, 'bogus') - expect_err('Invalid method: … の り 。…', + expect_err('Invalid method: … の り 。…$', request, '… の り 。…') - expect_err('Invalid method: <empty>', + expect_err('Invalid method: <empty>$', request, '') - expect_err("can't serialize object", + + -- Non-RPC: rpcrequest(v:servername) uses internal channel. + expect_err('Invalid method: … の り 。…$', + request, 'nvim_eval', + [=[rpcrequest(sockconnect('pipe', v:servername, {'rpc':1}), '… の り 。…')]=]) + expect_err('Invalid method: bogus$', + request, 'nvim_eval', + [=[rpcrequest(sockconnect('pipe', v:servername, {'rpc':1}), 'bogus')]=]) + + -- XXX: This must be the last one, else next one will fail: + -- "Packer instance already working. Use another Packer ..." + expect_err("can't serialize object$", request, nil) end) @@ -158,7 +170,7 @@ describe('API', function() end) it("VimL error: returns error details, does NOT update v:errmsg", function() - expect_err('E121: Undefined variable: bogus', request, + expect_err('E121: Undefined variable: bogus$', request, 'nvim_eval', 'bogus expression') eq('', eval('v:errmsg')) -- v:errmsg was not updated. end) @@ -173,7 +185,7 @@ describe('API', function() end) it("VimL validation error: returns specific error, does NOT update v:errmsg", function() - expect_err('E117: Unknown function: bogus function', request, + expect_err('E117: Unknown function: bogus function$', request, 'nvim_call_function', 'bogus function', {'arg1'}) expect_err('E119: Not enough arguments for function: atan', request, 'nvim_call_function', 'atan', {}) @@ -182,11 +194,11 @@ describe('API', function() end) it("VimL error: returns error details, does NOT update v:errmsg", function() - expect_err('E808: Number or Float required', request, + expect_err('E808: Number or Float required$', request, 'nvim_call_function', 'atan', {'foo'}) - expect_err('Invalid channel stream "xxx"', request, + expect_err('Invalid channel stream "xxx"$', request, 'nvim_call_function', 'chanclose', {999, 'xxx'}) - expect_err('E900: Invalid channel id', request, + expect_err('E900: Invalid channel id$', request, 'nvim_call_function', 'chansend', {999, 'foo'}) eq('', eval('v:exception')) eq('', eval('v:errmsg')) -- v:errmsg was not updated. @@ -198,7 +210,7 @@ describe('API', function() throw 'wtf' endfunction ]]) - expect_err('wtf', request, + expect_err('wtf$', request, 'nvim_call_function', 'Foo', {}) eq('', eval('v:exception')) eq('', eval('v:errmsg')) -- v:errmsg was not updated. @@ -212,7 +224,7 @@ describe('API', function() endfunction ]]) -- E740 - expect_err('Function called with too many arguments', request, + expect_err('Function called with too many arguments$', request, 'nvim_call_function', 'Foo', too_many_args) end) end) @@ -248,23 +260,23 @@ describe('API', function() it('validates args', function() command('let g:d={"baz":"zub","meep":[]}') - expect_err('Not found: bogus', request, + expect_err('Not found: bogus$', request, 'nvim_call_dict_function', 'g:d', 'bogus', {1,2}) - expect_err('Not a function: baz', request, + expect_err('Not a function: baz$', request, 'nvim_call_dict_function', 'g:d', 'baz', {1,2}) - expect_err('Not a function: meep', request, + expect_err('Not a function: meep$', request, 'nvim_call_dict_function', 'g:d', 'meep', {1,2}) - expect_err('E117: Unknown function: f', request, + expect_err('E117: Unknown function: f$', request, 'nvim_call_dict_function', { f = '' }, 'f', {1,2}) - expect_err('Not a function: f', request, + expect_err('Not a function: f$', request, 'nvim_call_dict_function', "{ 'f': '' }", 'f', {1,2}) - expect_err('dict argument type must be String or Dictionary', request, + expect_err('dict argument type must be String or Dictionary$', request, 'nvim_call_dict_function', 42, 'f', {1,2}) - expect_err('Failed to evaluate dict expression', request, + expect_err('Failed to evaluate dict expression$', request, 'nvim_call_dict_function', 'foo', 'f', {1,2}) - expect_err('dict not found', request, + expect_err('dict not found$', request, 'nvim_call_dict_function', '42', 'f', {1,2}) - expect_err('Invalid %(empty%) function name', request, + expect_err('Invalid %(empty%) function name$', request, 'nvim_call_dict_function', "{ 'f': '' }", '', {1,2}) end) end) @@ -337,7 +349,7 @@ describe('API', function() eq(1, funcs.exists('g:lua')) meths.del_var('lua') eq(0, funcs.exists('g:lua')) - eq({false, 'Key does not exist: lua'}, meth_pcall(meths.del_var, 'lua')) + eq({false, "Key not found: lua"}, meth_pcall(meths.del_var, 'lua')) meths.set_var('lua', 1) command('lockvar lua') eq({false, 'Key is locked: lua'}, meth_pcall(meths.del_var, 'lua')) @@ -829,10 +841,6 @@ describe('API', function() end) it('works for job channel', function() - if iswin() and os.getenv('APPVEYOR') ~= nil then - pending("jobstart(['cat']) unreliable on appveyor") - return - end eq(3, eval("jobstart(['cat'], {'rpc': v:true})")) local info = { stream='job', @@ -948,7 +956,7 @@ describe('API', function() } local status, err = pcall(meths.call_atomic, req) eq(false, status) - ok(err:match(' All items in calls array must be arrays of size 2') ~= nil) + ok(err:match('Items in calls array must be arrays of size 2') ~= nil) -- call before was done, but not after eq(1, meths.get_var('avar')) @@ -958,7 +966,7 @@ describe('API', function() } status, err = pcall(meths.call_atomic, req) eq(false, status) - ok(err:match('All items in calls array must be arrays') ~= nil) + ok(err:match('Items in calls array must be arrays') ~= nil) eq({2,3}, meths.get_var('bvar')) req = { @@ -1242,6 +1250,8 @@ describe('API', function() ext_popupmenu = false, ext_tabline = false, ext_wildmenu = false, + ext_linegrid = screen._options.ext_linegrid or false, + ext_hlstate=false, height = 4, rgb = true, width = 20, @@ -1252,18 +1262,9 @@ describe('API', function() screen:detach() screen = Screen.new(44, 99) screen:attach({ rgb = false }) - expected = { - { - chan = 1, - ext_cmdline = false, - ext_popupmenu = false, - ext_tabline = false, - ext_wildmenu = false, - height = 99, - rgb = false, - width = 44, - } - } + expected[1].rgb = false + expected[1].width = 44 + expected[1].height = 99 eq(expected, nvim("list_uis")) end) end) diff --git a/test/functional/api/window_spec.lua b/test/functional/api/window_spec.lua index d404ef5426..27d7aa11b4 100644 --- a/test/functional/api/window_spec.lua +++ b/test/functional/api/window_spec.lua @@ -169,7 +169,7 @@ describe('api/win', function() eq(1, funcs.exists('w:lua')) curwinmeths.del_var('lua') eq(0, funcs.exists('w:lua')) - eq({false, 'Key does not exist: lua'}, meth_pcall(curwinmeths.del_var, 'lua')) + eq({false, 'Key not found: lua'}, meth_pcall(curwinmeths.del_var, 'lua')) curwinmeths.set_var('lua', 1) command('lockvar w:lua') eq({false, 'Key is locked: lua'}, meth_pcall(curwinmeths.del_var, 'lua')) diff --git a/test/functional/core/channels_spec.lua b/test/functional/core/channels_spec.lua index f79f208b69..7b89172f92 100644 --- a/test/functional/core/channels_spec.lua +++ b/test/functional/core/channels_spec.lua @@ -192,7 +192,6 @@ describe('channels', function() end) it('can use buffered output mode', function() - if helpers.pending_win32(pending) then return end source([[ let g:job_opts = { \ 'on_stdout': function('OnEvent'), @@ -225,7 +224,6 @@ describe('channels', function() end) it('can use buffered output mode with no stream callback', function() - if helpers.pending_win32(pending) then return end source([[ function! OnEvent(id, data, event) dict call rpcnotify(1, a:event, a:id, a:data, self.stdout) diff --git a/test/functional/core/job_spec.lua b/test/functional/core/job_spec.lua index 4a21444ee0..eb02610df0 100644 --- a/test/functional/core/job_spec.lua +++ b/test/functional/core/job_spec.lua @@ -17,6 +17,7 @@ local pathroot = helpers.pathroot local nvim_set = helpers.nvim_set local expect_twostreams = helpers.expect_twostreams local expect_msg_seq = helpers.expect_msg_seq +local expect_err = helpers.expect_err local Screen = require('test.functional.ui.screen') -- Kill process with given pid @@ -115,6 +116,17 @@ describe('jobs', function() ok(string.find(err, "E475: Invalid argument: expected valid directory$") ~= nil) end) + it('produces error when using non-executable `cwd`', function() + if iswin() then return end -- N/A for Windows + + local dir = 'Xtest_not_executable_dir' + mkdir(dir) + funcs.setfperm(dir, 'rw-------') + expect_err('E475: Invalid argument: expected valid directory$', nvim, + 'command', "call jobstart('pwd', {'cwd': '" .. dir .. "'})") + rmdir(dir) + end) + it('returns 0 when it fails to start', function() eq("", eval("v:errmsg")) feed_command("let g:test_jobid = jobstart([])") @@ -417,7 +429,7 @@ describe('jobs', function() \ }) ]]) - screen:expect("{2:E120: Using <SID> not in a script context: s:OnEvent}",nil,nil,nil,true) + screen:expect{any="{2:E120: Using <SID> not in a script context: s:OnEvent}"} end) it('does not repeat output with slow output handlers', function() @@ -663,7 +675,7 @@ describe('jobs', function() call rpcnotify(g:channel, 'wait', jobwait([ \ jobstart('exit 4'), \ jobstart((has('win32') ? 'Start-Sleep 10' : 'sleep 10').'; exit 5'), - \ ], has('win32') ? 3000 : 100)) + \ ], has('win32') ? 6000 : 100)) ]]) eq({'notification', 'wait', {{4, -1}}}, next_msg()) end) @@ -731,7 +743,7 @@ describe('jobs', function() local children retry(nil, nil, function() children = meths.get_proc_children(ppid) - eq(3, #children) + eq((iswin() and 4 or 3), #children) end) -- Assert that nvim_get_proc() sees the children. for _, child_pid in ipairs(children) do diff --git a/test/functional/core/startup_spec.lua b/test/functional/core/startup_spec.lua index 2a67453bce..15121261c7 100644 --- a/test/functional/core/startup_spec.lua +++ b/test/functional/core/startup_spec.lua @@ -4,6 +4,7 @@ local Screen = require('test.functional.ui.screen') local clear = helpers.clear local command = helpers.command local eq = helpers.eq +local eval = helpers.eval local feed = helpers.feed local funcs = helpers.funcs local nvim_prog = helpers.nvim_prog @@ -196,5 +197,10 @@ describe('startup', function() funcs.system({nvim_prog, '-n', '-es' }, { 'set encoding', '' })) end) + + it('does not crash if --embed is given twice', function() + clear{args={'--embed'}} + eq(2, eval('1+1')) + end) end) diff --git a/test/functional/eval/execute_spec.lua b/test/functional/eval/execute_spec.lua index 925e311c7d..af37ab8d55 100644 --- a/test/functional/eval/execute_spec.lua +++ b/test/functional/eval/execute_spec.lua @@ -161,13 +161,13 @@ describe('execute()', function() eq('42', eval('g:mes')) command('let g:mes = execute("echon 13", "silent")') - screen:expect([[ + screen:expect{grid=[[ ^ | ~ | ~ | ~ | | - ]]) + ]], unchanged=true} eq('13', eval('g:mes')) end) diff --git a/test/functional/eval/input_spec.lua b/test/functional/eval/input_spec.lua index 777f49462d..8c65297ac6 100644 --- a/test/functional/eval/input_spec.lua +++ b/test/functional/eval/input_spec.lua @@ -149,14 +149,14 @@ describe('input()', function() {EOB:~ }| {T:Foo>}Bar^ | ]]) - command('redraw!') - screen:expect([[ + command('mode') + screen:expect{grid=[[ | {EOB:~ }| {EOB:~ }| {EOB:~ }| {T:Foo>}Bar^ | - ]]) + ]], reset=true} feed('<BS>') screen:expect([[ | @@ -165,14 +165,14 @@ describe('input()', function() {EOB:~ }| {T:Foo>}Ba^ | ]]) - command('redraw!') - screen:expect([[ + command('mode') + screen:expect{grid=[[ | {EOB:~ }| {EOB:~ }| {EOB:~ }| {T:Foo>}Ba^ | - ]]) + ]], reset=true} end) it('allows omitting everything with dictionary argument', function() command('echohl Test') @@ -347,14 +347,14 @@ describe('inputdialog()', function() {EOB:~ }| {T:Foo>}Bar^ | ]]) - command('redraw!') - screen:expect([[ + command('mode') + screen:expect{grid=[[ | {EOB:~ }| {EOB:~ }| {EOB:~ }| {T:Foo>}Bar^ | - ]]) + ]], reset=true} feed('<BS>') screen:expect([[ | @@ -363,14 +363,14 @@ describe('inputdialog()', function() {EOB:~ }| {T:Foo>}Ba^ | ]]) - command('redraw!') - screen:expect([[ + command('mode') + screen:expect{grid=[[ | {EOB:~ }| {EOB:~ }| {EOB:~ }| {T:Foo>}Ba^ | - ]]) + ]], reset=true} end) it('allows omitting everything with dictionary argument', function() command('echohl Test') diff --git a/test/functional/eval/special_vars_spec.lua b/test/functional/eval/special_vars_spec.lua index b5773a5529..97a12d490d 100644 --- a/test/functional/eval/special_vars_spec.lua +++ b/test/functional/eval/special_vars_spec.lua @@ -174,5 +174,17 @@ describe('Special values', function() command('let count = []') -- v:count is readonly eq(1, eval('count is# g:["count"]')) end) + it('v:errmsg is distinct from errmsg', function() + command('let errmsg = 1') + eq(1, eval('errmsg is# g:["errmsg"]')) + end) + it('v:shell_error is distinct from shell_error', function() + command('let shell_error = []') -- v:shell_error is readonly + eq(1, eval('shell_error is# g:["shell_error"]')) + end) + it('v:this_session is distinct from this_session', function() + command('let this_session = []') + eq(1, eval('this_session is# g:["this_session"]')) + end) end) end) diff --git a/test/functional/eval/system_spec.lua b/test/functional/eval/system_spec.lua index 5e12b6a6a4..5cbf34365b 100644 --- a/test/functional/eval/system_spec.lua +++ b/test/functional/eval/system_spec.lua @@ -124,7 +124,6 @@ describe('system()', function() local screen before_each(function() - clear() screen = Screen.new() screen:attach() end) @@ -203,6 +202,38 @@ describe('system()', function() ]]) end) + it('prints verbose information', function() + screen:try_resize(72, 14) + feed(':4verbose echo system("echo hi")<cr>') + if iswin() then + screen:expect{any=[[Executing command: "'cmd.exe' '/s' '/c' '"echo hi"'"]]} + else + screen:expect{any=[[Executing command: "'/[^']*sh' '%-c' 'echo hi'"]]} + end + feed('<cr>') + end) + + it('self and total time recorded separately', function() + local tempfile = helpers.tmpname() + + feed(':function! AlmostNoSelfTime()<cr>') + feed('echo system("echo hi")<cr>') + feed('endfunction<cr>') + + feed(':profile start ' .. tempfile .. '<cr>') + feed(':profile func AlmostNoSelfTime<cr>') + feed(':call AlmostNoSelfTime()<cr>') + feed(':profile dump<cr>') + + feed(':edit ' .. tempfile .. '<cr>') + + local command_total_time = tonumber(helpers.funcs.split(helpers.funcs.getline(7))[2]) + local command_self_time = tonumber(helpers.funcs.split(helpers.funcs.getline(7))[3]) + + helpers.neq(nil, command_total_time) + helpers.neq(nil, command_self_time) + end) + it('`yes` interrupted with CTRL-C', function() feed(':call system("' .. (iswin() and 'for /L %I in (1,0,2) do @echo y' @@ -386,13 +417,12 @@ describe('systemlist()', function() local screen before_each(function() - clear() - screen = Screen.new() - screen:attach() + screen = Screen.new() + screen:attach() end) after_each(function() - screen:detach() + screen:detach() end) it('`echo` and waits for its return', function() diff --git a/test/functional/eval/timer_spec.lua b/test/functional/eval/timer_spec.lua index 2dd9968a01..124b9625c3 100644 --- a/test/functional/eval/timer_spec.lua +++ b/test/functional/eval/timer_spec.lua @@ -1,6 +1,6 @@ local helpers = require('test.functional.helpers')(after_each) local Screen = require('test.functional.ui.screen') -local ok, feed, eq, eval = helpers.ok, helpers.feed, helpers.eq, helpers.eval +local feed, eq, eval = helpers.feed, helpers.eq, helpers.eval local source, nvim_async, run = helpers.source, helpers.nvim_async, helpers.run local clear, command, funcs = helpers.clear, helpers.command, helpers.funcs local curbufmeths = helpers.curbufmeths @@ -72,7 +72,8 @@ describe('timers', function() run(nil, nil, nil, 300) feed("<cr>") local diff = eval("g:val") - count - ok(0 <= diff and diff <= 4) + assert(0 <= diff and diff <= 4, + 'expected (0 <= diff <= 4), got: '..tostring(diff)) end) it('are triggered in blocking getchar() call', function() @@ -81,7 +82,7 @@ describe('timers', function() run(nil, nil, nil, 300) feed("c") local count = eval("g:val") - ok(count >= 4) + assert(count >= 3, 'expected count >= 3, got: '..tostring(count)) eq(99, eval("g:c")) end) @@ -96,6 +97,7 @@ describe('timers', function() source([[ func! AddItem(timer) call nvim_buf_set_lines(0, 2, 2, v:true, ['ITEM 3']) + call getchar(1) redraw endfunc call timer_start(200, 'AddItem') @@ -111,7 +113,6 @@ describe('timers', function() ^ | ]]) - screen:sleep(200) screen:expect([[ ITEM 1 | ITEM 2 | @@ -141,9 +142,10 @@ describe('timers', function() local count = eval("g:val") run(nil, nil, nil, 300) local count2 = eval("g:val") - ok(4 <= count and count <= 7) -- when count is eval:ed after timer_stop this should be non-racy eq(count, count2) + assert(3 <= count and count <= 7, + 'expected (3 <= count <= 7), got: '..tostring(count)) end) it('can be stopped from the handler', function() @@ -170,8 +172,8 @@ describe('timers', function() let g:val2 += 1 endfunc ]]) - command("call timer_start(50, 'MyHandler', {'repeat': 3})") - command("call timer_start(100, 'MyHandler2', {'repeat': 2})") + command("call timer_start(20, 'MyHandler', {'repeat': 3})") + command("call timer_start(40, 'MyHandler2', {'repeat': 2})") run(nil, nil, nil, 300) eq(3,eval("g:val")) eq(2,eval("g:val2")) @@ -197,13 +199,14 @@ describe('timers', function() screen:attach() screen:set_default_attr_ids( {[0] = {bold=true, foreground=255}} ) source([[ + let g:val = 0 func! MyHandler(timer) echo "evil" + let g:val = 1 endfunc ]]) command("call timer_start(100, 'MyHandler', {'repeat': 1})") feed(":good") - screen:sleep(200) screen:expect([[ | {0:~ }| @@ -212,6 +215,17 @@ describe('timers', function() {0:~ }| :good^ | ]]) + + screen:expect{grid=[[ + | + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + :good^ | + ]], intermediate=true, timeout=200} + + eq(1, eval('g:val')) end) end) diff --git a/test/functional/ex_cmds/cmd_map_spec.lua b/test/functional/ex_cmds/cmd_map_spec.lua index 77d025dcc7..67b3ab49d6 100644 --- a/test/functional/ex_cmds/cmd_map_spec.lua +++ b/test/functional/ex_cmds/cmd_map_spec.lua @@ -8,6 +8,7 @@ local eval = helpers.eval local funcs = helpers.funcs local insert = helpers.insert local exc_exec = helpers.exc_exec +local source = helpers.source local Screen = require('test.functional.ui.screen') describe('mappings with <Cmd>', function() @@ -656,7 +657,6 @@ describe('mappings with <Cmd>', function() end) it('works in cmdline mode', function() - cmdmap('<F2>', 'call setcmdpos(2)') feed(':text<F3>') eq('c', eval('m')) -- didn't leave cmdline mode @@ -768,5 +768,43 @@ describe('mappings with <Cmd>', function() end) + it("doesn't crash when invoking cmdline mode recursively #8859", function() + cmdmap('<F2>', 'norm! :foo') + feed(':bar') + screen:expect([[ + some short lines | + of test text | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + :bar^ | + ]]) + + feed('<f2>x') + screen:expect([[ + some short lines | + of test text | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + :barx^ | + ]]) + end) + + it("works with <SID> mappings", function() + source([[ + map <f2> <Cmd>call <SID>do_it()<Cr> + function! s:do_it() + let g:x = 10 + endfunction + ]]) + feed('<f2>') + eq('', eval('v:errmsg')) + eq(10, eval('g:x')) + end) end) diff --git a/test/functional/ex_cmds/ctrl_c_spec.lua b/test/functional/ex_cmds/ctrl_c_spec.lua index 8f76099f79..4c5383b712 100644 --- a/test/functional/ex_cmds/ctrl_c_spec.lua +++ b/test/functional/ex_cmds/ctrl_c_spec.lua @@ -43,7 +43,7 @@ describe("CTRL-C (mapped)", function() feed(":global/^/p<CR>") screen:sleep(ms) feed("<C-C>") - screen:expect([[Interrupt]], nil, nil, nil, true) + screen:expect{any="Interrupt"} end -- The test is time-sensitive. Try different sleep values. diff --git a/test/functional/ex_cmds/digraphs_spec.lua b/test/functional/ex_cmds/digraphs_spec.lua index f2d0b76739..37d3814136 100644 --- a/test/functional/ex_cmds/digraphs_spec.lua +++ b/test/functional/ex_cmds/digraphs_spec.lua @@ -23,13 +23,13 @@ describe(':digraphs', function() it('displays digraphs', function() feed(':digraphs<CR>') screen:expect([[ - A@ {6:Å} 197 E` {6:È} 200 E^ {6:Ê} 202 E" {6:Ë} 203 I` {6:Ì} 204 | - I^ {6:Î} 206 I" {6:Ï} 207 N~ {6:Ñ} 209 O` {6:Ò} 210 O^ {6:Ô} 212 | - O~ {6:Õ} 213 /\ {6:×} 215 U` {6:Ù} 217 U^ {6:Û} 219 Ip {6:Þ} 222 | - a` {6:à} 224 a^ {6:â} 226 a~ {6:ã} 227 a" {6:ä} 228 a@ {6:å} 229 | - e` {6:è} 232 e^ {6:ê} 234 e" {6:ë} 235 i` {6:ì} 236 i^ {6:î} 238 | - n~ {6:ñ} 241 o` {6:ò} 242 o^ {6:ô} 244 o~ {6:õ} 245 u` {6:ù} 249 | - u^ {6:û} 251 y" {6:ÿ} 255 | + E` {6:È} 200 E^ {6:Ê} 202 E" {6:Ë} 203 I` {6:Ì} 204 I^ {6:Î} 206 | + I" {6:Ï} 207 N~ {6:Ñ} 209 O` {6:Ò} 210 O^ {6:Ô} 212 O~ {6:Õ} 213 | + /\ {6:×} 215 U` {6:Ù} 217 U^ {6:Û} 219 Ip {6:Þ} 222 a` {6:à} 224 | + a^ {6:â} 226 a~ {6:ã} 227 a" {6:ä} 228 a@ {6:å} 229 e` {6:è} 232 | + e^ {6:ê} 234 e" {6:ë} 235 i` {6:ì} 236 i^ {6:î} 238 n~ {6:ñ} 241 | + o` {6:ò} 242 o^ {6:ô} 244 o~ {6:õ} 245 u` {6:ù} 249 u^ {6:û} 251 | + y" {6:ÿ} 255 | {3:Press ENTER or type command to continue}^ | ]]) end) diff --git a/test/functional/ex_cmds/oldfiles_spec.lua b/test/functional/ex_cmds/oldfiles_spec.lua index 448326cdfb..e2958c2924 100644 --- a/test/functional/ex_cmds/oldfiles_spec.lua +++ b/test/functional/ex_cmds/oldfiles_spec.lua @@ -9,7 +9,7 @@ local eval = helpers.eval local shada_file = 'Xtest.shada' local function _clear() - set_session(spawn({nvim_prog, '--embed', '-u', 'NONE', + set_session(spawn({nvim_prog, '--embed', '--headless', '-u', 'NONE', -- Need shada for these tests. '-i', shada_file, '--cmd', 'set noswapfile undodir=. directory=. viewdir=. backupdir=. belloff= noshowcmd noruler'})) diff --git a/test/functional/ex_cmds/recover_spec.lua b/test/functional/ex_cmds/recover_spec.lua deleted file mode 100644 index cb68c29b9a..0000000000 --- a/test/functional/ex_cmds/recover_spec.lua +++ /dev/null @@ -1,76 +0,0 @@ -local helpers = require('test.functional.helpers')(after_each) -local lfs = require('lfs') -local feed_command, eq, clear, eval, feed, expect, source = - helpers.feed_command, helpers.eq, helpers.clear, helpers.eval, helpers.feed, - helpers.expect, helpers.source -local command = helpers.command -local ok = helpers.ok -local rmdir = helpers.rmdir - -describe(':recover', function() - before_each(clear) - - it('fails if given a non-existent swapfile', function() - local swapname = 'bogus-swapfile' - feed_command('recover '..swapname) -- This should not segfault. #2117 - eq('E305: No swap file found for '..swapname, eval('v:errmsg')) - end) - -end) - -describe(':preserve', function() - local swapdir = lfs.currentdir()..'/testdir_recover_spec' - before_each(function() - clear() - rmdir(swapdir) - lfs.mkdir(swapdir) - end) - after_each(function() - command('%bwipeout!') - rmdir(swapdir) - end) - - it("saves to custom 'directory' and (R)ecovers (issue #1836)", function() - local testfile = 'testfile_recover_spec' - -- Put swapdir at the start of the 'directory' list. #1836 - -- Note: `set swapfile` *must* go after `set directory`: otherwise it may - -- attempt to create a swapfile in different directory. - local init = [[ - set directory^=]]..swapdir:gsub([[\]], [[\\]])..[[// - set swapfile fileformat=unix undolevels=-1 - ]] - - source(init) - command('edit! '..testfile) - feed('isometext<esc>') - command('preserve') - source('redir => g:swapname | silent swapname | redir END') - - local swappath1 = eval('g:swapname') - - --TODO(justinmk): this is an ugly hack to force `helpers` to support - --multiple sessions. - local nvim2 = helpers.spawn({helpers.nvim_prog, '-u', 'NONE', '-i', 'NONE', '--embed'}, - true) - helpers.set_session(nvim2) - - source(init) - - -- Use the "SwapExists" event to choose the (R)ecover choice at the dialog. - command('autocmd SwapExists * let v:swapchoice = "r"') - command('silent edit! '..testfile) - source('redir => g:swapname | silent swapname | redir END') - - local swappath2 = eval('g:swapname') - - expect('sometext') - -- swapfile from session 1 should end in .swp - eq(testfile..'.swp', string.match(swappath1, '[^%%]+$')) - -- swapfile from session 2 should end in .swo - eq(testfile..'.swo', string.match(swappath2, '[^%%]+$')) - -- Verify that :swapname was not truncated (:help 'shortmess'). - ok(nil == string.find(swappath1, '%.%.%.')) - ok(nil == string.find(swappath2, '%.%.%.')) - end) - -end) diff --git a/test/functional/ex_cmds/sign_spec.lua b/test/functional/ex_cmds/sign_spec.lua index df0f5db860..9d08a66625 100644 --- a/test/functional/ex_cmds/sign_spec.lua +++ b/test/functional/ex_cmds/sign_spec.lua @@ -7,7 +7,7 @@ describe('sign', function() describe('without specifying buffer', function() it('deletes the sign from all buffers', function() -- place a sign with id 34 to first buffer - nvim('command', 'sign define Foo text=+ texthl=Delimiter linehl=Comment') + nvim('command', 'sign define Foo text=+ texthl=Delimiter linehl=Comment numhl=Number') local buf1 = nvim('eval', 'bufnr("%")') nvim('command', 'sign place 34 line=3 name=Foo buffer='..buf1) -- create a second buffer and place the sign on it as well diff --git a/test/functional/ex_cmds/swapfile_preserve_recover_spec.lua b/test/functional/ex_cmds/swapfile_preserve_recover_spec.lua new file mode 100644 index 0000000000..577a26178a --- /dev/null +++ b/test/functional/ex_cmds/swapfile_preserve_recover_spec.lua @@ -0,0 +1,154 @@ +local Screen = require('test.functional.ui.screen') +local helpers = require('test.functional.helpers')(after_each) +local lfs = require('lfs') +local feed_command, eq, eval, expect, source = + helpers.feed_command, helpers.eq, helpers.eval, helpers.expect, helpers.source +local clear = helpers.clear +local command = helpers.command +local feed = helpers.feed +local nvim_prog = helpers.nvim_prog +local ok = helpers.ok +local rmdir = helpers.rmdir +local set_session = helpers.set_session +local spawn = helpers.spawn + +describe(':recover', function() + before_each(clear) + + it('fails if given a non-existent swapfile', function() + local swapname = 'bogus-swapfile' + feed_command('recover '..swapname) -- This should not segfault. #2117 + eq('E305: No swap file found for '..swapname, eval('v:errmsg')) + end) + +end) + +describe(':preserve', function() + local swapdir = lfs.currentdir()..'/Xtest_recover_dir' + before_each(function() + clear() + rmdir(swapdir) + lfs.mkdir(swapdir) + end) + after_each(function() + command('%bwipeout!') + rmdir(swapdir) + end) + + it("saves to custom 'directory' and (R)ecovers #1836", function() + local testfile = 'Xtest_recover_file1' + -- Put swapdir at the start of the 'directory' list. #1836 + -- Note: `set swapfile` *must* go after `set directory`: otherwise it may + -- attempt to create a swapfile in different directory. + local init = [[ + set directory^=]]..swapdir:gsub([[\]], [[\\]])..[[// + set swapfile fileformat=unix undolevels=-1 + ]] + + source(init) + command('edit! '..testfile) + feed('isometext<esc>') + command('preserve') + source('redir => g:swapname | silent swapname | redir END') + + local swappath1 = eval('g:swapname') + + -- Start another Nvim instance. + local nvim2 = spawn({nvim_prog, '-u', 'NONE', '-i', 'NONE', '--embed'}, + true) + set_session(nvim2) + + source(init) + + -- Use the "SwapExists" event to choose the (R)ecover choice at the dialog. + command('autocmd SwapExists * let v:swapchoice = "r"') + command('silent edit! '..testfile) + source('redir => g:swapname | silent swapname | redir END') + + local swappath2 = eval('g:swapname') + + expect('sometext') + -- swapfile from session 1 should end in .swp + eq(testfile..'.swp', string.match(swappath1, '[^%%]+$')) + -- swapfile from session 2 should end in .swo + eq(testfile..'.swo', string.match(swappath2, '[^%%]+$')) + -- Verify that :swapname was not truncated (:help 'shortmess'). + ok(nil == string.find(swappath1, '%.%.%.')) + ok(nil == string.find(swappath2, '%.%.%.')) + end) + +end) + +describe('swapfile detection', function() + local swapdir = lfs.currentdir()..'/Xtest_swapdialog_dir' + before_each(function() + clear() + rmdir(swapdir) + lfs.mkdir(swapdir) + end) + after_each(function() + command('%bwipeout!') + rmdir(swapdir) + end) + + it('always show swapfile dialog #8840 #9027', function() + local testfile = 'Xtest_swapdialog_file1' + -- Put swapdir at the start of the 'directory' list. #1836 + -- Note: `set swapfile` *must* go after `set directory`: otherwise it may + -- attempt to create a swapfile in different directory. + local init = [[ + set directory^=]]..swapdir:gsub([[\]], [[\\]])..[[// + set swapfile fileformat=unix undolevels=-1 hidden + ]] + + local expected_no_dialog = '^'..(' '):rep(256)..'|\n' + for _=1,37 do + expected_no_dialog = expected_no_dialog..'~'..(' '):rep(255)..'|\n' + end + expected_no_dialog = expected_no_dialog..testfile..(' '):rep(216)..'0,0-1 All|\n' + expected_no_dialog = expected_no_dialog..(' '):rep(256)..'|\n' + + source(init) + command('edit! '..testfile) + feed('isometext<esc>') + command('preserve') + + -- Start another Nvim instance. + local nvim2 = spawn({nvim_prog, '-u', 'NONE', '-i', 'NONE', '--embed'}, + true) + set_session(nvim2) + local screen2 = Screen.new(256, 40) + screen2:attach() + source(init) + + -- With shortmess+=F + command('set shortmess+=F') + feed(':edit '..testfile..'<CR>') + screen2:expect{any=[[E325: ATTENTION.*]]..'\n'..[[Found a swap file by the name ".*]] + ..[[Xtest_swapdialog_dir[/\].*]]..testfile..[[%.swp"]]} + feed('e') -- Chose "Edit" at the swap dialog. + screen2:expect(expected_no_dialog) + + -- With :silent and shortmess+=F + feed(':silent edit %<CR>') + screen2:expect{any=[[Found a swap file by the name ".*]] + ..[[Xtest_swapdialog_dir[/\].*]]..testfile..[[%.swp"]]} + feed('e') -- Chose "Edit" at the swap dialog. + screen2:expect(expected_no_dialog) + + -- With :silent! and shortmess+=F + feed(':silent! edit %<CR>') + screen2:expect{any=[[Found a swap file by the name ".*]] + ..[[Xtest_swapdialog_dir[/\].*]]..testfile..[[%.swp"]]} + feed('e') -- Chose "Edit" at the swap dialog. + screen2:expect(expected_no_dialog) + + -- With API (via eval/VimL) call and shortmess+=F + feed(':call nvim_command("edit %")<CR>') + screen2:expect{any=[[Found a swap file by the name ".*]] + ..[[Xtest_swapdialog_dir[/\].*]]..testfile..[[%.swp"]]} + feed('e') -- Chose "Edit" at the swap dialog. + feed('<c-c>') + screen2:expect(expected_no_dialog) + end) +end) diff --git a/test/functional/helpers.lua b/test/functional/helpers.lua index a6d2764187..db90cb559f 100644 --- a/test/functional/helpers.lua +++ b/test/functional/helpers.lua @@ -18,6 +18,7 @@ local expect_err = global_helpers.expect_err local filter = global_helpers.filter local map = global_helpers.map local matches = global_helpers.matches +local near = global_helpers.near local neq = global_helpers.neq local ok = global_helpers.ok local read_file = global_helpers.read_file @@ -299,8 +300,10 @@ end -- Calls fn() until it succeeds, up to `max` times or until `max_ms` -- milliseconds have passed. local function retry(max, max_ms, fn) + assert(max == nil or max > 0) + assert(max_ms == nil or max_ms > 0) local tries = 1 - local timeout = (max_ms and max_ms > 0) and max_ms or 10000 + local timeout = (max_ms and max_ms or 10000) local start_time = luv.now() while true do local status, result = pcall(fn) @@ -329,6 +332,7 @@ local function clear(...) local new_args local env = nil local opts = select(1, ...) + local headless = true if type(opts) == 'table' then if opts.env then local env_tbl = {} @@ -354,9 +358,15 @@ local function clear(...) end end new_args = opts.args or {} + if opts.headless == false then + headless = false + end else new_args = {...} end + if headless then + table.insert(args, '--headless') + end for _, arg in ipairs(new_args) do table.insert(args, arg) end @@ -699,6 +709,7 @@ local module = { meths = meths, missing_provider = missing_provider, mkdir = lfs.mkdir, + near = near, neq = neq, new_pipename = new_pipename, next_msg = next_msg, diff --git a/test/functional/legacy/003_cindent_spec.lua b/test/functional/legacy/003_cindent_spec.lua index 1cede8a7d7..061904c42f 100644 --- a/test/functional/legacy/003_cindent_spec.lua +++ b/test/functional/legacy/003_cindent_spec.lua @@ -4754,4 +4754,21 @@ describe('cindent', function() 4 /* end of define */]=]) end) + + it('* immediately follows comment / vim-patch 8.0.1291', function() + insert_([=[ + { + a = second/*bug*/*line; + }]=]) + + feed_command('set cin cino&') + feed_command('/a = second') + feed('ox') + + expect([=[ + { + a = second/*bug*/*line; + x + }]=]) + end) end) diff --git a/test/functional/legacy/008_autocommands_spec.lua b/test/functional/legacy/008_autocommands_spec.lua index 453638ce45..939404cb5e 100644 --- a/test/functional/legacy/008_autocommands_spec.lua +++ b/test/functional/legacy/008_autocommands_spec.lua @@ -2,9 +2,9 @@ -- Test for BufUnload autocommand that unloads all other buffers. local helpers = require('test.functional.helpers')(after_each) -local feed, source = helpers.feed, helpers.source -local clear, feed_command, expect, eq, eval = helpers.clear, helpers.feed_command, helpers.expect, helpers.eq, helpers.eval -local write_file, wait, dedent = helpers.write_file, helpers.wait, helpers.dedent +local source = helpers.source +local clear, command, expect, eq, eval = helpers.clear, helpers.command, helpers.expect, helpers.eq, helpers.eval +local write_file, dedent = helpers.write_file, helpers.dedent local read_file = helpers.read_file describe('autocommands that delete and unload buffers:', function() @@ -26,29 +26,25 @@ describe('autocommands that delete and unload buffers:', function() before_each(clear) it('BufWritePre, BufUnload', function() - feed_command('au BufWritePre Xxx1 bunload') - feed_command('au BufWritePre Xxx2 bwipe') - feed_command('e Xxx2') + command('au BufWritePre Xxx1 bunload') + command('au BufWritePre Xxx2 bwipe') + command('e Xxx2') eq('Xxx2', eval('bufname("%")')) - feed_command('e Xxx1') + command('e Xxx1') eq('Xxx1', eval('bufname("%")')) -- The legacy test file did not check the error message. - feed_command('let v:errmsg = "no error"') - feed_command('write') - -- Discard all "hit enter" prompts and messages. - feed('<C-L>') + command('let v:errmsg = "no error"') + command('silent! write') eq('E203: Autocommands deleted or unloaded buffer to be written', eval('v:errmsg')) eq('Xxx2', eval('bufname("%")')) expect(text2) -- Start editing Xxx2. - feed_command('e! Xxx2') + command('e! Xxx2') -- The legacy test file did not check the error message. - feed_command('let v:errmsg = "no error"') + command('let v:errmsg = "no error"') -- Write Xxx2, will delete the buffer and give an error msg. - feed_command('w') - -- Discard all "hit enter" prompts and messages. - feed('<C-L>') + command('silent! write') eq('E203: Autocommands deleted or unloaded buffer to be written', eval('v:errmsg')) eq('Xxx1', eval('bufname("%")')) @@ -75,18 +71,11 @@ describe('autocommands that delete and unload buffers:', function() au BufUnload * call CloseAll() au VimLeave * call WriteToOut() ]]) - feed_command('e Xxx2') - -- Discard all "hit enter" prompts and messages. - feed('<C-L>') - feed_command('e Xxx1') - -- Discard all "hit enter" prompts and messages. - feed('<C-L>') - feed_command('e Makefile') -- an existing file - feed('<C-L>') - feed_command('sp new2') - feed('<C-L>') - feed_command('q') - wait() + command('silent! edit Xxx2') + command('silent! edit Xxx1') + command('silent! edit Makefile') -- an existing file + command('silent! split new2') + command('silent! quit') eq('VimLeave done', string.match(read_file(test_file), "^%s*(.-)%s*$")) end) diff --git a/test/functional/legacy/011_autocommands_spec.lua b/test/functional/legacy/011_autocommands_spec.lua index 379646b2ba..7cc31dc787 100644 --- a/test/functional/legacy/011_autocommands_spec.lua +++ b/test/functional/legacy/011_autocommands_spec.lua @@ -18,6 +18,7 @@ local clear, feed_command, expect, eq, neq, dedent, write_file, feed = helpers.clear, helpers.feed_command, helpers.expect, helpers.eq, helpers.neq, helpers.dedent, helpers.write_file, helpers.feed local iswin = helpers.iswin +local read_file = helpers.read_file local function has_gzip() local null = iswin() and 'nul' or '/dev/null' @@ -60,7 +61,7 @@ describe('file reading, writing and bufnew and filter autocommands', function() os.remove('test.out') end) - if iswin() or not has_gzip() then + if not has_gzip() then pending('skipped (missing `gzip` utility)', function() end) else @@ -77,7 +78,7 @@ describe('file reading, writing and bufnew and filter autocommands', function() it('BufReadPre, BufReadPost (using gzip)', function() prepare_gz_file('Xtestfile', text1) - local gzip_data = io.open('Xtestfile.gz'):read('*all') + local gzip_data = read_file('Xtestfile.gz') feed_command('let $GZIP = ""') -- Setup autocommands to decompress before reading and re-compress afterwards. feed_command("au BufReadPre *.gz exe '!gzip -d ' . shellescape(expand('<afile>'))") @@ -91,7 +92,7 @@ describe('file reading, writing and bufnew and filter autocommands', function() -- Expect the decompressed file in the buffer. expect(text1) -- Expect the original file to be unchanged. - eq(gzip_data, io.open('Xtestfile.gz'):read('*all')) + eq(gzip_data, read_file('Xtestfile.gz')) end) -- luacheck: ignore 621 (Indentation) @@ -142,7 +143,6 @@ describe('file reading, writing and bufnew and filter autocommands', function() end) it('FilterReadPre, FilterReadPost', function() - if helpers.pending_win32(pending) then return end -- Write a special input file for this test block. write_file('test.out', dedent([[ startstart diff --git a/test/functional/legacy/packadd_spec.lua b/test/functional/legacy/packadd_spec.lua index fb308475c0..7c3d48317b 100644 --- a/test/functional/legacy/packadd_spec.lua +++ b/test/functional/legacy/packadd_spec.lua @@ -15,7 +15,7 @@ describe('packadd', function() source([=[ func SetUp() - let s:topdir = expand(expand('%:p:h') . '/Xdir') + let s:topdir = expand(getcwd() . '/Xdir') exe 'set packpath=' . s:topdir let s:plugdir = expand(s:topdir . '/pack/mine/opt/mytest') endfunc @@ -58,6 +58,24 @@ describe('packadd', function() call assert_fails("packadd", 'E471:') endfunc + func Test_packadd_start() + let plugdir = expand(s:topdir . '/pack/mine/start/other') + call mkdir(plugdir . '/plugin', 'p') + set rtp& + let rtp = &rtp + filetype on + + exe 'split ' . plugdir . '/plugin/test.vim' + call setline(1, 'let g:plugin_works = 24') + wq + + packadd other + + call assert_equal(24, g:plugin_works) + call assert_true(len(&rtp) > len(rtp)) + call assert_true(&rtp =~ (escape(plugdir, '\') . '\($\|,\)')) + endfunc + func Test_packadd_noload() call mkdir(s:plugdir . '/plugin', 'p') call mkdir(s:plugdir . '/syntax', 'p') @@ -286,6 +304,11 @@ describe('packadd', function() expected_empty() end) + it('loads packages from "start" directory', function() + call('Test_packadd_start') + expected_empty() + end) + describe('command line completion', function() local Screen = require('test.functional.ui.screen') local screen diff --git a/test/functional/legacy/search_spec.lua b/test/functional/legacy/search_spec.lua index 277d8d6c7f..3ed06a22e7 100644 --- a/test/functional/legacy/search_spec.lua +++ b/test/functional/legacy/search_spec.lua @@ -92,6 +92,7 @@ describe('search cmdline', function() 9 {inc:the}se | /the^ | ]]) + screen.bell = false feed('<C-G>') if wrapscan == 'wrapscan' then screen:expect([[ @@ -100,11 +101,13 @@ describe('search cmdline', function() /the^ | ]]) else - screen:expect([[ + screen:expect{grid=[[ 8 them | 9 {inc:the}se | /the^ | - ]]) + ]], condition=function() + eq(true, screen.bell) + end} feed('<CR>') eq({0, 0, 0, 0}, funcs.getpos('"')) end @@ -120,6 +123,7 @@ describe('search cmdline', function() 10 foobar | ?the^ | ]]) + screen.bell = false if wrapscan == 'wrapscan' then feed('<C-G>') screen:expect([[ @@ -135,11 +139,13 @@ describe('search cmdline', function() ]]) else feed('<C-G>') - screen:expect([[ + screen:expect{grid=[[ 9 {inc:the}se | 10 foobar | ?the^ | - ]]) + ]], condition=function() + eq(true, screen.bell) + end} feed('<CR>') screen:expect([[ 9 ^these | @@ -173,6 +179,7 @@ describe('search cmdline', function() 3 the | ?the^ | ]]) + screen.bell = false feed('<C-T>') if wrapscan == 'wrapscan' then screen:expect([[ @@ -181,11 +188,13 @@ describe('search cmdline', function() ?the^ | ]]) else - screen:expect([[ + screen:expect{grid=[[ 2 {inc:the}se | 3 the | ?the^ | - ]]) + ]], condition=function() + eq(true, screen.bell) + end} end end diff --git a/test/functional/normal/put_spec.lua b/test/functional/normal/put_spec.lua index 148a35ec6b..29cef13e39 100644 --- a/test/functional/normal/put_spec.lua +++ b/test/functional/normal/put_spec.lua @@ -874,20 +874,23 @@ describe('put command', function() local function bell_test(actions, should_ring) local screen = Screen.new() screen:attach() + if should_ring then + -- check bell is not set by nvim before the action + screen:sleep(50) + end helpers.ok(not screen.bell and not screen.visualbell) actions() - helpers.wait() - screen:wait(function() + screen:expect{condition=function() if should_ring then if not screen.bell and not screen.visualbell then - return 'Bell was not rung after action' + error('Bell was not rung after action') end else if screen.bell or screen.visualbell then - return 'Bell was rung after action' + error('Bell was rung after action') end end - end) + end, unchanged=(not should_ring)} screen:detach() end diff --git a/test/functional/options/defaults_spec.lua b/test/functional/options/defaults_spec.lua index 787ec366b8..f6f3f02f45 100644 --- a/test/functional/options/defaults_spec.lua +++ b/test/functional/options/defaults_spec.lua @@ -118,6 +118,13 @@ describe('startup defaults', function() clear('-u', 'NORC', '-c', 'syntax off') eq(0, eval('exists("g:syntax_on")')) end) + + it('":if 0|syntax on|endif" does not affect default #8728', function() + clear('-u', 'NORC', '--cmd', ':if 0|syntax on|endif') + eq(1, eval('exists("g:syntax_on")')) + clear('-u', 'NORC', '--cmd', ':if 0|syntax off|endif') + eq(1, eval('exists("g:syntax_on")')) + end) end) describe("'fillchars'", function() diff --git a/test/functional/options/fillchars_spec.lua b/test/functional/options/fillchars_spec.lua index ab61935d4c..99177a11b4 100644 --- a/test/functional/options/fillchars_spec.lua +++ b/test/functional/options/fillchars_spec.lua @@ -36,6 +36,13 @@ describe("'fillchars'", function() ]]) end) it('supports whitespace', function() + screen:expect([[ + ^ | + ~ | + ~ | + ~ | + | + ]]) command('set fillchars=eob:\\ ') screen:expect([[ ^ | diff --git a/test/functional/options/shortmess_spec.lua b/test/functional/options/shortmess_spec.lua index 96823476de..8ea9a19464 100644 --- a/test/functional/options/shortmess_spec.lua +++ b/test/functional/options/shortmess_spec.lua @@ -1,43 +1,96 @@ local helpers = require('test.functional.helpers')(after_each) local Screen = require('test.functional.ui.screen') +local clear = helpers.clear local command = helpers.command -local clear, feed_command = helpers.clear, helpers.feed_command - -if helpers.pending_win32(pending) then return end +local eq = helpers.eq +local eval = helpers.eval +local feed = helpers.feed describe("'shortmess'", function() local screen before_each(function() clear() - screen = Screen.new(25, 5) + screen = Screen.new(42, 5) screen:attach() end) - after_each(function() - screen:detach() - end) - describe('"F" flag', function() - it('hides messages about the files read', function() - command("set shortmess-=F") - feed_command('e test') + it('hides :edit fileinfo messages', function() + command('set hidden') + command('set shortmess-=F') + feed(':edit foo<CR>') + screen:expect([[ + ^ | + ~ | + ~ | + ~ | + "foo" [New File] | + ]]) + eq(1, eval('bufnr("%")')) + + command('set shortmess+=F') + feed(':edit bar<CR>') + screen:expect([[ + ^ | + ~ | + ~ | + ~ | + :edit bar | + ]]) + eq(2, eval('bufnr("%")')) + end) + + it('hides :bnext, :bprevious fileinfo messages', function() + command('set hidden') + command('set shortmess-=F') + feed(':edit foo<CR>') + screen:expect([[ + ^ | + ~ | + ~ | + ~ | + "foo" [New File] | + ]]) + eq(1, eval('bufnr("%")')) + feed(':edit bar<CR>') + screen:expect([[ + ^ | + ~ | + ~ | + ~ | + "bar" [New File] | + ]]) + eq(2, eval('bufnr("%")')) + feed(':bprevious<CR>') + screen:expect([[ + ^ | + ~ | + ~ | + ~ | + "foo" [New file] --No lines in buffer-- | + ]]) + eq(1, eval('bufnr("%")')) + + command('set shortmess+=F') + feed(':bnext<CR>') screen:expect([[ - ^ | - ~ | - ~ | - ~ | - "test" is a directory | + ^ | + ~ | + ~ | + ~ | + :bnext | ]]) - feed_command('set shortmess=F') - feed_command('e test') + eq(2, eval('bufnr("%")')) + feed(':bprevious<CR>') screen:expect([[ - ^ | - ~ | - ~ | - ~ | - :e test | + ^ | + ~ | + ~ | + ~ | + :bprevious | ]]) + eq(1, eval('bufnr("%")')) end) end) end) diff --git a/test/functional/plugin/helpers.lua b/test/functional/plugin/helpers.lua index 2024e6ebbf..4359380bd7 100644 --- a/test/functional/plugin/helpers.lua +++ b/test/functional/plugin/helpers.lua @@ -13,7 +13,7 @@ local function nvim_argv(shada_file) '--cmd', 'set shortmess+=I background=light noswapfile belloff= noshowcmd noruler', '--cmd', 'let &runtimepath=' .. rtp_value, '--cmd', additional_cmd, - '--embed'} + '--embed', '--headless'} if helpers.prepend_argv then return merge_args(helpers.prepend_argv, nvim_args) else diff --git a/test/functional/plugin/msgpack_spec.lua b/test/functional/plugin/msgpack_spec.lua index 4b014cbc73..2ce9974812 100644 --- a/test/functional/plugin/msgpack_spec.lua +++ b/test/functional/plugin/msgpack_spec.lua @@ -581,6 +581,8 @@ describe('autoload/msgpack.vim', function() eval_eq('binary', {'', 'abc', '', '', 'def'}, '"\\nabc\\n\\n\\ndef"') eval_eq('binary', {''}, '""') eval_eq('binary', {'"'}, '"\\""') + eval_eq('binary', {'py3 print(sys.version_info)'}, + '"py3 print(sys.version_info)"') end) it('correctly loads strings', function() @@ -595,6 +597,8 @@ describe('autoload/msgpack.vim', function() eval_eq('string', {'', 'abc', '', '', 'def'}, '="\\nabc\\n\\n\\ndef"') eval_eq('string', {''}, '=""') eval_eq('string', {'"'}, '="\\""') + eval_eq('string', {'py3 print(sys.version_info)'}, + '="py3 print(sys.version_info)"') end) it('correctly loads ext values', function() @@ -625,6 +629,9 @@ describe('autoload/msgpack.vim', function() '+(-1)"\\nabc\\n\\n\\ndef"') eval_eq('ext', {-1, {''}}, '+(-1)""') eval_eq('ext', {-1, {'"'}}, '+(-1)"\\""') + + eval_eq('ext', {42, {'py3 print(sys.version_info)'}}, + '+(42)"py3 print(sys.version_info)"') end) it('correctly loads floats', function() @@ -676,6 +683,13 @@ describe('autoload/msgpack.vim', function() eval_eq('map', {{{_TYPE={}, _VAL={{1, 2}}}, {_TYPE={}, _VAL={{3, 4}}}}, {1, 2}}, '{{1: 2}: {3: 4}, 1: 2}') + + eval_eq('map', {{{_TYPE={}, _VAL={ + {{_TYPE={}, _VAL={'py3 print(sys.version_info)'}}, + 2}}}, + {_TYPE={}, _VAL={{3, 4}}}}, + {1, 2}}, + '{{"py3 print(sys.version_info)": 2}: {3: 4}, 1: 2}') end) it('correctly loads arrays', function() @@ -684,6 +698,9 @@ describe('autoload/msgpack.vim', function() eval_eq('array', {{_TYPE={}, _VAL=1}}, '[TRUE]') eval_eq('array', {{{_TYPE={}, _VAL={{1, 2}}}}, {_TYPE={}, _VAL={{3, 4}}}}, '[[{1: 2}], {3: 4}]') + + eval_eq('array', {{_TYPE={}, _VAL={'py3 print(sys.version_info)'}}}, + '["py3 print(sys.version_info)"]') end) it('errors out when needed', function() @@ -711,6 +728,11 @@ describe('autoload/msgpack.vim', function() exc_exec('call msgpack#eval("0x", {})')) eq('name-unknown:Unknown name FOO: FOO', exc_exec('call msgpack#eval("FOO", {})')) + + eq('name-unknown:Unknown name py3: py3 print(sys.version_info)', + exc_exec('call msgpack#eval("py3 print(sys.version_info)", {})')) + eq('name-unknown:Unknown name o: o', + exc_exec('call msgpack#eval("-info", {})')) end) end) end) diff --git a/test/functional/plugin/shada_spec.lua b/test/functional/plugin/shada_spec.lua index 5a5b4df1ef..1482d83ee6 100644 --- a/test/functional/plugin/shada_spec.lua +++ b/test/functional/plugin/shada_spec.lua @@ -2301,7 +2301,6 @@ describe('plugin/shada.vim', function() describe('event FileWriteCmd', function() it('works', function() - if helpers.pending_win32(pending) then return end nvim('set_var', 'shada#add_own_header', 0) curbuf('set_lines', 0, 1, true, { 'Jump with timestamp ' .. epoch .. ':', diff --git a/test/functional/shada/helpers.lua b/test/functional/shada/helpers.lua index 1312d762d8..d5e061bb50 100644 --- a/test/functional/shada/helpers.lua +++ b/test/functional/shada/helpers.lua @@ -9,9 +9,12 @@ local tmpname = helpers.tmpname() local append_argv = nil local function nvim_argv(shada_file, embed) + if embed == nil then + embed = true + end local argv = {nvim_prog, '-u', 'NONE', '-i', shada_file or tmpname, '-N', '--cmd', 'set shortmess+=I background=light noswapfile', - embed or '--embed'} + '--headless', embed and '--embed' or nil} if helpers.prepend_argv or append_argv then return merge_args(helpers.prepend_argv, argv, append_argv) else diff --git a/test/functional/shada/marks_spec.lua b/test/functional/shada/marks_spec.lua index 4cceae1aa3..c2f6351e00 100644 --- a/test/functional/shada/marks_spec.lua +++ b/test/functional/shada/marks_spec.lua @@ -224,7 +224,7 @@ describe('ShaDa support code', function() it('does not create incorrect file for non-existent buffers when writing from -c', function() add_argv('--cmd', 'silent edit ' .. non_existent_testfilename, '-c', 'qall') - local argv = nvim_argv(nil, '--headless') + local argv = nvim_argv(nil, false) -- no --embed eq('', funcs.system(argv)) eq(0, exc_exec('rshada')) end) @@ -233,7 +233,7 @@ describe('ShaDa support code', function() function() add_argv('-c', 'silent edit ' .. non_existent_testfilename, '-c', 'autocmd VimEnter * qall') - local argv = nvim_argv(nil, '--headless') + local argv = nvim_argv(nil, false) -- no --embed eq('', funcs.system(argv)) eq(0, exc_exec('rshada')) end) diff --git a/test/functional/shada/shada_spec.lua b/test/functional/shada/shada_spec.lua index 720855860a..5f7daf73e5 100644 --- a/test/functional/shada/shada_spec.lua +++ b/test/functional/shada/shada_spec.lua @@ -137,7 +137,7 @@ describe('ShaDa support code', function() it('does not write NONE file', function() local session = spawn({nvim_prog, '-u', 'NONE', '-i', 'NONE', '--embed', - '--cmd', 'qall'}, true) + '--headless', '--cmd', 'qall'}, true) session:close() eq(nil, lfs.attributes('NONE')) eq(nil, lfs.attributes('NONE.tmp.a')) @@ -145,8 +145,8 @@ describe('ShaDa support code', function() it('does not read NONE file', function() write_file('NONE', '\005\001\015\131\161na\162rX\194\162rc\145\196\001-') - local session = spawn({nvim_prog, '-u', 'NONE', '-i', 'NONE', '--embed'}, - true) + local session = spawn({nvim_prog, '-u', 'NONE', '-i', 'NONE', '--embed', + '--headless'}, true) set_session(session) eq('', funcs.getreg('a')) session:close() diff --git a/test/functional/terminal/buffer_spec.lua b/test/functional/terminal/buffer_spec.lua index c290031fbe..4d6b125f9f 100644 --- a/test/functional/terminal/buffer_spec.lua +++ b/test/functional/terminal/buffer_spec.lua @@ -207,18 +207,18 @@ describe('terminal buffer', function() feed_command('terminal') feed('<c-\\><c-n>') feed_command('confirm bdelete') - screen:expect('Close "term://', nil, true, nil, true) + screen:expect{any='Close "term://', attr_ignore=true} end) it('with &confirm', function() feed_command('terminal') feed('<c-\\><c-n>') feed_command('bdelete') - screen:expect('E89', nil, true, nil, true) + screen:expect{any='E89', attr_ignore=true} feed('<cr>') eq('terminal', eval('&buftype')) feed_command('set confirm | bdelete') - screen:expect('Close "term://', nil, true, nil, true) + screen:expect{any='Close "term://', attr_ignore=true} feed('y') neq('terminal', eval('&buftype')) end) @@ -242,7 +242,7 @@ describe('No heap-buffer-overflow when using', function() feed('$') -- Let termopen() modify the buffer feed_command('call termopen("echo")') - wait() + eq(2, eval('1+1')) -- check nvim still running feed_command('bdelete!') end) end) diff --git a/test/functional/terminal/ex_terminal_spec.lua b/test/functional/terminal/ex_terminal_spec.lua index f98add41a0..dbee9bdb49 100644 --- a/test/functional/terminal/ex_terminal_spec.lua +++ b/test/functional/terminal/ex_terminal_spec.lua @@ -54,7 +54,7 @@ describe(':terminal', function() else feed_command([[terminal printf '\e[6n'; sleep 0.5 ]]) end - screen:expect('%^%[%[1;1R', nil, nil, nil, true) + screen:expect{any='%^%[%[1;1R'} end) it("in normal-mode :split does not move cursor", function() diff --git a/test/functional/terminal/scrollback_spec.lua b/test/functional/terminal/scrollback_spec.lua index c665e64a80..1c97441213 100644 --- a/test/functional/terminal/scrollback_spec.lua +++ b/test/functional/terminal/scrollback_spec.lua @@ -396,19 +396,18 @@ describe("'scrollback' option", function() it('set to 0 behaves as 1', function() local screen if iswin() then - screen = thelpers.screen_setup(nil, - "['powershell.exe', '-NoLogo', '-NoProfile', '-NoExit', '-Command', 'function global:prompt {return "..'"$"'.."}']", 30) + screen = thelpers.screen_setup(nil, "['cmd.exe']", 30) else screen = thelpers.screen_setup(nil, "['sh']", 30) end curbufmeths.set_option('scrollback', 0) if iswin() then - feed_data('for($i=1;$i -le 30;$i++){Write-Host \"line$i\"}\r') + feed_data('for /L %I in (1,1,30) do @(echo line%I)\r') else feed_data('for i in $(seq 1 30); do echo "line$i"; done\n') end - screen:expect('line30 ', nil, nil, nil, true) + screen:expect{any='line30 '} retry(nil, nil, function() expect_lines(7) end) screen:detach() @@ -417,8 +416,8 @@ describe("'scrollback' option", function() it('deletes lines (only) if necessary', function() local screen if iswin() then - screen = thelpers.screen_setup(nil, - "['powershell.exe', '-NoLogo', '-NoProfile', '-NoExit', '-Command', 'function global:prompt {return "..'"$"'.."}']", 30) + command([[let $PROMPT='$$']]) + screen = thelpers.screen_setup(nil, "['cmd.exe']", 30) else screen = thelpers.screen_setup(nil, "['sh']", 30) end @@ -426,16 +425,15 @@ describe("'scrollback' option", function() curbufmeths.set_option('scrollback', 200) -- Wait for prompt. - screen:expect('$', nil, nil, nil, true) + screen:expect{any='%$'} - wait() if iswin() then - feed_data('for($i=1;$i -le 30;$i++){Write-Host \"line$i\"}\r') + feed_data('for /L %I in (1,1,30) do @(echo line%I)\r') else feed_data('for i in $(seq 1 30); do echo "line$i"; done\n') end - screen:expect('line30 ', nil, nil, nil, true) + screen:expect{any='line30 '} retry(nil, nil, function() expect_lines(33, 2) end) curbufmeths.set_option('scrollback', 10) @@ -447,17 +445,17 @@ describe("'scrollback' option", function() -- 'scrollback' option is synchronized with the internal sb_buffer. command('sleep 100m') if iswin() then - feed_data('for($i=1;$i -le 40;$i++){Write-Host \"line$i\"}\r') + feed_data('for /L %I in (1,1,40) do @(echo line%I)\r') else feed_data('for i in $(seq 1 40); do echo "line$i"; done\n') end - screen:expect('line40 ', nil, nil, nil, true) + screen:expect{any='line40 '} retry(nil, nil, function() expect_lines(58) end) -- Verify off-screen state - eq('line35', eval("getline(line('w0') - 1)")) - eq('line26', eval("getline(line('w0') - 10)")) + eq((iswin() and 'line36' or 'line35'), eval("getline(line('w0') - 1)")) + eq((iswin() and 'line27' or 'line26'), eval("getline(line('w0') - 10)")) screen:detach() end) diff --git a/test/functional/terminal/tui_spec.lua b/test/functional/terminal/tui_spec.lua index 5603224975..365bd2a0be 100644 --- a/test/functional/terminal/tui_spec.lua +++ b/test/functional/terminal/tui_spec.lua @@ -17,7 +17,6 @@ local nvim_prog = helpers.nvim_prog local nvim_set = helpers.nvim_set local ok = helpers.ok local read_file = helpers.read_file -local wait = helpers.wait if helpers.pending_win32(pending) then return end @@ -207,7 +206,7 @@ describe('tui', function() screen:set_default_attr_ids({ [1] = {reverse = true}, [2] = {foreground = 13, special = Screen.colors.Grey0}, - [3] = {special = Screen.colors.Grey0, bold = true, reverse = true}, + [3] = {bold = true, reverse = true, special = Screen.colors.Grey0}, [4] = {bold = true}, [5] = {special = Screen.colors.Grey0, reverse = true, foreground = 4}, [6] = {foreground = 4, special = Screen.colors.Grey0}, @@ -257,11 +256,11 @@ describe('tui', function() it('shows up in nvim_list_uis', function() feed_data(':echo map(nvim_list_uis(), {k,v -> sort(items(v))})\013') screen:expect([=[ - {5: }| - [[['ext_cmdline', v:false], ['ext_popupmenu', v:fa| - lse], ['ext_tabline', v:false], ['ext_wildmenu', v| - :false], ['height', 6], ['rgb', v:false], ['width'| - , 50]]] | + [[['ext_cmdline', v:false], ['ext_hlstate', v:fals| + e], ['ext_linegrid', v:true], ['ext_popupmenu', v:| + false], ['ext_tabline', v:false], ['ext_wildmenu',| + v:false], ['height', 6], ['rgb', v:false], ['widt| + h', 50]]] | {10:Press ENTER or type command to continue}{1: } | {3:-- TERMINAL --} | ]=]) @@ -371,7 +370,7 @@ describe('tui FocusGained/FocusLost', function() {3:-- TERMINAL --} | ]]) feed_data('\027[O') - screen:expect([[ + screen:expect{grid=[[ | {4:~ }| {4:~ }| @@ -379,7 +378,7 @@ describe('tui FocusGained/FocusLost', function() {5:[No Name] }| :{1: } | {3:-- TERMINAL --} | - ]]) + ]], unchanged=true} end) it('in cmdline-mode', function() @@ -400,7 +399,7 @@ describe('tui FocusGained/FocusLost', function() -- Exit cmdline-mode. Redraws from timers/events are blocked during -- cmdline-mode, so the buffer won't be updated until we exit cmdline-mode. feed_data('\n') - screen:expect('lost'..(' '):rep(46)..'\ngained', nil, nil, nil, true) + screen:expect{any='lost'..(' '):rep(46)..'\ngained'} end) end) @@ -473,14 +472,24 @@ describe("tui 't_Co' (terminal colors)", function() nvim_prog, nvim_set)) - feed_data(":echo &t_Co\n") - wait() local tline if maxcolors == 8 or maxcolors == 16 then tline = "~ " else tline = "{4:~ }" end + + screen:expect(string.format([[ + {1: } | + %s| + %s| + %s| + %s| + | + {3:-- TERMINAL --} | + ]], tline, tline, tline, tline)) + + feed_data(":echo &t_Co\n") screen:expect(string.format([[ {1: } | %s| @@ -740,7 +749,7 @@ describe("tui 'term' option", function() screen.timeout = 250 -- We want screen:expect() to fail quickly. retry(nil, 2 * full_timeout, function() -- Wait for TUI thread to set 'term'. feed_data(":echo 'term='.(&term)\n") - screen:expect('term='..term_expected, nil, nil, nil, true) + screen:expect{any='term='..term_expected} end) end diff --git a/test/functional/terminal/window_split_tab_spec.lua b/test/functional/terminal/window_split_tab_spec.lua index 714c2476ce..fecffe3295 100644 --- a/test/functional/terminal/window_split_tab_spec.lua +++ b/test/functional/terminal/window_split_tab_spec.lua @@ -6,6 +6,8 @@ local feed_command = helpers.feed_command local command = helpers.command local eq = helpers.eq local eval = helpers.eval +local iswin = helpers.iswin +local retry = helpers.retry describe('terminal', function() local screen @@ -66,8 +68,22 @@ describe('terminal', function() end) it('forwards resize request to the program', function() - feed([[<C-\><C-N>:]]) -- Go to cmdline-mode, so cursor is at bottom. - screen:try_resize(screen._width - 3, screen._height - 2) + feed([[<C-\><C-N>G:]]) -- Go to cmdline-mode, so cursor is at bottom. + local w1, h1 = screen._width - 3, screen._height - 2 + local w2, h2 = w1 - 6, h1 - 3 + + if iswin() then + -- win: SIGWINCH is unreliable, use a weaker test. #7506 + retry(3, 30000, function() + screen:try_resize(w1, h1) + screen:expect{any='rows: 7, cols: 47'} + screen:try_resize(w2, h2) + screen:expect{any='rows: 4, cols: 41'} + end) + return + end + + screen:try_resize(w1, h1) screen:expect([[ tty ready | rows: 7, cols: 47 | @@ -78,7 +94,7 @@ describe('terminal', function() | :^ | ]]) - screen:try_resize(screen._width - 6, screen._height - 3) + screen:try_resize(w2, h2) screen:expect([[ tty ready | rows: 7, cols: 47 | 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) diff --git a/test/functional/viml/completion_spec.lua b/test/functional/viml/completion_spec.lua index c14f7fc1a6..70b4717c32 100644 --- a/test/functional/viml/completion_spec.lua +++ b/test/functional/viml/completion_spec.lua @@ -757,7 +757,7 @@ describe('completion', function() eval('1 + 1') -- popupmenu still visible - screen:expect([[ + screen:expect{grid=[[ foobar fooegg | fooegg^ | {1:foobar }{0: }| @@ -766,7 +766,7 @@ describe('completion', function() {0:~ }| {0:~ }| {3:-- Keyword completion (^N^P) }{4:match 1 of 2} | - ]]) + ]], unchanged=true} feed('<c-p>') -- Didn't restart completion: old matches still used @@ -873,107 +873,6 @@ describe('completion', function() {3:-- Keyword Local completion (^N^P) }{4:match 1 of 7} | ]]) end) -end) - -describe('ui/ext_popupmenu', function() - local screen - local items, selected, anchor - 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}, - }) - screen:set_on_event_handler(function(name, data) - if name == "popupmenu_show" then - local row, col - items, selected, row, col = unpack(data) - anchor = {row, col} - elseif name == "popupmenu_select" then - selected = data[1] - elseif name == "popupmenu_hide" then - items = nil - end - end) - 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([[ - | - foo^ | - {1:~ }| - {1:~ }| - {1:~ }| - {1:~ }| - {1:~ }| - {2:-- INSERT --} | - ]], nil, nil, function() - eq(expected, items) - eq(0, selected) - eq({1,0}, anchor) - end) - - feed('<c-p>') - screen:expect([[ - | - ^ | - {1:~ }| - {1:~ }| - {1:~ }| - {1:~ }| - {1:~ }| - {2:-- INSERT --} | - ]], nil, nil, function() - eq(expected, items) - eq(-1, selected) - eq({1,0}, anchor) - end) - - -- down moves the selection in the menu, but does not insert anything - feed('<down><down>') - screen:expect([[ - | - ^ | - {1:~ }| - {1:~ }| - {1:~ }| - {1:~ }| - {1:~ }| - {2:-- INSERT --} | - ]], nil, nil, function() - eq(expected, items) - eq(1, selected) - eq({1,0}, anchor) - end) - - feed('<cr>') - screen:expect([[ - | - bar^ | - {1:~ }| - {1:~ }| - {1:~ }| - {1:~ }| - {1:~ }| - {2:-- INSERT --} | - ]], nil, nil, function() - eq(nil, items) -- popupmenu was hidden - end) - end) it('TextChangedP autocommand', function() curbufmeths.set_lines(0, 1, false, { 'foo', 'bar', 'foobar'}) diff --git a/test/helpers.lua b/test/helpers.lua index a774a67df3..6ef7a456f4 100644 --- a/test/helpers.lua +++ b/test/helpers.lua @@ -1,6 +1,7 @@ local assert = require('luassert') local luv = require('luv') local lfs = require('lfs') +local relpath = require('pl.path').relpath local quote_me = '[^.%w%+%-%@%_%/]' -- complement (needn't quote) local function shell_quote(str) @@ -45,8 +46,8 @@ local check_logs_useless_lines = { ['See README_MISSING_SYSCALL_OR_IOCTL for guidance']=3, } -local function eq(expected, actual) - return assert.are.same(expected, actual) +local function eq(expected, actual, ctx) + return assert.are.same(expected, actual, ctx) end local function neq(expected, actual) return assert.are_not.same(expected, actual) @@ -54,6 +55,9 @@ end local function ok(res) return assert.is_true(res) end +local function near(actual, expected, tolerance) + return assert.is.near(actual, expected, tolerance) +end local function matches(pat, actual) if nil ~= string.match(actual, pat) then return true @@ -133,7 +137,6 @@ local function check_logs() fd:close() os.remove(file) if #lines > 0 then - -- local out = os.getenv('TRAVIS_CI_BUILD') and io.stdout or io.stderr local out = io.stdout out:write(start_msg .. '\n') out:write('= ' .. table.concat(lines, '\n= ') .. '\n') @@ -242,7 +245,7 @@ local function check_cores(app, force) -- Workspace-local $TMPDIR, scrubbed and pattern-escaped. -- "./Xtest-tmpdir/" => "Xtest%-tmpdir" local local_tmpdir = (tmpdir_is_local(tmpdir_get()) - and tmpdir_get():gsub('^[ ./]+',''):gsub('%/+$',''):gsub('([^%w])', '%%%1') + and relpath(tmpdir_get()):gsub('^[ ./]+',''):gsub('%/+$',''):gsub('([^%w])', '%%%1') or nil) local db_cmd if hasenv('NVIM_TEST_CORE_GLOB_DIRECTORY') then @@ -643,8 +646,38 @@ local function hexdump(str) return dump .. hex .. string.rep(" ", 8 - len % 8) .. asc end -local function read_file(name) - local file = io.open(name, 'r') +-- Reads text lines from `filename` into a table. +-- +-- filename: path to file +-- start: start line (1-indexed), negative means "lines before end" (tail) +local function read_file_list(filename, start) + local lnum = (start ~= nil and type(start) == 'number') and start or 1 + local tail = (lnum < 0) + local maxlines = tail and math.abs(lnum) or nil + local file = io.open(filename, 'r') + if not file then + return nil + end + local lines = {} + local i = 1 + for line in file:lines() do + if i >= start then + table.insert(lines, line) + if #lines > maxlines then + table.remove(lines, 1) + end + end + i = i + 1 + end + file:close() + return lines +end + +-- Reads the entire contents of `filename` into a string. +-- +-- filename: path to file +local function read_file(filename) + local file = io.open(filename, 'r') if not file then return nil end @@ -671,6 +704,32 @@ local function write_file(name, text, no_dedent, append) file:close() end +local function isCI() + local is_travis = nil ~= os.getenv('TRAVIS') + local is_appveyor = nil ~= os.getenv('APPVEYOR') + local is_quickbuild = nil ~= os.getenv('PR_NUMBER') + return is_travis or is_appveyor or is_quickbuild +end + +-- Gets the contents of $NVIM_LOG_FILE for printing to the build log. +-- Also removes the file, if the current environment looks like CI. +local function read_nvim_log() + local logfile = os.getenv('NVIM_LOG_FILE') or '.nvimlog' + local keep = isCI() and 999 or 10 + local lines = read_file_list(logfile, -keep) or {} + local log = (('-'):rep(78)..'\n' + ..string.format('$NVIM_LOG_FILE: %s\n', logfile) + ..(#lines > 0 and '(last '..tostring(keep)..' lines)\n' or '(empty)\n')) + for _,line in ipairs(lines) do + log = log..line..'\n' + end + log = log..('-'):rep(78)..'\n' + if isCI() then + os.remove(logfile) + end + return log +end + local module = { REMOVE_THIS = REMOVE_THIS, argss_to_cmd = argss_to_cmd, @@ -694,14 +753,17 @@ local module = { map = map, matches = matches, mergedicts_copy = mergedicts_copy, + near = near, neq = neq, ok = ok, popen_r = popen_r, popen_w = popen_w, read_file = read_file, + read_file_list = read_file_list, + read_nvim_log = read_nvim_log, repeated_read_cmd = repeated_read_cmd, - sleep = sleep, shallowcopy = shallowcopy, + sleep = sleep, table_flatten = table_flatten, tmpname = tmpname, uname = uname, diff --git a/test/unit/os/shell_spec.lua b/test/unit/os/shell_spec.lua index 37274502de..a73fc8e47e 100644 --- a/test/unit/os/shell_spec.lua +++ b/test/unit/os/shell_spec.lua @@ -25,6 +25,7 @@ describe('shell functions', function() local res = cimported.shell_build_argv( cmd and to_cstr(cmd), extra_args and to_cstr(extra_args)) + -- `res` is zero-indexed (C pointer, not Lua table)! local argc = 0 local ret = {} -- Explicitly free everything, so if it is not in allocated memory it will @@ -38,6 +39,26 @@ describe('shell functions', function() return ret end + local function shell_argv_to_str(argv_table) + -- C string array (char **). + local argv = (argv_table + and ffi.new("char*[?]", #argv_table+1) + or NULL) + + local argc = 1 + while argv_table ~= nil and argv_table[argc] ~= nil do + -- `argv` is zero-indexed (C pointer, not Lua table)! + argv[argc - 1] = to_cstr(argv_table[argc]) + argc = argc + 1 + end + if argv_table ~= nil then + argv[argc - 1] = NULL + end + + local res = cimported.shell_argv_to_str(argv) + return ffi.string(res) + end + local function os_system(cmd, input) local input_or = input and to_cstr(input) or NULL local input_len = (input ~= nil) and string.len(input) or 0 @@ -151,4 +172,14 @@ describe('shell functions', function() eq(nil, argv[3]) end) end) + + itp('shell_argv_to_str', function() + eq('', shell_argv_to_str({ nil })) + eq("''", shell_argv_to_str({ '' })) + eq("'foo' '' 'bar'", shell_argv_to_str({ 'foo', '', 'bar' })) + eq("'/bin/sh' '-c' 'abc def'", shell_argv_to_str({'/bin/sh', '-c', 'abc def'})) + eq("'abc def' 'ghi jkl'", shell_argv_to_str({'abc def', 'ghi jkl'})) + eq("'/bin/sh' '-c' 'abc def' '"..('x'):rep(225).."...", + shell_argv_to_str({'/bin/sh', '-c', 'abc def', ('x'):rep(999)})) + end) end) |