aboutsummaryrefslogtreecommitdiff
path: root/test
diff options
context:
space:
mode:
Diffstat (limited to 'test')
-rw-r--r--test/README.md22
-rw-r--r--test/busted/outputHandlers/TAP.lua2
-rw-r--r--test/busted/outputHandlers/nvim.lua46
-rw-r--r--test/functional/api/extmark_spec.lua1477
-rw-r--r--test/functional/api/highlight_spec.lua35
-rw-r--r--test/functional/api/keymap_spec.lua8
-rw-r--r--test/functional/api/menu_spec.lua4
-rw-r--r--test/functional/api/proc_spec.lua2
-rw-r--r--test/functional/api/rpc_fixture.lua14
-rw-r--r--test/functional/api/server_requests_spec.lua27
-rw-r--r--test/functional/api/tabpage_spec.lua4
-rw-r--r--test/functional/api/vim_spec.lua216
-rw-r--r--test/functional/api/window_spec.lua6
-rw-r--r--test/functional/autocmd/autocmd_spec.lua101
-rw-r--r--test/functional/autocmd/cmdline_spec.lua2
-rw-r--r--test/functional/autocmd/tabclose_spec.lua18
-rw-r--r--test/functional/autocmd/tabnewentered_spec.lua563
-rw-r--r--test/functional/autocmd/textyankpost_spec.lua48
-rw-r--r--test/functional/autoread/focus_spec.lua58
-rw-r--r--test/functional/cmdline/ctrl_r_spec.lua2
-rw-r--r--test/functional/cmdline/history_spec.lua4
-rw-r--r--test/functional/core/fileio_spec.lua25
-rw-r--r--test/functional/core/job_spec.lua84
-rw-r--r--test/functional/core/main_spec.lua2
-rw-r--r--test/functional/core/startup_spec.lua60
-rw-r--r--test/functional/eval/api_functions_spec.lua3
-rw-r--r--test/functional/eval/buf_functions_spec.lua6
-rw-r--r--test/functional/eval/ctx_functions_spec.lua2
-rw-r--r--test/functional/eval/environ_spec.lua1
-rw-r--r--test/functional/eval/fnamemodify_spec.lua119
-rw-r--r--test/functional/eval/input_spec.lua2
-rw-r--r--test/functional/eval/let_spec.lua19
-rw-r--r--test/functional/eval/map_functions_spec.lua2
-rw-r--r--test/functional/eval/null_spec.lua6
-rw-r--r--test/functional/eval/sort_spec.lua3
-rw-r--r--test/functional/eval/system_spec.lua21
-rw-r--r--test/functional/eval/timer_spec.lua21
-rw-r--r--test/functional/eval/uniq_spec.lua2
-rw-r--r--test/functional/eval/wait_spec.lua7
-rw-r--r--test/functional/ex_cmds/dict_notifications_spec.lua14
-rw-r--r--test/functional/ex_cmds/drop_spec.lua8
-rw-r--r--test/functional/ex_cmds/echo_spec.lua167
-rw-r--r--test/functional/ex_cmds/highlight_spec.lua4
-rw-r--r--test/functional/ex_cmds/ls_spec.lua12
-rw-r--r--test/functional/ex_cmds/mksession_spec.lua45
-rw-r--r--test/functional/ex_cmds/profile_spec.lua76
-rw-r--r--test/functional/ex_cmds/script_spec.lua4
-rw-r--r--test/functional/ex_cmds/sign_spec.lua4
-rw-r--r--test/functional/ex_cmds/write_spec.lua7
-rw-r--r--test/functional/example_spec.lua38
-rw-r--r--test/functional/fixtures/CMakeLists.txt10
-rw-r--r--test/functional/fixtures/api_level_6.mpackbin25194 -> 25287 bytes
-rw-r--r--test/functional/fixtures/fake-lsp-server.lua450
-rw-r--r--test/functional/fixtures/printenv-test.c6
-rw-r--r--test/functional/helpers.lua62
-rw-r--r--test/functional/insert/insert_spec.lua6
-rw-r--r--test/functional/legacy/021_control_wi_spec.lua2
-rw-r--r--test/functional/legacy/022_line_ending_spec.lua25
-rw-r--r--test/functional/legacy/041_writing_and_reading_hundred_kbyte_spec.lua43
-rw-r--r--test/functional/legacy/045_folding_spec.lua3
-rw-r--r--test/functional/legacy/063_match_and_matchadd_spec.lua20
-rw-r--r--test/functional/legacy/075_maparg_spec.lua6
-rw-r--r--test/functional/legacy/077_mf_hash_grow_spec.lua52
-rw-r--r--test/functional/legacy/084_curswant_spec.lua49
-rw-r--r--test/functional/legacy/095_regexp_multibyte_spec.lua2
-rw-r--r--test/functional/legacy/097_glob_path_spec.lua2
-rw-r--r--test/functional/legacy/098_scrollbind_spec.lua48
-rw-r--r--test/functional/legacy/104_let_assignment_spec.lua54
-rw-r--r--test/functional/legacy/assert_spec.lua62
-rw-r--r--test/functional/legacy/breakindent_spec.lua214
-rw-r--r--test/functional/legacy/delete_spec.lua2
-rw-r--r--test/functional/legacy/eval_spec.lua4
-rw-r--r--test/functional/legacy/expand_spec.lua39
-rw-r--r--test/functional/legacy/memory_usage_spec.lua169
-rw-r--r--test/functional/legacy/prompt_buffer_spec.lua153
-rw-r--r--test/functional/legacy/search_spec.lua34
-rw-r--r--test/functional/lua/api_spec.lua32
-rw-r--r--test/functional/lua/buffer_updates_spec.lua17
-rw-r--r--test/functional/lua/commands_spec.lua99
-rw-r--r--test/functional/lua/luaeval_spec.lua308
-rw-r--r--test/functional/lua/overrides_spec.lua34
-rw-r--r--test/functional/lua/treesitter_spec.lua512
-rw-r--r--test/functional/lua/uri_spec.lua150
-rw-r--r--test/functional/lua/utility_functions_spec.lua291
-rw-r--r--test/functional/lua/vim_spec.lua1235
-rw-r--r--test/functional/normal/jump_spec.lua91
-rw-r--r--test/functional/normal/put_spec.lua10
-rw-r--r--test/functional/normal/tabpage_spec.lua38
-rw-r--r--test/functional/options/chars_spec.lua48
-rw-r--r--test/functional/options/defaults_spec.lua42
-rw-r--r--test/functional/options/keymap_spec.lua2
-rw-r--r--test/functional/options/num_options_spec.lua20
-rw-r--r--test/functional/options/shortmess_spec.lua8
-rw-r--r--test/functional/plugin/health_spec.lua20
-rw-r--r--test/functional/plugin/lsp_spec.lua1620
-rw-r--r--test/functional/plugin/man_spec.lua54
-rw-r--r--test/functional/preload.lua10
-rw-r--r--test/functional/provider/clipboard_spec.lua48
-rw-r--r--test/functional/provider/define_spec.lua15
-rw-r--r--test/functional/provider/nodejs_spec.lua5
-rw-r--r--test/functional/provider/perl_spec.lua80
-rw-r--r--test/functional/provider/python3_spec.lua5
-rw-r--r--test/functional/provider/python_spec.lua5
-rw-r--r--test/functional/provider/ruby_spec.lua5
-rw-r--r--test/functional/shada/errors_spec.lua21
-rw-r--r--test/functional/shada/history_spec.lua10
-rw-r--r--test/functional/shada/variables_spec.lua35
-rw-r--r--test/functional/terminal/buffer_spec.lua46
-rw-r--r--test/functional/terminal/edit_spec.lua6
-rw-r--r--test/functional/terminal/ex_terminal_spec.lua2
-rw-r--r--test/functional/terminal/helpers.lua2
-rw-r--r--test/functional/terminal/highlight_spec.lua57
-rw-r--r--test/functional/terminal/mouse_spec.lua38
-rw-r--r--test/functional/terminal/scrollback_spec.lua32
-rw-r--r--test/functional/terminal/tui_spec.lua285
-rw-r--r--test/functional/terminal/window_split_tab_spec.lua10
-rw-r--r--test/functional/ui/bufhl_spec.lua370
-rw-r--r--test/functional/ui/cmdline_highlight_spec.lua8
-rw-r--r--test/functional/ui/cmdline_spec.lua13
-rw-r--r--test/functional/ui/cursor_spec.lua23
-rw-r--r--test/functional/ui/diff_spec.lua151
-rw-r--r--test/functional/ui/embed_spec.lua3
-rw-r--r--test/functional/ui/float_spec.lua101
-rw-r--r--test/functional/ui/fold_spec.lua115
-rw-r--r--test/functional/ui/highlight_spec.lua141
-rw-r--r--test/functional/ui/hlstate_spec.lua12
-rw-r--r--test/functional/ui/inccommand_spec.lua223
-rw-r--r--test/functional/ui/messages_spec.lua304
-rw-r--r--test/functional/ui/mode_spec.lua53
-rw-r--r--test/functional/ui/mouse_spec.lua30
-rw-r--r--test/functional/ui/multibyte_spec.lua42
-rw-r--r--test/functional/ui/multigrid_spec.lua191
-rw-r--r--test/functional/ui/options_spec.lua47
-rw-r--r--test/functional/ui/output_spec.lua28
-rw-r--r--test/functional/ui/popupmenu_spec.lua286
-rw-r--r--test/functional/ui/screen.lua243
-rw-r--r--test/functional/ui/screen_basic_spec.lua3
-rw-r--r--test/functional/ui/searchhl_spec.lua2
-rw-r--r--test/functional/ui/sign_spec.lua4
-rw-r--r--test/functional/ui/spell_spec.lua35
-rw-r--r--test/functional/ui/syntax_conceal_spec.lua97
-rw-r--r--test/functional/ui/tabline_spec.lua12
-rw-r--r--test/functional/ui/wildmode_spec.lua138
-rw-r--r--test/helpers.lua96
-rw-r--r--test/unit/eval/helpers.lua14
-rw-r--r--test/unit/eval/typval_spec.lua94
-rw-r--r--test/unit/helpers.lua8
-rw-r--r--test/unit/marktree_spec.lua190
-rw-r--r--test/unit/mbyte_spec.lua5
-rw-r--r--test/unit/os/env_spec.lua4
-rw-r--r--test/unit/os/fs_spec.lua2
-rw-r--r--test/unit/path_spec.lua6
-rw-r--r--test/unit/preprocess.lua4
-rw-r--r--test/unit/tui_spec.lua136
154 files changed, 11423 insertions, 2099 deletions
diff --git a/test/README.md b/test/README.md
index 64892b5576..a6e9080a40 100644
--- a/test/README.md
+++ b/test/README.md
@@ -77,11 +77,14 @@ To run all legacy Vim tests:
make oldtest
-To run a *single* legacy test set `TEST_FILE`, for example:
+To run a *single* legacy test file you can use either:
- TEST_FILE=test_syntax.res make oldtest
+ make oldtest TEST_FILE=test_syntax.vim
+
+or:
+
+ make src/nvim/testdir/test_syntax.vim
-- The `.res` extension (instead of `.vim`) is required.
- Specify only the test file name, not the full path.
@@ -126,7 +129,7 @@ end)
To run only test with filter name:
- TEST_TAG='foo.*api' make functionaltest
+ TEST_FILTER='foo.*api' make functionaltest
### Filter by file
@@ -315,11 +318,12 @@ Number; !must be defined to function properly):
- `NVIM_TEST_RUN_TESTTEST` (U) (1): allows running
`test/unit/testtest_spec.lua` used to check how testing infrastructure works.
-- `NVIM_TEST_TRACE_LEVEL` (U) (N): specifies unit tests tracing level: `0`
- disables tracing (the fastest, but you get no data if tests crash and there
- was no core dump generated), `1` or empty/undefined leaves only C function
- cals and returns in the trace (faster then recording everything), `2` records
- all function calls, returns and lua source lines exuecuted.
+- `NVIM_TEST_TRACE_LEVEL` (U) (N): specifies unit tests tracing level:
+ - `0` disables tracing (the fastest, but you get no data if tests crash and
+ there no core dump was generated),
+ - `1` leaves only C function calls and returns in the trace (faster than
+ recording everything),
+ - `2` records all function calls, returns and executed Lua source lines.
- `NVIM_TEST_TRACE_ON_ERROR` (U) (1): makes unit tests yield trace on error in
addition to regular error message.
diff --git a/test/busted/outputHandlers/TAP.lua b/test/busted/outputHandlers/TAP.lua
index 8dc4ff55b6..5de48c0ad3 100644
--- a/test/busted/outputHandlers/TAP.lua
+++ b/test/busted/outputHandlers/TAP.lua
@@ -7,7 +7,7 @@ return function(options)
local handler = require 'busted.outputHandlers.TAP'(options)
local suiteEnd = function()
- io.write(global_helpers.read_nvim_log())
+ io.write(global_helpers.read_nvim_log(nil, true))
return nil, true
end
busted.subscribe({ 'suite', 'end' }, suiteEnd)
diff --git a/test/busted/outputHandlers/nvim.lua b/test/busted/outputHandlers/nvim.lua
index 1b500fc999..5456e9ca98 100644
--- a/test/busted/outputHandlers/nvim.lua
+++ b/test/busted/outputHandlers/nvim.lua
@@ -196,7 +196,7 @@ return function(options)
local tests = (testCount == 1 and 'test' or 'tests')
local files = (fileCount == 1 and 'file' or 'files')
io.write(globalTeardown)
- io.write(global_helpers.read_nvim_log())
+ io.write(global_helpers.read_nvim_log(nil, true))
io.write(suiteEndString:format(testCount, tests, fileCount, files, elapsedTime_ms))
io.write(getSummaryString())
io.flush()
@@ -227,8 +227,12 @@ return function(options)
return nil, true
end
+ local function write_status(element, string)
+ io.write(timeString:format(getElapsedTime(element)) .. ' ' .. string)
+ io.flush()
+ end
+
handler.testEnd = function(element, _parent, status, _debug)
- local elapsedTime_ms = getElapsedTime(element)
local string
fileTestCount = fileTestCount + 1
@@ -241,45 +245,21 @@ return function(options)
string = skippedString
elseif status == 'failure' then
failureCount = failureCount + 1
- string = nil
+ string = failureString .. failureDescription(handler.failures[#handler.failures])
elseif status == 'error' then
errorCount = errorCount + 1
- string = nil
- end
-
- if string ~= nil then
- if elapsedTime_ms == elapsedTime_ms then
- string = timeString:format(elapsedTime_ms) .. ' ' .. string
- end
- io.write(string)
- io.flush()
+ string = errorString .. failureDescription(handler.errors[#handler.errors])
+ else
+ string = "unexpected test status! ("..status..")"
end
+ write_status(element, string)
return nil, true
end
- handler.testFailure = function(_element, _parent, _message, _debug)
- io.write(failureString)
- io.flush()
-
- io.write(failureDescription(handler.failures[#handler.failures]))
- io.flush()
- return nil, true
- end
-
- handler.testError = function(_element, _parent, _message, _debug)
- io.write(errorString)
- io.flush()
-
- io.write(failureDescription(handler.errors[#handler.errors]))
- io.flush()
- return nil, true
- end
-
handler.error = function(element, _parent, _message, _debug)
if element.descriptor ~= 'it' then
- io.write(failureDescription(handler.errors[#handler.errors]))
- io.flush()
+ write_status(element, failureDescription(handler.errors[#handler.errors]))
errorCount = errorCount + 1
end
@@ -293,8 +273,6 @@ return function(options)
busted.subscribe({ 'file', 'end' }, handler.fileEnd)
busted.subscribe({ 'test', 'start' }, handler.testStart, { predicate = handler.cancelOnPending })
busted.subscribe({ 'test', 'end' }, handler.testEnd, { predicate = handler.cancelOnPending })
- busted.subscribe({ 'failure', 'it' }, handler.testFailure)
- busted.subscribe({ 'error', 'it' }, handler.testError)
busted.subscribe({ 'failure' }, handler.error)
busted.subscribe({ 'error' }, handler.error)
diff --git a/test/functional/api/extmark_spec.lua b/test/functional/api/extmark_spec.lua
new file mode 100644
index 0000000000..9ea35e50a2
--- /dev/null
+++ b/test/functional/api/extmark_spec.lua
@@ -0,0 +1,1477 @@
+local helpers = require('test.functional.helpers')(after_each)
+local Screen = require('test.functional.ui.screen')
+
+local request = helpers.request
+local eq = helpers.eq
+local ok = helpers.ok
+local curbufmeths = helpers.curbufmeths
+local bufmeths = helpers.bufmeths
+local pcall_err = helpers.pcall_err
+local insert = helpers.insert
+local feed = helpers.feed
+local clear = helpers.clear
+local command = helpers.command
+local meths = helpers.meths
+
+local function expect(contents)
+ return eq(contents, helpers.curbuf_contents())
+end
+
+local function check_undo_redo(ns, mark, sr, sc, er, ec) --s = start, e = end
+ local rv = curbufmeths.get_extmark_by_id(ns, mark)
+ eq({er, ec}, rv)
+ feed("u")
+ rv = curbufmeths.get_extmark_by_id(ns, mark)
+ eq({sr, sc}, rv)
+ feed("<c-r>")
+ rv = curbufmeths.get_extmark_by_id(ns, mark)
+ eq({er, ec}, rv)
+end
+
+local function set_extmark(ns_id, id, line, col, opts)
+ if opts == nil then
+ opts = {}
+ end
+ return curbufmeths.set_extmark(ns_id, id, line, col, opts)
+end
+
+local function get_extmarks(ns_id, start, end_, opts)
+ if opts == nil then
+ opts = {}
+ end
+ return curbufmeths.get_extmarks(ns_id, start, end_, opts)
+end
+
+local function batch_set(ns_id, positions)
+ local ids = {}
+ for _, pos in ipairs(positions) do
+ table.insert(ids, set_extmark(ns_id, 0, pos[1], pos[2]))
+ end
+ return ids
+end
+
+local function batch_check(ns_id, ids, positions)
+ local actual, expected = {}, {}
+ for i,id in ipairs(ids) do
+ expected[id] = positions[i]
+ end
+ for _, mark in pairs(get_extmarks(ns_id, 0, -1, {})) do
+ actual[mark[1]] = {mark[2], mark[3]}
+ end
+ eq(expected, actual)
+end
+
+local function batch_check_undo_redo(ns_id, ids, before, after)
+ batch_check(ns_id, ids, after)
+ feed("u")
+ batch_check(ns_id, ids, before)
+ feed("<c-r>")
+ batch_check(ns_id, ids, after)
+end
+
+describe('API/extmarks', function()
+ local screen
+ local marks, positions, init_text, row, col
+ local ns, ns2
+
+ before_each(function()
+ -- Initialize some namespaces and insert 12345 into a buffer
+ marks = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}
+ positions = {{0, 0,}, {0, 2}, {0, 3}}
+
+ init_text = "12345"
+ row = 0
+ col = 2
+
+ clear()
+
+ insert(init_text)
+ ns = request('nvim_create_namespace', "my-fancy-plugin")
+ ns2 = request('nvim_create_namespace', "my-fancy-plugin2")
+ end)
+
+ it('adds, updates and deletes marks', function()
+ local rv = set_extmark(ns, marks[1], positions[1][1], positions[1][2])
+ eq(marks[1], rv)
+ rv = curbufmeths.get_extmark_by_id(ns, marks[1])
+ eq({positions[1][1], positions[1][2]}, rv)
+ -- Test adding a second mark on same row works
+ rv = set_extmark(ns, marks[2], positions[2][1], positions[2][2])
+ eq(marks[2], rv)
+
+ -- Test an update, (same pos)
+ rv = set_extmark(ns, marks[1], positions[1][1], positions[1][2])
+ eq(marks[1], rv)
+ rv = curbufmeths.get_extmark_by_id(ns, marks[2])
+ eq({positions[2][1], positions[2][2]}, rv)
+ -- Test an update, (new pos)
+ row = positions[1][1]
+ col = positions[1][2] + 1
+ rv = set_extmark(ns, marks[1], row, col)
+ eq(marks[1], rv)
+ rv = curbufmeths.get_extmark_by_id(ns, marks[1])
+ eq({row, col}, rv)
+
+ -- remove the test marks
+ eq(true, curbufmeths.del_extmark(ns, marks[1]))
+ eq(false, curbufmeths.del_extmark(ns, marks[1]))
+ eq(true, curbufmeths.del_extmark(ns, marks[2]))
+ eq(false, curbufmeths.del_extmark(ns, marks[3]))
+ eq(false, curbufmeths.del_extmark(ns, 1000))
+ end)
+
+ it('can clear a specific namespace range', function()
+ set_extmark(ns, 1, 0, 1)
+ set_extmark(ns2, 1, 0, 1)
+ -- force a new undo buffer
+ feed('o<esc>')
+ curbufmeths.clear_namespace(ns2, 0, -1)
+ eq({{1, 0, 1}}, get_extmarks(ns, {0, 0}, {-1, -1}))
+ eq({}, get_extmarks(ns2, {0, 0}, {-1, -1}))
+ feed('u')
+ eq({{1, 0, 1}}, get_extmarks(ns, {0, 0}, {-1, -1}))
+ eq({}, get_extmarks(ns2, {0, 0}, {-1, -1}))
+ feed('<c-r>')
+ eq({{1, 0, 1}}, get_extmarks(ns, {0, 0}, {-1, -1}))
+ eq({}, get_extmarks(ns2, {0, 0}, {-1, -1}))
+ end)
+
+ it('can clear a namespace range using 0,-1', function()
+ set_extmark(ns, 1, 0, 1)
+ set_extmark(ns2, 1, 0, 1)
+ -- force a new undo buffer
+ feed('o<esc>')
+ curbufmeths.clear_namespace(-1, 0, -1)
+ eq({}, get_extmarks(ns, {0, 0}, {-1, -1}))
+ eq({}, get_extmarks(ns2, {0, 0}, {-1, -1}))
+ feed('u')
+ eq({}, get_extmarks(ns, {0, 0}, {-1, -1}))
+ eq({}, get_extmarks(ns2, {0, 0}, {-1, -1}))
+ feed('<c-r>')
+ eq({}, get_extmarks(ns, {0, 0}, {-1, -1}))
+ eq({}, get_extmarks(ns2, {0, 0}, {-1, -1}))
+ end)
+
+ it('querying for information and ranges', function()
+ --marks = {1, 2, 3}
+ --positions = {{0, 0,}, {0, 2}, {0, 3}}
+ -- add some more marks
+ for i, m in ipairs(marks) do
+ if positions[i] ~= nil then
+ local rv = set_extmark(ns, m, positions[i][1], positions[i][2])
+ eq(m, rv)
+ end
+ end
+
+ -- {0, 0} and {-1, -1} work as extreme values
+ eq({{1, 0, 0}}, get_extmarks(ns, {0, 0}, {0, 0}))
+ eq({}, get_extmarks(ns, {-1, -1}, {-1, -1}))
+ local rv = get_extmarks(ns, {0, 0}, {-1, -1})
+ for i, m in ipairs(marks) do
+ if positions[i] ~= nil then
+ eq({m, positions[i][1], positions[i][2]}, rv[i])
+ end
+ end
+
+ -- 0 and -1 works as short hand extreme values
+ eq({{1, 0, 0}}, get_extmarks(ns, 0, 0))
+ eq({}, get_extmarks(ns, -1, -1))
+ rv = get_extmarks(ns, 0, -1)
+ for i, m in ipairs(marks) do
+ if positions[i] ~= nil then
+ eq({m, positions[i][1], positions[i][2]}, rv[i])
+ end
+ end
+
+ -- next with mark id
+ rv = get_extmarks(ns, marks[1], {-1, -1}, {limit=1})
+ eq({{marks[1], positions[1][1], positions[1][2]}}, rv)
+ rv = get_extmarks(ns, marks[2], {-1, -1}, {limit=1})
+ eq({{marks[2], positions[2][1], positions[2][2]}}, rv)
+ -- next with positional when mark exists at position
+ rv = get_extmarks(ns, positions[1], {-1, -1}, {limit=1})
+ eq({{marks[1], positions[1][1], positions[1][2]}}, rv)
+ -- next with positional index (no mark at position)
+ rv = get_extmarks(ns, {positions[1][1], positions[1][2] +1}, {-1, -1}, {limit=1})
+ eq({{marks[2], positions[2][1], positions[2][2]}}, rv)
+ -- next with Extremity index
+ rv = get_extmarks(ns, {0,0}, {-1, -1}, {limit=1})
+ eq({{marks[1], positions[1][1], positions[1][2]}}, rv)
+
+ -- nextrange with mark id
+ rv = get_extmarks(ns, marks[1], marks[3])
+ eq({marks[1], positions[1][1], positions[1][2]}, rv[1])
+ eq({marks[2], positions[2][1], positions[2][2]}, rv[2])
+ -- nextrange with `limit`
+ rv = get_extmarks(ns, marks[1], marks[3], {limit=2})
+ eq(2, #rv)
+ -- nextrange with positional when mark exists at position
+ rv = get_extmarks(ns, positions[1], positions[3])
+ eq({marks[1], positions[1][1], positions[1][2]}, rv[1])
+ eq({marks[2], positions[2][1], positions[2][2]}, rv[2])
+ rv = get_extmarks(ns, positions[2], positions[3])
+ eq(2, #rv)
+ -- nextrange with positional index (no mark at position)
+ local lower = {positions[1][1], positions[2][2] -1}
+ local upper = {positions[2][1], positions[3][2] - 1}
+ rv = get_extmarks(ns, lower, upper)
+ eq({{marks[2], positions[2][1], positions[2][2]}}, rv)
+ lower = {positions[3][1], positions[3][2] + 1}
+ upper = {positions[3][1], positions[3][2] + 2}
+ rv = get_extmarks(ns, lower, upper)
+ eq({}, rv)
+ -- nextrange with extremity index
+ lower = {positions[2][1], positions[2][2]+1}
+ upper = {-1, -1}
+ rv = get_extmarks(ns, lower, upper)
+ eq({{marks[3], positions[3][1], positions[3][2]}}, rv)
+
+ -- prev with mark id
+ rv = get_extmarks(ns, marks[3], {0, 0}, {limit=1})
+ eq({{marks[3], positions[3][1], positions[3][2]}}, rv)
+ rv = get_extmarks(ns, marks[2], {0, 0}, {limit=1})
+ eq({{marks[2], positions[2][1], positions[2][2]}}, rv)
+ -- prev with positional when mark exists at position
+ rv = get_extmarks(ns, positions[3], {0, 0}, {limit=1})
+ eq({{marks[3], positions[3][1], positions[3][2]}}, rv)
+ -- prev with positional index (no mark at position)
+ rv = get_extmarks(ns, {positions[1][1], positions[1][2] +1}, {0, 0}, {limit=1})
+ eq({{marks[1], positions[1][1], positions[1][2]}}, rv)
+ -- prev with Extremity index
+ rv = get_extmarks(ns, {-1,-1}, {0,0}, {limit=1})
+ eq({{marks[3], positions[3][1], positions[3][2]}}, rv)
+
+ -- prevrange with mark id
+ rv = get_extmarks(ns, marks[3], marks[1])
+ eq({marks[3], positions[3][1], positions[3][2]}, rv[1])
+ eq({marks[2], positions[2][1], positions[2][2]}, rv[2])
+ eq({marks[1], positions[1][1], positions[1][2]}, rv[3])
+ -- prevrange with limit
+ rv = get_extmarks(ns, marks[3], marks[1], {limit=2})
+ eq(2, #rv)
+ -- prevrange with positional when mark exists at position
+ rv = get_extmarks(ns, positions[3], positions[1])
+ eq({{marks[3], positions[3][1], positions[3][2]},
+ {marks[2], positions[2][1], positions[2][2]},
+ {marks[1], positions[1][1], positions[1][2]}}, rv)
+ rv = get_extmarks(ns, positions[2], positions[1])
+ eq(2, #rv)
+ -- prevrange with positional index (no mark at position)
+ lower = {positions[2][1], positions[2][2] + 1}
+ upper = {positions[3][1], positions[3][2] + 1}
+ rv = get_extmarks(ns, upper, lower)
+ eq({{marks[3], positions[3][1], positions[3][2]}}, rv)
+ lower = {positions[3][1], positions[3][2] + 1}
+ upper = {positions[3][1], positions[3][2] + 2}
+ rv = get_extmarks(ns, upper, lower)
+ eq({}, rv)
+ -- prevrange with extremity index
+ lower = {0,0}
+ upper = {positions[2][1], positions[2][2] - 1}
+ rv = get_extmarks(ns, upper, lower)
+ eq({{marks[1], positions[1][1], positions[1][2]}}, rv)
+ end)
+
+ it('querying for information with limit', function()
+ -- add some more marks
+ for i, m in ipairs(marks) do
+ if positions[i] ~= nil then
+ local rv = set_extmark(ns, m, positions[i][1], positions[i][2])
+ eq(m, rv)
+ end
+ end
+
+ local rv = get_extmarks(ns, {0, 0}, {-1, -1}, {limit=1})
+ eq(1, #rv)
+ rv = get_extmarks(ns, {0, 0}, {-1, -1}, {limit=2})
+ eq(2, #rv)
+ rv = get_extmarks(ns, {0, 0}, {-1, -1}, {limit=3})
+ eq(3, #rv)
+
+ -- now in reverse
+ rv = get_extmarks(ns, {0, 0}, {-1, -1}, {limit=1})
+ eq(1, #rv)
+ rv = get_extmarks(ns, {0, 0}, {-1, -1}, {limit=2})
+ eq(2, #rv)
+ rv = get_extmarks(ns, {0, 0}, {-1, -1}, {limit=3})
+ eq(3, #rv)
+ end)
+
+ it('get_marks works when mark col > upper col', function()
+ feed('A<cr>12345<esc>')
+ feed('A<cr>12345<esc>')
+ set_extmark(ns, 10, 0, 2) -- this shouldn't be found
+ set_extmark(ns, 11, 2, 1) -- this shouldn't be found
+ set_extmark(ns, marks[1], 0, 4) -- check col > our upper bound
+ set_extmark(ns, marks[2], 1, 1) -- check col < lower bound
+ set_extmark(ns, marks[3], 2, 0) -- check is inclusive
+ eq({{marks[1], 0, 4},
+ {marks[2], 1, 1},
+ {marks[3], 2, 0}},
+ get_extmarks(ns, {0, 3}, {2, 0}))
+ end)
+
+ it('get_marks works in reverse when mark col < lower col', function()
+ feed('A<cr>12345<esc>')
+ feed('A<cr>12345<esc>')
+ set_extmark(ns, 10, 0, 1) -- this shouldn't be found
+ set_extmark(ns, 11, 2, 4) -- this shouldn't be found
+ set_extmark(ns, marks[1], 2, 1) -- check col < our lower bound
+ set_extmark(ns, marks[2], 1, 4) -- check col > upper bound
+ set_extmark(ns, marks[3], 0, 2) -- check is inclusive
+ local rv = get_extmarks(ns, {2, 3}, {0, 2})
+ eq({{marks[1], 2, 1},
+ {marks[2], 1, 4},
+ {marks[3], 0, 2}},
+ rv)
+ end)
+
+ it('get_marks limit=0 returns nothing', function()
+ set_extmark(ns, marks[1], positions[1][1], positions[1][2])
+ local rv = get_extmarks(ns, {-1, -1}, {-1, -1}, {limit=0})
+ eq({}, rv)
+ end)
+
+
+ it('marks move with line insertations', function()
+ set_extmark(ns, marks[1], 0, 0)
+ feed("yyP")
+ check_undo_redo(ns, marks[1], 0, 0, 1, 0)
+ end)
+
+ it('marks move with multiline insertations', function()
+ feed("a<cr>22<cr>33<esc>")
+ set_extmark(ns, marks[1], 1, 1)
+ feed('ggVGyP')
+ check_undo_redo(ns, marks[1], 1, 1, 4, 1)
+ end)
+
+ it('marks move with line join', function()
+ -- do_join in ops.c
+ feed("a<cr>222<esc>")
+ set_extmark(ns, marks[1], 1, 0)
+ feed('ggJ')
+ check_undo_redo(ns, marks[1], 1, 0, 0, 6)
+ end)
+
+ it('join works when no marks are present', function()
+ screen = Screen.new(15, 10)
+ screen:attach()
+ feed("a<cr>1<esc>")
+ feed('kJ')
+ -- This shouldn't seg fault
+ screen:expect([[
+ 12345^ 1 |
+ ~ |
+ ~ |
+ ~ |
+ ~ |
+ ~ |
+ ~ |
+ ~ |
+ ~ |
+ |
+ ]])
+ end)
+
+ it('marks move with multiline join', function()
+ -- do_join in ops.c
+ feed("a<cr>222<cr>333<cr>444<esc>")
+ set_extmark(ns, marks[1], 3, 0)
+ feed('2GVGJ')
+ check_undo_redo(ns, marks[1], 3, 0, 1, 8)
+ end)
+
+ it('marks move with line deletes', function()
+ feed("a<cr>222<cr>333<cr>444<esc>")
+ set_extmark(ns, marks[1], 2, 1)
+ feed('ggjdd')
+ check_undo_redo(ns, marks[1], 2, 1, 1, 1)
+ end)
+
+ it('marks move with multiline deletes', function()
+ feed("a<cr>222<cr>333<cr>444<esc>")
+ set_extmark(ns, marks[1], 3, 0)
+ feed('gg2dd')
+ check_undo_redo(ns, marks[1], 3, 0, 1, 0)
+ -- regression test, undoing multiline delete when mark is on row 1
+ feed('ugg3dd')
+ check_undo_redo(ns, marks[1], 3, 0, 0, 0)
+ end)
+
+ it('marks move with open line', function()
+ -- open_line in misc1.c
+ -- testing marks below are also moved
+ feed("yyP")
+ set_extmark(ns, marks[1], 0, 4)
+ set_extmark(ns, marks[2], 1, 4)
+ feed('1G<s-o><esc>')
+ check_undo_redo(ns, marks[1], 0, 4, 1, 4)
+ check_undo_redo(ns, marks[2], 1, 4, 2, 4)
+ feed('2Go<esc>')
+ check_undo_redo(ns, marks[1], 1, 4, 1, 4)
+ check_undo_redo(ns, marks[2], 2, 4, 3, 4)
+ end)
+
+ it('marks move with char inserts', function()
+ -- insertchar in edit.c (the ins_str branch)
+ screen = Screen.new(15, 10)
+ screen:attach()
+ set_extmark(ns, marks[1], 0, 3)
+ feed('0')
+ insert('abc')
+ screen:expect([[
+ ab^c12345 |
+ ~ |
+ ~ |
+ ~ |
+ ~ |
+ ~ |
+ ~ |
+ ~ |
+ ~ |
+ |
+ ]])
+ local rv = curbufmeths.get_extmark_by_id(ns, marks[1])
+ eq({0, 6}, rv)
+ check_undo_redo(ns, marks[1], 0, 3, 0, 6)
+ end)
+
+ -- gravity right as definted in tk library
+ it('marks have gravity right', function()
+ -- insertchar in edit.c (the ins_str branch)
+ set_extmark(ns, marks[1], 0, 2)
+ feed('03l')
+ insert("X")
+ check_undo_redo(ns, marks[1], 0, 2, 0, 2)
+
+ -- check multibyte chars
+ feed('03l<esc>')
+ insert("~~")
+ check_undo_redo(ns, marks[1], 0, 2, 0, 2)
+ end)
+
+ it('we can insert multibyte chars', function()
+ -- insertchar in edit.c
+ feed('a<cr>12345<esc>')
+ set_extmark(ns, marks[1], 1, 2)
+ -- Insert a fullwidth (two col) tilde, NICE
+ feed('0i~<esc>')
+ check_undo_redo(ns, marks[1], 1, 2, 1, 5)
+ end)
+
+ it('marks move with blockwise inserts', function()
+ -- op_insert in ops.c
+ feed('a<cr>12345<esc>')
+ set_extmark(ns, marks[1], 1, 2)
+ feed('0<c-v>lkI9<esc>')
+ check_undo_redo(ns, marks[1], 1, 2, 1, 3)
+ end)
+
+ it('marks move with line splits (using enter)', function()
+ -- open_line in misc1.c
+ -- testing marks below are also moved
+ feed("yyP")
+ set_extmark(ns, marks[1], 0, 4)
+ set_extmark(ns, marks[2], 1, 4)
+ feed('1Gla<cr><esc>')
+ check_undo_redo(ns, marks[1], 0, 4, 1, 2)
+ check_undo_redo(ns, marks[2], 1, 4, 2, 4)
+ end)
+
+ it('marks at last line move on insert new line', function()
+ -- open_line in misc1.c
+ set_extmark(ns, marks[1], 0, 4)
+ feed('0i<cr><esc>')
+ check_undo_redo(ns, marks[1], 0, 4, 1, 4)
+ end)
+
+ it('yet again marks move with line splits', function()
+ -- the first test above wasn't catching all errors..
+ feed("A67890<esc>")
+ set_extmark(ns, marks[1], 0, 4)
+ feed("04li<cr><esc>")
+ check_undo_redo(ns, marks[1], 0, 4, 1, 0)
+ end)
+
+ it('and one last time line splits...', function()
+ set_extmark(ns, marks[1], 0, 1)
+ set_extmark(ns, marks[2], 0, 2)
+ feed("02li<cr><esc>")
+ check_undo_redo(ns, marks[1], 0, 1, 0, 1)
+ check_undo_redo(ns, marks[2], 0, 2, 1, 0)
+ end)
+
+ it('multiple marks move with mark splits', function()
+ set_extmark(ns, marks[1], 0, 1)
+ set_extmark(ns, marks[2], 0, 3)
+ feed("0li<cr><esc>")
+ check_undo_redo(ns, marks[1], 0, 1, 1, 0)
+ check_undo_redo(ns, marks[2], 0, 3, 1, 2)
+ end)
+
+ it('deleting right before a mark works', function()
+ -- op_delete in ops.c
+ set_extmark(ns, marks[1], 0, 2)
+ feed('0lx')
+ check_undo_redo(ns, marks[1], 0, 2, 0, 1)
+ end)
+
+ it('deleting right after a mark works', function()
+ -- op_delete in ops.c
+ set_extmark(ns, marks[1], 0, 2)
+ feed('02lx')
+ check_undo_redo(ns, marks[1], 0, 2, 0, 2)
+ end)
+
+ it('marks move with char deletes', function()
+ -- op_delete in ops.c
+ set_extmark(ns, marks[1], 0, 2)
+ feed('02dl')
+ check_undo_redo(ns, marks[1], 0, 2, 0, 0)
+ -- from the other side (nothing should happen)
+ feed('$x')
+ check_undo_redo(ns, marks[1], 0, 0, 0, 0)
+ end)
+
+ it('marks move with char deletes over a range', function()
+ -- op_delete in ops.c
+ set_extmark(ns, marks[1], 0, 2)
+ set_extmark(ns, marks[2], 0, 3)
+ feed('0l3dl<esc>')
+ check_undo_redo(ns, marks[1], 0, 2, 0, 1)
+ check_undo_redo(ns, marks[2], 0, 3, 0, 1)
+ -- delete 1, nothing should happen to our marks
+ feed('u')
+ feed('$x')
+ check_undo_redo(ns, marks[2], 0, 3, 0, 3)
+ end)
+
+ it('deleting marks at end of line works', function()
+ set_extmark(ns, marks[1], 0, 4)
+ feed('$x')
+ check_undo_redo(ns, marks[1], 0, 4, 0, 4)
+ -- check the copy happened correctly on delete at eol
+ feed('$x')
+ check_undo_redo(ns, marks[1], 0, 4, 0, 3)
+ feed('u')
+ check_undo_redo(ns, marks[1], 0, 4, 0, 4)
+ end)
+
+ it('marks move with blockwise deletes', function()
+ -- op_delete in ops.c
+ feed('a<cr>12345<esc>')
+ set_extmark(ns, marks[1], 1, 4)
+ feed('h<c-v>hhkd')
+ check_undo_redo(ns, marks[1], 1, 4, 1, 1)
+ end)
+
+ it('marks move with blockwise deletes over a range', function()
+ -- op_delete in ops.c
+ feed('a<cr>12345<esc>')
+ set_extmark(ns, marks[1], 0, 1)
+ set_extmark(ns, marks[2], 0, 3)
+ set_extmark(ns, marks[3], 1, 2)
+ feed('0<c-v>k3lx')
+ check_undo_redo(ns, marks[1], 0, 1, 0, 0)
+ check_undo_redo(ns, marks[2], 0, 3, 0, 0)
+ check_undo_redo(ns, marks[3], 1, 2, 1, 0)
+ -- delete 1, nothing should happen to our marks
+ feed('u')
+ feed('$<c-v>jx')
+ check_undo_redo(ns, marks[2], 0, 3, 0, 3)
+ check_undo_redo(ns, marks[3], 1, 2, 1, 2)
+ end)
+
+ it('works with char deletes over multilines', function()
+ feed('a<cr>12345<cr>test-me<esc>')
+ set_extmark(ns, marks[1], 2, 5)
+ feed('gg')
+ feed('dv?-m?<cr>')
+ check_undo_redo(ns, marks[1], 2, 5, 0, 0)
+ end)
+
+ it('marks outside of deleted range move with visual char deletes', function()
+ -- op_delete in ops.c
+ set_extmark(ns, marks[1], 0, 3)
+ feed('0vx<esc>')
+ check_undo_redo(ns, marks[1], 0, 3, 0, 2)
+
+ feed("u")
+ feed('0vlx<esc>')
+ check_undo_redo(ns, marks[1], 0, 3, 0, 1)
+
+ feed("u")
+ feed('0v2lx<esc>')
+ check_undo_redo(ns, marks[1], 0, 3, 0, 0)
+
+ -- from the other side (nothing should happen)
+ feed('$vx')
+ check_undo_redo(ns, marks[1], 0, 0, 0, 0)
+ end)
+
+ it('marks outside of deleted range move with char deletes', function()
+ -- op_delete in ops.c
+ set_extmark(ns, marks[1], 0, 3)
+ feed('0x<esc>')
+ check_undo_redo(ns, marks[1], 0, 3, 0, 2)
+
+ feed("u")
+ feed('02x<esc>')
+ check_undo_redo(ns, marks[1], 0, 3, 0, 1)
+
+ feed("u")
+ feed('0v3lx<esc>')
+ check_undo_redo(ns, marks[1], 0, 3, 0, 0)
+
+ -- from the other side (nothing should happen)
+ feed("u")
+ feed('$vx')
+ check_undo_redo(ns, marks[1], 0, 3, 0, 3)
+ end)
+
+ it('marks move with P(backward) paste', function()
+ -- do_put in ops.c
+ feed('0iabc<esc>')
+ set_extmark(ns, marks[1], 0, 7)
+ feed('0veyP')
+ check_undo_redo(ns, marks[1], 0, 7, 0, 15)
+ end)
+
+ it('marks move with p(forward) paste', function()
+ -- do_put in ops.c
+ feed('0iabc<esc>')
+ set_extmark(ns, marks[1], 0, 7)
+ feed('0veyp')
+ check_undo_redo(ns, marks[1], 0, 7, 0, 15)
+ end)
+
+ it('marks move with blockwise P(backward) paste', function()
+ -- do_put in ops.c
+ feed('a<cr>12345<esc>')
+ set_extmark(ns, marks[1], 1, 4)
+ feed('<c-v>hhkyP<esc>')
+ check_undo_redo(ns, marks[1], 1, 4, 1, 7)
+ end)
+
+ it('marks move with blockwise p(forward) paste', function()
+ -- do_put in ops.c
+ feed('a<cr>12345<esc>')
+ set_extmark(ns, marks[1], 1, 4)
+ feed('<c-v>hhkyp<esc>')
+ check_undo_redo(ns, marks[1], 1, 4, 1, 7)
+ end)
+
+ describe('multiline regions', function()
+ before_each(function()
+ feed('dd')
+ -- Achtung: code has been spiced with some unicode,
+ -- to make life more interesting.
+ -- luacheck whines about TABs inside strings for whatever reason.
+ -- luacheck: push ignore 621
+ insert([[
+ static int nlua_rpcrequest(lua_State *lstate)
+ {
+ Ïf (!nlua_is_deferred_safe(lstate)) {
+ // strictly not allowed
+ Яetörn luaL_error(lstate, e_luv_api_disabled, "rpcrequest");
+ }
+ return nlua_rpc(lstate, true);
+ }]])
+ -- luacheck: pop
+ end)
+
+ it('delete', function()
+ local pos1 = {
+ {2, 4}, {2, 12}, {2, 13}, {2, 14}, {2, 25},
+ {4, 8}, {4, 10}, {4, 20},
+ {5, 3}, {6, 10}
+ }
+ local ids = batch_set(ns, pos1)
+ batch_check(ns, ids, pos1)
+ feed('3Gfiv2+ftd')
+ batch_check_undo_redo(ns, ids, pos1, {
+ {2, 4}, {2, 12}, {2, 13}, {2, 13}, {2, 13},
+ {2, 13}, {2, 15}, {2, 25},
+ {3, 3}, {4, 10}
+ })
+ end)
+
+ -- TODO(bfredl): add more tests!
+ end)
+
+ it('replace works', function()
+ set_extmark(ns, marks[1], 0, 2)
+ feed('0r2')
+ check_undo_redo(ns, marks[1], 0, 2, 0, 2)
+ end)
+
+ it('blockwise replace works', function()
+ feed('a<cr>12345<esc>')
+ set_extmark(ns, marks[1], 0, 2)
+ feed('0<c-v>llkr1<esc>')
+ check_undo_redo(ns, marks[1], 0, 2, 0, 3)
+ end)
+
+ it('shift line', function()
+ -- shift_line in ops.c
+ feed(':set shiftwidth=4<cr><esc>')
+ set_extmark(ns, marks[1], 0, 2)
+ feed('0>>')
+ check_undo_redo(ns, marks[1], 0, 2, 0, 6)
+ expect(' 12345')
+
+ feed('>>')
+ -- this is counter-intuitive. But what happens
+ -- is that 4 spaces gets extended to one tab (== 8 spaces)
+ check_undo_redo(ns, marks[1], 0, 6, 0, 3)
+ expect('\t12345')
+
+ feed('<LT><LT>') -- have to escape, same as <<
+ check_undo_redo(ns, marks[1], 0, 3, 0, 6)
+ end)
+
+ it('blockwise shift', function()
+ -- shift_block in ops.c
+ feed(':set shiftwidth=4<cr><esc>')
+ feed('a<cr>12345<esc>')
+ set_extmark(ns, marks[1], 1, 2)
+ feed('0<c-v>k>')
+ check_undo_redo(ns, marks[1], 1, 2, 1, 6)
+ feed('<c-v>j>')
+ expect('\t12345\n\t12345')
+ check_undo_redo(ns, marks[1], 1, 6, 1, 3)
+
+ feed('<c-v>j<LT>')
+ check_undo_redo(ns, marks[1], 1, 3, 1, 6)
+ end)
+
+ it('tab works with expandtab', function()
+ -- ins_tab in edit.c
+ feed(':set expandtab<cr><esc>')
+ feed(':set shiftwidth=2<cr><esc>')
+ set_extmark(ns, marks[1], 0, 2)
+ feed('0i<tab><tab><esc>')
+ check_undo_redo(ns, marks[1], 0, 2, 0, 6)
+ end)
+
+ it('tabs work', function()
+ -- ins_tab in edit.c
+ feed(':set noexpandtab<cr><esc>')
+ feed(':set shiftwidth=2<cr><esc>')
+ feed(':set softtabstop=2<cr><esc>')
+ feed(':set tabstop=8<cr><esc>')
+ set_extmark(ns, marks[1], 0, 2)
+ feed('0i<tab><esc>')
+ check_undo_redo(ns, marks[1], 0, 2, 0, 4)
+ feed('0iX<tab><esc>')
+ check_undo_redo(ns, marks[1], 0, 4, 0, 6)
+ end)
+
+ it('marks move when using :move', function()
+ set_extmark(ns, marks[1], 0, 0)
+ feed('A<cr>2<esc>:1move 2<cr><esc>')
+ check_undo_redo(ns, marks[1], 0, 0, 1, 0)
+ -- test codepath when moving lines up
+ feed(':2move 0<cr><esc>')
+ check_undo_redo(ns, marks[1], 1, 0, 0, 0)
+ end)
+
+ it('marks move when using :move part 2', function()
+ -- make sure we didn't get lucky with the math...
+ feed('A<cr>2<cr>3<cr>4<cr>5<cr>6<esc>')
+ set_extmark(ns, marks[1], 1, 0)
+ feed(':2,3move 5<cr><esc>')
+ check_undo_redo(ns, marks[1], 1, 0, 3, 0)
+ -- test codepath when moving lines up
+ feed(':4,5move 1<cr><esc>')
+ check_undo_redo(ns, marks[1], 3, 0, 1, 0)
+ end)
+
+ it('undo and redo of set and unset marks', function()
+ -- Force a new undo head
+ feed('o<esc>')
+ set_extmark(ns, marks[1], 0, 1)
+ feed('o<esc>')
+ set_extmark(ns, marks[2], 0, -1)
+ set_extmark(ns, marks[3], 0, -1)
+
+ feed("u")
+ local rv = get_extmarks(ns, {0, 0}, {-1, -1})
+ eq(3, #rv)
+
+ feed("<c-r>")
+ rv = get_extmarks(ns, {0, 0}, {-1, -1})
+ eq(3, #rv)
+
+ -- Test updates
+ feed('o<esc>')
+ set_extmark(ns, marks[1], positions[1][1], positions[1][2])
+ rv = get_extmarks(ns, marks[1], marks[1], {limit=1})
+ eq(1, #rv)
+ feed("u")
+ feed("<c-r>")
+ -- old value is NOT kept in history
+ check_undo_redo(ns, marks[1], positions[1][1], positions[1][2], positions[1][1], positions[1][2])
+
+ -- Test unset
+ feed('o<esc>')
+ curbufmeths.del_extmark(ns, marks[3])
+ feed("u")
+ rv = get_extmarks(ns, {0, 0}, {-1, -1})
+ -- undo does NOT restore deleted marks
+ eq(2, #rv)
+ feed("<c-r>")
+ rv = get_extmarks(ns, {0, 0}, {-1, -1})
+ eq(2, #rv)
+ end)
+
+ it('undo and redo of marks deleted during edits', function()
+ -- test extmark_adjust
+ feed('A<cr>12345<esc>')
+ set_extmark(ns, marks[1], 1, 2)
+ feed('dd')
+ check_undo_redo(ns, marks[1], 1, 2, 1, 0)
+ end)
+
+ it('namespaces work properly', function()
+ local rv = set_extmark(ns, marks[1], positions[1][1], positions[1][2])
+ eq(1, rv)
+ rv = set_extmark(ns2, marks[1], positions[1][1], positions[1][2])
+ eq(1, rv)
+ rv = get_extmarks(ns, {0, 0}, {-1, -1})
+ eq(1, #rv)
+ rv = get_extmarks(ns2, {0, 0}, {-1, -1})
+ eq(1, #rv)
+
+ -- Set more marks for testing the ranges
+ set_extmark(ns, marks[2], positions[2][1], positions[2][2])
+ set_extmark(ns, marks[3], positions[3][1], positions[3][2])
+ set_extmark(ns2, marks[2], positions[2][1], positions[2][2])
+ set_extmark(ns2, marks[3], positions[3][1], positions[3][2])
+
+ -- get_next (limit set)
+ rv = get_extmarks(ns, {0, 0}, positions[2], {limit=1})
+ eq(1, #rv)
+ rv = get_extmarks(ns2, {0, 0}, positions[2], {limit=1})
+ eq(1, #rv)
+ -- get_prev (limit set)
+ rv = get_extmarks(ns, positions[1], {0, 0}, {limit=1})
+ eq(1, #rv)
+ rv = get_extmarks(ns2, positions[1], {0, 0}, {limit=1})
+ eq(1, #rv)
+
+ -- get_next (no limit)
+ rv = get_extmarks(ns, positions[1], positions[2])
+ eq(2, #rv)
+ rv = get_extmarks(ns2, positions[1], positions[2])
+ eq(2, #rv)
+ -- get_prev (no limit)
+ rv = get_extmarks(ns, positions[2], positions[1])
+ eq(2, #rv)
+ rv = get_extmarks(ns2, positions[2], positions[1])
+ eq(2, #rv)
+
+ curbufmeths.del_extmark(ns, marks[1])
+ rv = get_extmarks(ns, {0, 0}, {-1, -1})
+ eq(2, #rv)
+ curbufmeths.del_extmark(ns2, marks[1])
+ rv = get_extmarks(ns2, {0, 0}, {-1, -1})
+ eq(2, #rv)
+ end)
+
+ it('mark set can create unique identifiers', function()
+ -- create mark with id 1
+ eq(1, set_extmark(ns, 1, positions[1][1], positions[1][2]))
+ -- ask for unique id, it should be the next one, i e 2
+ eq(2, set_extmark(ns, 0, positions[1][1], positions[1][2]))
+ eq(3, set_extmark(ns, 3, positions[2][1], positions[2][2]))
+ eq(4, set_extmark(ns, 0, positions[1][1], positions[1][2]))
+
+ -- mixing manual and allocated id:s are not recommened, but it should
+ -- do something reasonable
+ eq(6, set_extmark(ns, 6, positions[2][1], positions[2][2]))
+ eq(7, set_extmark(ns, 0, positions[1][1], positions[1][2]))
+ eq(8, set_extmark(ns, 0, positions[1][1], positions[1][2]))
+ end)
+
+ it('auto indenting with enter works', function()
+ -- op_reindent in ops.c
+ feed(':set cindent<cr><esc>')
+ feed(':set autoindent<cr><esc>')
+ feed(':set shiftwidth=2<cr><esc>')
+ feed("0iint <esc>A {1M1<esc>b<esc>")
+ -- Set the mark on the M, should move..
+ set_extmark(ns, marks[1], 0, 12)
+ -- Set the mark before the cursor, should stay there
+ set_extmark(ns, marks[2], 0, 10)
+ feed("i<cr><esc>")
+ local rv = curbufmeths.get_extmark_by_id(ns, marks[1])
+ eq({1, 3}, rv)
+ rv = curbufmeths.get_extmark_by_id(ns, marks[2])
+ eq({0, 10}, rv)
+ check_undo_redo(ns, marks[1], 0, 12, 1, 3)
+ end)
+
+ it('auto indenting entire line works', function()
+ feed(':set cindent<cr><esc>')
+ feed(':set autoindent<cr><esc>')
+ feed(':set shiftwidth=2<cr><esc>')
+ -- <c-f> will force an indent of 2
+ feed("0iint <esc>A {<cr><esc>0i1M1<esc>")
+ set_extmark(ns, marks[1], 1, 1)
+ feed("0i<c-f><esc>")
+ local rv = curbufmeths.get_extmark_by_id(ns, marks[1])
+ eq({1, 3}, rv)
+ check_undo_redo(ns, marks[1], 1, 1, 1, 3)
+ -- now check when cursor at eol
+ feed("uA<c-f><esc>")
+ rv = curbufmeths.get_extmark_by_id(ns, marks[1])
+ eq({1, 3}, rv)
+ end)
+
+ it('removing auto indenting with <C-D> works', function()
+ feed(':set cindent<cr><esc>')
+ feed(':set autoindent<cr><esc>')
+ feed(':set shiftwidth=2<cr><esc>')
+ feed("0i<tab><esc>")
+ set_extmark(ns, marks[1], 0, 3)
+ feed("bi<c-d><esc>")
+ local rv = curbufmeths.get_extmark_by_id(ns, marks[1])
+ eq({0, 1}, rv)
+ check_undo_redo(ns, marks[1], 0, 3, 0, 1)
+ -- check when cursor at eol
+ feed("uA<c-d><esc>")
+ rv = curbufmeths.get_extmark_by_id(ns, marks[1])
+ eq({0, 1}, rv)
+ end)
+
+ it('indenting multiple lines with = works', function()
+ feed(':set cindent<cr><esc>')
+ feed(':set autoindent<cr><esc>')
+ feed(':set shiftwidth=2<cr><esc>')
+ feed("0iint <esc>A {<cr><bs>1M1<cr><bs>2M2<esc>")
+ set_extmark(ns, marks[1], 1, 1)
+ set_extmark(ns, marks[2], 2, 1)
+ feed('=gg')
+ check_undo_redo(ns, marks[1], 1, 1, 1, 3)
+ check_undo_redo(ns, marks[2], 2, 1, 2, 5)
+ end)
+
+ it('substitutes by deleting inside the replace matches', function()
+ -- do_sub in ex_cmds.c
+ set_extmark(ns, marks[1], 0, 2)
+ set_extmark(ns, marks[2], 0, 3)
+ feed(':s/34/xx<cr>')
+ check_undo_redo(ns, marks[1], 0, 2, 0, 4)
+ check_undo_redo(ns, marks[2], 0, 3, 0, 4)
+ end)
+
+ it('substitutes when insert text > deleted', function()
+ -- do_sub in ex_cmds.c
+ set_extmark(ns, marks[1], 0, 2)
+ set_extmark(ns, marks[2], 0, 3)
+ feed(':s/34/xxx<cr>')
+ check_undo_redo(ns, marks[1], 0, 2, 0, 5)
+ check_undo_redo(ns, marks[2], 0, 3, 0, 5)
+ end)
+
+ it('substitutes when marks around eol', function()
+ -- do_sub in ex_cmds.c
+ set_extmark(ns, marks[1], 0, 4)
+ set_extmark(ns, marks[2], 0, 5)
+ feed(':s/5/xxx<cr>')
+ check_undo_redo(ns, marks[1], 0, 4, 0, 7)
+ check_undo_redo(ns, marks[2], 0, 5, 0, 7)
+ end)
+
+ it('substitutes over range insert text > deleted', function()
+ -- do_sub in ex_cmds.c
+ feed('A<cr>x34xx<esc>')
+ feed('A<cr>xxx34<esc>')
+ set_extmark(ns, marks[1], 0, 2)
+ set_extmark(ns, marks[2], 1, 1)
+ set_extmark(ns, marks[3], 2, 4)
+ feed(':1,3s/34/xxx<cr><esc>')
+ check_undo_redo(ns, marks[1], 0, 2, 0, 5)
+ check_undo_redo(ns, marks[2], 1, 1, 1, 4)
+ check_undo_redo(ns, marks[3], 2, 4, 2, 6)
+ end)
+
+ it('substitutes multiple matches in a line', function()
+ -- do_sub in ex_cmds.c
+ feed('ddi3x3x3<esc>')
+ set_extmark(ns, marks[1], 0, 0)
+ set_extmark(ns, marks[2], 0, 2)
+ set_extmark(ns, marks[3], 0, 4)
+ feed(':s/3/yy/g<cr><esc>')
+ check_undo_redo(ns, marks[1], 0, 0, 0, 2)
+ check_undo_redo(ns, marks[2], 0, 2, 0, 5)
+ check_undo_redo(ns, marks[3], 0, 4, 0, 8)
+ end)
+
+ it('substitions over multiple lines with newline in pattern', function()
+ feed('A<cr>67890<cr>xx<esc>')
+ set_extmark(ns, marks[1], 0, 3)
+ set_extmark(ns, marks[2], 0, 4)
+ set_extmark(ns, marks[3], 1, 0)
+ set_extmark(ns, marks[4], 1, 5)
+ set_extmark(ns, marks[5], 2, 0)
+ feed([[:1,2s:5\n:5 <cr>]])
+ check_undo_redo(ns, marks[1], 0, 3, 0, 3)
+ check_undo_redo(ns, marks[2], 0, 4, 0, 6)
+ check_undo_redo(ns, marks[3], 1, 0, 0, 6)
+ check_undo_redo(ns, marks[4], 1, 5, 0, 11)
+ check_undo_redo(ns, marks[5], 2, 0, 1, 0)
+ end)
+
+ it('inserting', function()
+ feed('A<cr>67890<cr>xx<esc>')
+ set_extmark(ns, marks[1], 0, 3)
+ set_extmark(ns, marks[2], 0, 4)
+ set_extmark(ns, marks[3], 1, 0)
+ set_extmark(ns, marks[4], 1, 5)
+ set_extmark(ns, marks[5], 2, 0)
+ set_extmark(ns, marks[6], 1, 2)
+ feed([[:1,2s:5\n67:X<cr>]])
+ check_undo_redo(ns, marks[1], 0, 3, 0, 3)
+ check_undo_redo(ns, marks[2], 0, 4, 0, 5)
+ check_undo_redo(ns, marks[3], 1, 0, 0, 5)
+ check_undo_redo(ns, marks[4], 1, 5, 0, 8)
+ check_undo_redo(ns, marks[5], 2, 0, 1, 0)
+ check_undo_redo(ns, marks[6], 1, 2, 0, 5)
+ end)
+
+ it('substitions with multiple newlines in pattern', function()
+ feed('A<cr>67890<cr>xx<esc>')
+ set_extmark(ns, marks[1], 0, 4)
+ set_extmark(ns, marks[2], 0, 5)
+ set_extmark(ns, marks[3], 1, 0)
+ set_extmark(ns, marks[4], 1, 5)
+ set_extmark(ns, marks[5], 2, 0)
+ feed([[:1,2s:\n.*\n:X<cr>]])
+ check_undo_redo(ns, marks[1], 0, 4, 0, 4)
+ check_undo_redo(ns, marks[2], 0, 5, 0, 6)
+ check_undo_redo(ns, marks[3], 1, 0, 0, 6)
+ check_undo_redo(ns, marks[4], 1, 5, 0, 6)
+ check_undo_redo(ns, marks[5], 2, 0, 0, 6)
+ end)
+
+ it('substitions over multiple lines with replace in substition', function()
+ feed('A<cr>67890<cr>xx<esc>')
+ set_extmark(ns, marks[1], 0, 1)
+ set_extmark(ns, marks[2], 0, 2)
+ set_extmark(ns, marks[3], 0, 4)
+ set_extmark(ns, marks[4], 1, 0)
+ set_extmark(ns, marks[5], 2, 0)
+ feed([[:1,2s:3:\r<cr>]])
+ check_undo_redo(ns, marks[1], 0, 1, 0, 1)
+ check_undo_redo(ns, marks[2], 0, 2, 1, 0)
+ check_undo_redo(ns, marks[3], 0, 4, 1, 1)
+ check_undo_redo(ns, marks[4], 1, 0, 2, 0)
+ check_undo_redo(ns, marks[5], 2, 0, 3, 0)
+ feed('u')
+ feed([[:1,2s:3:\rxx<cr>]])
+ eq({1, 3}, curbufmeths.get_extmark_by_id(ns, marks[3]))
+ end)
+
+ it('substitions over multiple lines with replace in substition', function()
+ feed('A<cr>x3<cr>xx<esc>')
+ set_extmark(ns, marks[1], 1, 0)
+ set_extmark(ns, marks[2], 1, 1)
+ set_extmark(ns, marks[3], 1, 2)
+ feed([[:2,2s:3:\r<cr>]])
+ check_undo_redo(ns, marks[1], 1, 0, 1, 0)
+ check_undo_redo(ns, marks[2], 1, 1, 2, 0)
+ check_undo_redo(ns, marks[3], 1, 2, 2, 0)
+ end)
+
+ it('substitions over multiple lines with replace in substition', function()
+ feed('A<cr>x3<cr>xx<esc>')
+ set_extmark(ns, marks[1], 0, 1)
+ set_extmark(ns, marks[2], 0, 2)
+ set_extmark(ns, marks[3], 0, 4)
+ set_extmark(ns, marks[4], 1, 1)
+ set_extmark(ns, marks[5], 2, 0)
+ feed([[:1,2s:3:\r<cr>]])
+ check_undo_redo(ns, marks[1], 0, 1, 0, 1)
+ check_undo_redo(ns, marks[2], 0, 2, 1, 0)
+ check_undo_redo(ns, marks[3], 0, 4, 1, 1)
+ check_undo_redo(ns, marks[4], 1, 1, 3, 0)
+ check_undo_redo(ns, marks[5], 2, 0, 4, 0)
+ feed('u')
+ feed([[:1,2s:3:\rxx<cr>]])
+ check_undo_redo(ns, marks[3], 0, 4, 1, 3)
+ end)
+
+ it('substitions with newline in match and sub, delta is 0', function()
+ feed('A<cr>67890<cr>xx<esc>')
+ set_extmark(ns, marks[1], 0, 3)
+ set_extmark(ns, marks[2], 0, 4)
+ set_extmark(ns, marks[3], 0, 5)
+ set_extmark(ns, marks[4], 1, 0)
+ set_extmark(ns, marks[5], 1, 5)
+ set_extmark(ns, marks[6], 2, 0)
+ feed([[:1,1s:5\n:\r<cr>]])
+ check_undo_redo(ns, marks[1], 0, 3, 0, 3)
+ check_undo_redo(ns, marks[2], 0, 4, 1, 0)
+ check_undo_redo(ns, marks[3], 0, 5, 1, 0)
+ check_undo_redo(ns, marks[4], 1, 0, 1, 0)
+ check_undo_redo(ns, marks[5], 1, 5, 1, 5)
+ check_undo_redo(ns, marks[6], 2, 0, 2, 0)
+ end)
+
+ it('substitions with newline in match and sub, delta > 0', function()
+ feed('A<cr>67890<cr>xx<esc>')
+ set_extmark(ns, marks[1], 0, 3)
+ set_extmark(ns, marks[2], 0, 4)
+ set_extmark(ns, marks[3], 0, 5)
+ set_extmark(ns, marks[4], 1, 0)
+ set_extmark(ns, marks[5], 1, 5)
+ set_extmark(ns, marks[6], 2, 0)
+ feed([[:1,1s:5\n:\r\r<cr>]])
+ check_undo_redo(ns, marks[1], 0, 3, 0, 3)
+ check_undo_redo(ns, marks[2], 0, 4, 2, 0)
+ check_undo_redo(ns, marks[3], 0, 5, 2, 0)
+ check_undo_redo(ns, marks[4], 1, 0, 2, 0)
+ check_undo_redo(ns, marks[5], 1, 5, 2, 5)
+ check_undo_redo(ns, marks[6], 2, 0, 3, 0)
+ end)
+
+ it('substitions with newline in match and sub, delta < 0', function()
+ feed('A<cr>67890<cr>xx<cr>xx<esc>')
+ set_extmark(ns, marks[1], 0, 3)
+ set_extmark(ns, marks[2], 0, 4)
+ set_extmark(ns, marks[3], 0, 5)
+ set_extmark(ns, marks[4], 1, 0)
+ set_extmark(ns, marks[5], 1, 5)
+ set_extmark(ns, marks[6], 2, 1)
+ set_extmark(ns, marks[7], 3, 0)
+ feed([[:1,2s:5\n.*\n:\r<cr>]])
+ check_undo_redo(ns, marks[1], 0, 3, 0, 3)
+ check_undo_redo(ns, marks[2], 0, 4, 1, 0)
+ check_undo_redo(ns, marks[3], 0, 5, 1, 0)
+ check_undo_redo(ns, marks[4], 1, 0, 1, 0)
+ check_undo_redo(ns, marks[5], 1, 5, 1, 0)
+ check_undo_redo(ns, marks[6], 2, 1, 1, 1)
+ check_undo_redo(ns, marks[7], 3, 0, 2, 0)
+ end)
+
+ it('substitions with backrefs, newline inserted into sub', function()
+ feed('A<cr>67890<cr>xx<cr>xx<esc>')
+ set_extmark(ns, marks[1], 0, 3)
+ set_extmark(ns, marks[2], 0, 4)
+ set_extmark(ns, marks[3], 0, 5)
+ set_extmark(ns, marks[4], 1, 0)
+ set_extmark(ns, marks[5], 1, 5)
+ set_extmark(ns, marks[6], 2, 0)
+ feed([[:1,1s:5\(\n\):\0\1<cr>]])
+ check_undo_redo(ns, marks[1], 0, 3, 0, 3)
+ check_undo_redo(ns, marks[2], 0, 4, 2, 0)
+ check_undo_redo(ns, marks[3], 0, 5, 2, 0)
+ check_undo_redo(ns, marks[4], 1, 0, 2, 0)
+ check_undo_redo(ns, marks[5], 1, 5, 2, 5)
+ check_undo_redo(ns, marks[6], 2, 0, 3, 0)
+ end)
+
+ it('substitions a ^', function()
+ set_extmark(ns, marks[1], 0, 0)
+ set_extmark(ns, marks[2], 0, 1)
+ feed([[:s:^:x<cr>]])
+ check_undo_redo(ns, marks[1], 0, 0, 0, 1)
+ check_undo_redo(ns, marks[2], 0, 1, 0, 2)
+ end)
+
+ it('using <c-a> without increase in order of magnitude', function()
+ -- do_addsub in ops.c
+ feed('ddiabc998xxx<esc>Tc')
+ set_extmark(ns, marks[1], 0, 2)
+ set_extmark(ns, marks[2], 0, 3)
+ set_extmark(ns, marks[3], 0, 5)
+ set_extmark(ns, marks[4], 0, 6)
+ set_extmark(ns, marks[5], 0, 7)
+ feed('<c-a>')
+ check_undo_redo(ns, marks[1], 0, 2, 0, 2)
+ check_undo_redo(ns, marks[2], 0, 3, 0, 6)
+ check_undo_redo(ns, marks[3], 0, 5, 0, 6)
+ check_undo_redo(ns, marks[4], 0, 6, 0, 6)
+ check_undo_redo(ns, marks[5], 0, 7, 0, 7)
+ end)
+
+ it('using <c-a> when increase in order of magnitude', function()
+ -- do_addsub in ops.c
+ feed('ddiabc999xxx<esc>Tc')
+ set_extmark(ns, marks[1], 0, 2)
+ set_extmark(ns, marks[2], 0, 3)
+ set_extmark(ns, marks[3], 0, 5)
+ set_extmark(ns, marks[4], 0, 6)
+ set_extmark(ns, marks[5], 0, 7)
+ feed('<c-a>')
+ check_undo_redo(ns, marks[1], 0, 2, 0, 2)
+ check_undo_redo(ns, marks[2], 0, 3, 0, 7)
+ check_undo_redo(ns, marks[3], 0, 5, 0, 7)
+ check_undo_redo(ns, marks[4], 0, 6, 0, 7)
+ check_undo_redo(ns, marks[5], 0, 7, 0, 8)
+ end)
+
+ it('using <c-a> when negative and without decrease in order of magnitude', function()
+ feed('ddiabc-999xxx<esc>T-')
+ set_extmark(ns, marks[1], 0, 2)
+ set_extmark(ns, marks[2], 0, 3)
+ set_extmark(ns, marks[3], 0, 6)
+ set_extmark(ns, marks[4], 0, 7)
+ set_extmark(ns, marks[5], 0, 8)
+ feed('<c-a>')
+ check_undo_redo(ns, marks[1], 0, 2, 0, 2)
+ check_undo_redo(ns, marks[2], 0, 3, 0, 7)
+ check_undo_redo(ns, marks[3], 0, 6, 0, 7)
+ check_undo_redo(ns, marks[4], 0, 7, 0, 7)
+ check_undo_redo(ns, marks[5], 0, 8, 0, 8)
+ end)
+
+ it('using <c-a> when negative and decrease in order of magnitude', function()
+ feed('ddiabc-1000xxx<esc>T-')
+ set_extmark(ns, marks[1], 0, 2)
+ set_extmark(ns, marks[2], 0, 3)
+ set_extmark(ns, marks[3], 0, 7)
+ set_extmark(ns, marks[4], 0, 8)
+ set_extmark(ns, marks[5], 0, 9)
+ feed('<c-a>')
+ check_undo_redo(ns, marks[1], 0, 2, 0, 2)
+ check_undo_redo(ns, marks[2], 0, 3, 0, 7)
+ check_undo_redo(ns, marks[3], 0, 7, 0, 7)
+ check_undo_redo(ns, marks[4], 0, 8, 0, 7)
+ check_undo_redo(ns, marks[5], 0, 9, 0, 8)
+ end)
+
+ it('using <c-x> without decrease in order of magnitude', function()
+ -- do_addsub in ops.c
+ feed('ddiabc999xxx<esc>Tc')
+ set_extmark(ns, marks[1], 0, 2)
+ set_extmark(ns, marks[2], 0, 3)
+ set_extmark(ns, marks[3], 0, 5)
+ set_extmark(ns, marks[4], 0, 6)
+ set_extmark(ns, marks[5], 0, 7)
+ feed('<c-x>')
+ check_undo_redo(ns, marks[1], 0, 2, 0, 2)
+ check_undo_redo(ns, marks[2], 0, 3, 0, 6)
+ check_undo_redo(ns, marks[3], 0, 5, 0, 6)
+ check_undo_redo(ns, marks[4], 0, 6, 0, 6)
+ check_undo_redo(ns, marks[5], 0, 7, 0, 7)
+ end)
+
+ it('using <c-x> when decrease in order of magnitude', function()
+ -- do_addsub in ops.c
+ feed('ddiabc1000xxx<esc>Tc')
+ set_extmark(ns, marks[1], 0, 2)
+ set_extmark(ns, marks[2], 0, 3)
+ set_extmark(ns, marks[3], 0, 6)
+ set_extmark(ns, marks[4], 0, 7)
+ set_extmark(ns, marks[5], 0, 8)
+ feed('<c-x>')
+ check_undo_redo(ns, marks[1], 0, 2, 0, 2)
+ check_undo_redo(ns, marks[2], 0, 3, 0, 6)
+ check_undo_redo(ns, marks[3], 0, 6, 0, 6)
+ check_undo_redo(ns, marks[4], 0, 7, 0, 6)
+ check_undo_redo(ns, marks[5], 0, 8, 0, 7)
+ end)
+
+ it('using <c-x> when negative and without increase in order of magnitude', function()
+ feed('ddiabc-998xxx<esc>T-')
+ set_extmark(ns, marks[1], 0, 2)
+ set_extmark(ns, marks[2], 0, 3)
+ set_extmark(ns, marks[3], 0, 6)
+ set_extmark(ns, marks[4], 0, 7)
+ set_extmark(ns, marks[5], 0, 8)
+ feed('<c-x>')
+ check_undo_redo(ns, marks[1], 0, 2, 0, 2)
+ check_undo_redo(ns, marks[2], 0, 3, 0, 7)
+ check_undo_redo(ns, marks[3], 0, 6, 0, 7)
+ check_undo_redo(ns, marks[4], 0, 7, 0, 7)
+ check_undo_redo(ns, marks[5], 0, 8, 0, 8)
+ end)
+
+ it('using <c-x> when negative and increase in order of magnitude', function()
+ feed('ddiabc-999xxx<esc>T-')
+ set_extmark(ns, marks[1], 0, 2)
+ set_extmark(ns, marks[2], 0, 3)
+ set_extmark(ns, marks[3], 0, 6)
+ set_extmark(ns, marks[4], 0, 7)
+ set_extmark(ns, marks[5], 0, 8)
+ feed('<c-x>')
+ check_undo_redo(ns, marks[1], 0, 2, 0, 2)
+ check_undo_redo(ns, marks[2], 0, 3, 0, 8)
+ check_undo_redo(ns, marks[3], 0, 6, 0, 8)
+ check_undo_redo(ns, marks[4], 0, 7, 0, 8)
+ check_undo_redo(ns, marks[5], 0, 8, 0, 9)
+ end)
+
+ it('throws consistent error codes', function()
+ local ns_invalid = ns2 + 1
+ eq("Invalid ns_id", pcall_err(set_extmark, ns_invalid, marks[1], positions[1][1], positions[1][2]))
+ eq("Invalid ns_id", pcall_err(curbufmeths.del_extmark, ns_invalid, marks[1]))
+ eq("Invalid ns_id", pcall_err(get_extmarks, ns_invalid, positions[1], positions[2]))
+ eq("Invalid ns_id", pcall_err(curbufmeths.get_extmark_by_id, ns_invalid, marks[1]))
+ end)
+
+ it('when col = line-length, set the mark on eol', function()
+ set_extmark(ns, marks[1], 0, -1)
+ local rv = curbufmeths.get_extmark_by_id(ns, marks[1])
+ eq({0, init_text:len()}, rv)
+ -- Test another
+ set_extmark(ns, marks[1], 0, -1)
+ rv = curbufmeths.get_extmark_by_id(ns, marks[1])
+ eq({0, init_text:len()}, rv)
+ end)
+
+ it('when col = line-length, set the mark on eol', function()
+ local invalid_col = init_text:len() + 1
+ eq("col value outside range", pcall_err(set_extmark, ns, marks[1], 0, invalid_col))
+ end)
+
+ it('fails when line > line_count', function()
+ local invalid_col = init_text:len() + 1
+ local invalid_lnum = 3
+ eq('line value outside range', pcall_err(set_extmark, ns, marks[1], invalid_lnum, invalid_col))
+ eq({}, curbufmeths.get_extmark_by_id(ns, marks[1]))
+ end)
+
+ it('bug from check_col in extmark_set', function()
+ -- This bug was caused by extmark_set always using check_col. check_col
+ -- always uses the current buffer. This wasn't working during undo so we
+ -- now use check_col and check_lnum only when they are required.
+ feed('A<cr>67890<cr>xx<esc>')
+ feed('A<cr>12345<cr>67890<cr>xx<esc>')
+ set_extmark(ns, marks[1], 3, 4)
+ feed([[:1,5s:5\n:5 <cr>]])
+ check_undo_redo(ns, marks[1], 3, 4, 2, 6)
+ end)
+
+ it('in read-only buffer', function()
+ command("view! runtime/doc/help.txt")
+ eq(true, curbufmeths.get_option('ro'))
+ local id = set_extmark(ns, 0, 0, 2)
+ eq({{id, 0, 2}}, get_extmarks(ns,0, -1))
+ end)
+
+ it('can set a mark to other buffer', function()
+ local buf = request('nvim_create_buf', 0, 1)
+ request('nvim_buf_set_lines', buf, 0, -1, 1, {"", ""})
+ local id = bufmeths.set_extmark(buf, ns, 0, 1, 0, {})
+ eq({{id, 1, 0}}, bufmeths.get_extmarks(buf, ns, 0, -1, {}))
+ end)
+
+ it('does not crash with append/delete/undo seqence', function()
+ meths.exec([[
+ let ns = nvim_create_namespace('myplugin')
+ call nvim_buf_set_extmark(0, ns, 0, 0, 0, {})
+ call append(0, '')
+ %delete
+ undo]],false)
+ eq(2, meths.eval('1+1')) -- did not crash
+ end)
+end)
+
+describe('Extmarks buffer api with many marks', function()
+ local ns1
+ local ns2
+ local ns_marks = {}
+ before_each(function()
+ clear()
+ ns1 = request('nvim_create_namespace', "ns1")
+ ns2 = request('nvim_create_namespace', "ns2")
+ ns_marks = {[ns1]={}, [ns2]={}}
+ local lines = {}
+ for i = 1,30 do
+ lines[#lines+1] = string.rep("x ",i)
+ end
+ curbufmeths.set_lines(0, -1, true, lines)
+ local ns = ns1
+ local q = 0
+ for i = 0,29 do
+ for j = 0,i do
+ local id = set_extmark(ns,0, i,j)
+ eq(nil, ns_marks[ns][id])
+ ok(id > 0)
+ ns_marks[ns][id] = {i,j}
+ ns = ns1+ns2-ns
+ q = q + 1
+ end
+ end
+ eq(233, #ns_marks[ns1])
+ eq(232, #ns_marks[ns2])
+
+ end)
+
+ local function get_marks(ns)
+ local mark_list = get_extmarks(ns, 0, -1)
+ local marks = {}
+ for _, mark in ipairs(mark_list) do
+ local id, row, col = unpack(mark)
+ eq(nil, marks[id], "duplicate mark")
+ marks[id] = {row,col}
+ end
+ return marks
+ end
+
+ it("can get marks", function()
+ eq(ns_marks[ns1], get_marks(ns1))
+ eq(ns_marks[ns2], get_marks(ns2))
+ end)
+
+ it("can clear all marks in ns", function()
+ curbufmeths.clear_namespace(ns1, 0, -1)
+ eq({}, get_marks(ns1))
+ eq(ns_marks[ns2], get_marks(ns2))
+ curbufmeths.clear_namespace(ns2, 0, -1)
+ eq({}, get_marks(ns1))
+ eq({}, get_marks(ns2))
+ end)
+
+ it("can clear line range", function()
+ curbufmeths.clear_namespace(ns1, 10, 20)
+ for id, mark in pairs(ns_marks[ns1]) do
+ if 10 <= mark[1] and mark[1] < 20 then
+ ns_marks[ns1][id] = nil
+ end
+ end
+ eq(ns_marks[ns1], get_marks(ns1))
+ eq(ns_marks[ns2], get_marks(ns2))
+ end)
+
+ it("can delete line", function()
+ feed('10Gdd')
+ for _, marks in pairs(ns_marks) do
+ for id, mark in pairs(marks) do
+ if mark[1] == 9 then
+ marks[id] = {9,0}
+ elseif mark[1] >= 10 then
+ mark[1] = mark[1] - 1
+ end
+ end
+ end
+ eq(ns_marks[ns1], get_marks(ns1))
+ eq(ns_marks[ns2], get_marks(ns2))
+ end)
+
+ it("can delete lines", function()
+ feed('10G10dd')
+ for _, marks in pairs(ns_marks) do
+ for id, mark in pairs(marks) do
+ if 9 <= mark[1] and mark[1] < 19 then
+ marks[id] = {9,0}
+ elseif mark[1] >= 19 then
+ mark[1] = mark[1] - 10
+ end
+ end
+ end
+ eq(ns_marks[ns1], get_marks(ns1))
+ eq(ns_marks[ns2], get_marks(ns2))
+ end)
+
+ it("can wipe buffer", function()
+ command('bwipe!')
+ eq({}, get_marks(ns1))
+ eq({}, get_marks(ns2))
+ end)
+end)
diff --git a/test/functional/api/highlight_spec.lua b/test/functional/api/highlight_spec.lua
index 5297c6454e..a9d4c72d31 100644
--- a/test/functional/api/highlight_spec.lua
+++ b/test/functional/api/highlight_spec.lua
@@ -4,6 +4,9 @@ local Screen = require('test.functional.ui.screen')
local eq, eval = helpers.eq, helpers.eval
local command = helpers.command
local meths = helpers.meths
+local funcs = helpers.funcs
+local pcall_err = helpers.pcall_err
+local ok = helpers.ok
describe('API: highlight',function()
local expected_rgb = {
@@ -67,6 +70,22 @@ describe('API: highlight',function()
eq(false, err)
eq('Invalid highlight id: -1',
string.match(emsg, 'Invalid.*'))
+
+ -- Test highlight group without ctermbg value.
+ command('hi Normal ctermfg=red ctermbg=yellow')
+ command('hi NewConstant ctermfg=green guifg=white guibg=blue')
+ hl_id = eval("hlID('NewConstant')")
+ eq({foreground = 10,}, meths.get_hl_by_id(hl_id, false))
+
+ -- Test highlight group without ctermfg value.
+ command('hi clear NewConstant')
+ command('hi NewConstant ctermbg=Magenta guifg=white guibg=blue')
+ eq({background = 13,}, meths.get_hl_by_id(hl_id, false))
+
+ -- Test highlight group with ctermfg and ctermbg values.
+ command('hi clear NewConstant')
+ command('hi NewConstant ctermfg=green ctermbg=Magenta guifg=white guibg=blue')
+ eq({foreground = 10, background = 13,}, meths.get_hl_by_id(hl_id, false))
end)
it("nvim_get_hl_by_name", function()
@@ -110,4 +129,20 @@ describe('API: highlight',function()
meths.get_hl_by_name('cursorline', 0));
end)
+
+ it('nvim_get_hl_id_by_name', function()
+ -- precondition: use a hl group that does not yet exist
+ eq('Invalid highlight name: Shrubbery', pcall_err(meths.get_hl_by_name, "Shrubbery", true))
+ eq(0, funcs.hlID("Shrubbery"))
+
+ local hl_id = meths.get_hl_id_by_name("Shrubbery")
+ ok(hl_id > 0)
+ eq(hl_id, funcs.hlID("Shrubbery"))
+
+ command('hi Shrubbery guifg=#888888 guibg=#888888')
+ eq({foreground=tonumber("0x888888"), background=tonumber("0x888888")},
+ meths.get_hl_by_id(hl_id, true))
+ eq({foreground=tonumber("0x888888"), background=tonumber("0x888888")},
+ meths.get_hl_by_name("Shrubbery", true))
+ end)
end)
diff --git a/test/functional/api/keymap_spec.lua b/test/functional/api/keymap_spec.lua
index 773207d360..5da2c6b531 100644
--- a/test/functional/api/keymap_spec.lua
+++ b/test/functional/api/keymap_spec.lua
@@ -21,6 +21,7 @@ describe('nvim_get_keymap', function()
local foo_bar_string = 'nnoremap foo bar'
local foo_bar_map_table = {
lhs='foo',
+ script=0,
silent=0,
rhs='bar',
expr=0,
@@ -245,6 +246,7 @@ describe('nvim_get_keymap', function()
it('works correctly despite various &cpo settings', function()
local cpo_table = {
+ script=0,
silent=0,
expr=0,
sid=0,
@@ -302,6 +304,7 @@ describe('nvim_get_keymap', function()
lhs='| |',
rhs='| |',
mode='n',
+ script=0,
silent=0,
expr=0,
sid=0,
@@ -343,6 +346,7 @@ describe('nvim_set_keymap, nvim_del_keymap', function()
to_return.noremap = not opts.noremap and 0 or 1
to_return.lhs = lhs
to_return.rhs = rhs
+ to_return.script = 0
to_return.silent = not opts.silent and 0 or 1
to_return.nowait = not opts.nowait and 0 or 1
to_return.expr = not opts.expr and 0 or 1
@@ -585,13 +589,13 @@ describe('nvim_set_keymap, nvim_del_keymap', function()
end)
it('can set <expr> mappings whose RHS change dynamically', function()
- meths.command_output([[
+ meths.exec([[
function! FlipFlop() abort
if !exists('g:flip') | let g:flip = 0 | endif
let g:flip = !g:flip
return g:flip
endfunction
- ]])
+ ]], true)
eq(1, meths.call_function('FlipFlop', {}))
eq(0, meths.call_function('FlipFlop', {}))
eq(1, meths.call_function('FlipFlop', {}))
diff --git a/test/functional/api/menu_spec.lua b/test/functional/api/menu_spec.lua
index 2cfa0e3e47..34a92477f3 100644
--- a/test/functional/api/menu_spec.lua
+++ b/test/functional/api/menu_spec.lua
@@ -15,10 +15,6 @@ describe("update_menu notification", function()
screen:attach()
end)
- after_each(function()
- screen:detach()
- end)
-
local function expect_sent(expected)
screen:expect{condition=function()
if screen.update_menu ~= expected then
diff --git a/test/functional/api/proc_spec.lua b/test/functional/api/proc_spec.lua
index 063d382790..d828bdf948 100644
--- a/test/functional/api/proc_spec.lua
+++ b/test/functional/api/proc_spec.lua
@@ -10,7 +10,7 @@ local request = helpers.request
local retry = helpers.retry
local NIL = helpers.NIL
-describe('api', function()
+describe('API', function()
before_each(clear)
describe('nvim_get_proc_children', function()
diff --git a/test/functional/api/rpc_fixture.lua b/test/functional/api/rpc_fixture.lua
index 87f5a91115..94df751363 100644
--- a/test/functional/api/rpc_fixture.lua
+++ b/test/functional/api/rpc_fixture.lua
@@ -1,12 +1,8 @@
-local deps_prefix = (os.getenv('DEPS_PREFIX') and os.getenv('DEPS_PREFIX')
- or './.deps/usr')
-
-package.path = deps_prefix .. '/share/lua/5.1/?.lua;' ..
- deps_prefix .. '/share/lua/5.1/?/init.lua;' ..
- package.path
-
-package.cpath = deps_prefix .. '/lib/lua/5.1/?.so;' ..
- package.cpath
+--- RPC server fixture.
+--
+-- Lua's paths are passed as arguments to reflect the path in the test itself.
+package.path = arg[1]
+package.cpath = arg[2]
local mpack = require('mpack')
local StdioStream = require('nvim.stdio_stream')
diff --git a/test/functional/api/server_requests_spec.lua b/test/functional/api/server_requests_spec.lua
index e275d8cd35..237a4b01e4 100644
--- a/test/functional/api/server_requests_spec.lua
+++ b/test/functional/api/server_requests_spec.lua
@@ -241,10 +241,14 @@ describe('server -> client', function()
\ 'rpc': v:true
\ }
]])
- meths.set_var("args", {helpers.test_lua_prg,
- 'test/functional/api/rpc_fixture.lua'})
+ meths.set_var("args", {
+ helpers.test_lua_prg,
+ 'test/functional/api/rpc_fixture.lua',
+ package.path,
+ package.cpath,
+ })
jobid = eval("jobstart(g:args, g:job_opts)")
- neq(0, 'jobid')
+ neq(0, jobid)
end)
after_each(function()
@@ -254,7 +258,11 @@ describe('server -> client', function()
if helpers.pending_win32(pending) then return end
it('rpc and text stderr can be combined', function()
- eq("ok",funcs.rpcrequest(jobid, "poll"))
+ local status, rv = pcall(funcs.rpcrequest, jobid, 'poll')
+ if not status then
+ error(string.format('missing nvim Lua module? (%s)', rv))
+ end
+ eq('ok', rv)
funcs.rpcnotify(jobid, "ping")
eq({'notification', 'pong', {}}, next_msg())
eq("done!",funcs.rpcrequest(jobid, "write_stderr", "fluff\n"))
@@ -309,8 +317,7 @@ describe('server -> client', function()
set_session(server)
local status, address = pcall(funcs.serverstart, "127.0.0.1:")
if not status then
- pending('no ipv4 stack', function() end)
- return
+ pending('no ipv4 stack')
end
eq('127.0.0.1:', string.sub(address,1,10))
connect_test(server, 'tcp', address)
@@ -321,8 +328,7 @@ describe('server -> client', function()
set_session(server)
local status, address = pcall(funcs.serverstart, '::1:')
if not status then
- pending('no ipv6 stack', function() end)
- return
+ pending('no ipv6 stack')
end
eq('::1:', string.sub(address,1,4))
connect_test(server, 'tcp', address)
@@ -339,11 +345,6 @@ describe('server -> client', function()
describe('connecting to its own pipe address', function()
it('does not deadlock', function()
- if not helpers.isCI('travis') and helpers.is_os('mac') then
- -- It does, in fact, deadlock on QuickBuild. #6851
- pending("deadlocks on QuickBuild", function() end)
- return
- end
local address = funcs.serverlist()[1]
local first = string.sub(address,1,1)
ok(first == '/' or first == '\\')
diff --git a/test/functional/api/tabpage_spec.lua b/test/functional/api/tabpage_spec.lua
index ed7ce72597..20b3163d95 100644
--- a/test/functional/api/tabpage_spec.lua
+++ b/test/functional/api/tabpage_spec.lua
@@ -24,6 +24,10 @@ describe('api/tabpage', function()
nvim('set_current_win', win3)
eq(win3, tabpage('get_win', tab2))
end)
+
+ it('validates args', function()
+ eq('Invalid tabpage id: 23', pcall_err(tabpage, 'list_wins', 23))
+ end)
end)
describe('{get,set,del}_var', function()
diff --git a/test/functional/api/vim_spec.lua b/test/functional/api/vim_spec.lua
index 8b77dbcaa6..72e810e3e4 100644
--- a/test/functional/api/vim_spec.lua
+++ b/test/functional/api/vim_spec.lua
@@ -16,11 +16,14 @@ local parse_context = helpers.parse_context
local request = helpers.request
local source = helpers.source
local next_msg = helpers.next_msg
+local tmpname = helpers.tmpname
+local write_file = helpers.write_file
local pcall_err = helpers.pcall_err
local format_string = helpers.format_string
local intchar2lua = helpers.intchar2lua
local mergedicts_copy = helpers.mergedicts_copy
+local endswith = helpers.endswith
describe('API', function()
before_each(clear)
@@ -74,9 +77,127 @@ describe('API', function()
eq({mode='i', blocking=false}, nvim("get_mode"))
end)
+ describe('nvim_exec', function()
+ it('one-line input', function()
+ nvim('exec', "let x1 = 'a'", false)
+ eq('a', nvim('get_var', 'x1'))
+ end)
+
+ it(':verbose set {option}?', function()
+ nvim('exec', 'set nowrap', false)
+ eq('nowrap\n\tLast set from anonymous :source',
+ nvim('exec', 'verbose set wrap?', true))
+ end)
+
+ it('multiline input', function()
+ -- Heredoc + empty lines.
+ nvim('exec', "let x2 = 'a'\n", false)
+ eq('a', nvim('get_var', 'x2'))
+ nvim('exec','lua <<EOF\n\n\n\ny=3\n\n\nEOF', false)
+ eq(3, nvim('eval', "luaeval('y')"))
+
+ eq('', nvim('exec', 'lua <<EOF\ny=3\nEOF', false))
+ eq(3, nvim('eval', "luaeval('y')"))
+
+ -- Multiple statements
+ nvim('exec', 'let x1=1\nlet x2=2\nlet x3=3\n', false)
+ eq(1, nvim('eval', 'x1'))
+ eq(2, nvim('eval', 'x2'))
+ eq(3, nvim('eval', 'x3'))
+
+ -- Functions
+ nvim('exec', 'function Foo()\ncall setline(1,["xxx"])\nendfunction', false)
+ eq(nvim('get_current_line'), '')
+ nvim('exec', 'call Foo()', false)
+ eq(nvim('get_current_line'), 'xxx')
+
+ -- Autocmds
+ nvim('exec','autocmd BufAdd * :let x1 = "Hello"', false)
+ nvim('command', 'new foo')
+ eq('Hello', request('nvim_eval', 'g:x1'))
+ end)
+
+ it('non-ASCII input', function()
+ nvim('exec', [=[
+ new
+ exe "normal! i ax \n Ax "
+ :%s/ax/--a1234--/g | :%s/Ax/--A1234--/g
+ ]=], false)
+ nvim('command', '1')
+ eq(' --a1234-- ', nvim('get_current_line'))
+ nvim('command', '2')
+ eq(' --A1234-- ', nvim('get_current_line'))
+
+ nvim('exec', [[
+ new
+ call setline(1,['xxx'])
+ call feedkeys('r')
+ call feedkeys('ñ', 'xt')
+ ]], false)
+ eq('ñxx', nvim('get_current_line'))
+ end)
+
+ it('execution error', function()
+ eq('Vim:E492: Not an editor command: bogus_command',
+ pcall_err(request, 'nvim_exec', 'bogus_command', false))
+ eq('', nvim('eval', 'v:errmsg')) -- v:errmsg was not updated.
+ eq('', eval('v:exception'))
+
+ eq('Vim(buffer):E86: Buffer 23487 does not exist',
+ pcall_err(request, 'nvim_exec', 'buffer 23487', false))
+ eq('', eval('v:errmsg')) -- v:errmsg was not updated.
+ eq('', eval('v:exception'))
+ end)
+
+ it('recursion', function()
+ local fname = tmpname()
+ write_file(fname, 'let x1 = "set from :source file"\n')
+ -- nvim_exec
+ -- :source
+ -- nvim_exec
+ request('nvim_exec', [[
+ let x2 = substitute('foo','o','X','g')
+ let x4 = 'should be overwritten'
+ call nvim_exec("source ]]..fname..[[\nlet x3 = substitute('foo','foo','set by recursive nvim_exec','g')\nlet x5='overwritten'\nlet x4=x5\n", v:false)
+ ]], false)
+ eq('set from :source file', request('nvim_get_var', 'x1'))
+ eq('fXX', request('nvim_get_var', 'x2'))
+ eq('set by recursive nvim_exec', request('nvim_get_var', 'x3'))
+ eq('overwritten', request('nvim_get_var', 'x4'))
+ eq('overwritten', request('nvim_get_var', 'x5'))
+ os.remove(fname)
+ end)
+
+ it('traceback', function()
+ local fname = tmpname()
+ write_file(fname, 'echo "hello"\n')
+ local sourcing_fname = tmpname()
+ write_file(sourcing_fname, 'call nvim_exec("source '..fname..'", v:false)\n')
+ meths.exec('set verbose=2', false)
+ local traceback_output = 'line 0: sourcing "'..sourcing_fname..'"\n'..
+ 'line 0: sourcing "'..fname..'"\n'..
+ 'hello\n'..
+ 'finished sourcing '..fname..'\n'..
+ 'continuing in nvim_exec() called at '..sourcing_fname..':1\n'..
+ 'finished sourcing '..sourcing_fname..'\n'..
+ 'continuing in nvim_exec() called at nvim_exec():0'
+ eq(traceback_output,
+ meths.exec('call nvim_exec("source '..sourcing_fname..'", v:false)', true))
+ os.remove(fname)
+ os.remove(sourcing_fname)
+ end)
+
+ it('returns output', function()
+ eq('this is spinal tap',
+ nvim('exec', 'lua <<EOF\n\n\nprint("this is spinal tap")\n\n\nEOF', true))
+ eq('', nvim('exec', 'echo', true))
+ eq('foo 42', nvim('exec', 'echo "foo" 42', true))
+ end)
+ end)
+
describe('nvim_command', function()
it('works', function()
- local fname = helpers.tmpname()
+ local fname = tmpname()
nvim('command', 'new')
nvim('command', 'edit '..fname)
nvim('command', 'normal itesting\napi')
@@ -313,41 +434,44 @@ describe('API', function()
end)
end)
- describe('nvim_execute_lua', function()
+ describe('nvim_exec_lua', function()
it('works', function()
- meths.execute_lua('vim.api.nvim_set_var("test", 3)', {})
+ meths.exec_lua('vim.api.nvim_set_var("test", 3)', {})
eq(3, meths.get_var('test'))
- eq(17, meths.execute_lua('a, b = ...\nreturn a + b', {10,7}))
+ eq(17, meths.exec_lua('a, b = ...\nreturn a + b', {10,7}))
- eq(NIL, meths.execute_lua('function xx(a,b)\nreturn a..b\nend',{}))
+ eq(NIL, meths.exec_lua('function xx(a,b)\nreturn a..b\nend',{}))
+ eq("xy", meths.exec_lua('return xx(...)', {'x','y'}))
+
+ -- Deprecated name: nvim_execute_lua.
eq("xy", meths.execute_lua('return xx(...)', {'x','y'}))
end)
it('reports errors', function()
eq([[Error loading lua: [string "<nvim>"]:1: '=' expected near '+']],
- pcall_err(meths.execute_lua, 'a+*b', {}))
+ pcall_err(meths.exec_lua, 'a+*b', {}))
eq([[Error loading lua: [string "<nvim>"]:1: unexpected symbol near '1']],
- pcall_err(meths.execute_lua, '1+2', {}))
+ pcall_err(meths.exec_lua, '1+2', {}))
eq([[Error loading lua: [string "<nvim>"]:1: unexpected symbol]],
- pcall_err(meths.execute_lua, 'aa=bb\0', {}))
+ pcall_err(meths.exec_lua, 'aa=bb\0', {}))
eq([[Error executing lua: [string "<nvim>"]:1: attempt to call global 'bork' (a nil value)]],
- pcall_err(meths.execute_lua, 'bork()', {}))
+ pcall_err(meths.exec_lua, 'bork()', {}))
eq('Error executing lua: [string "<nvim>"]:1: did\nthe\nfail',
- pcall_err(meths.execute_lua, 'error("did\\nthe\\nfail")', {}))
+ pcall_err(meths.exec_lua, 'error("did\\nthe\\nfail")', {}))
end)
it('uses native float values', function()
- eq(2.5, meths.execute_lua("return select(1, ...)", {2.5}))
- eq("2.5", meths.execute_lua("return vim.inspect(...)", {2.5}))
+ eq(2.5, meths.exec_lua("return select(1, ...)", {2.5}))
+ eq("2.5", meths.exec_lua("return vim.inspect(...)", {2.5}))
-- "special" float values are still accepted as return values.
- eq(2.5, meths.execute_lua("return vim.api.nvim_eval('2.5')", {}))
- eq("{\n [false] = 2.5,\n [true] = 3\n}", meths.execute_lua("return vim.inspect(vim.api.nvim_eval('2.5'))", {}))
+ eq(2.5, meths.exec_lua("return vim.api.nvim_eval('2.5')", {}))
+ eq("{\n [false] = 2.5,\n [true] = 3\n}", meths.exec_lua("return vim.inspect(vim.api.nvim_eval('2.5'))", {}))
end)
end)
@@ -358,6 +482,11 @@ describe('API', function()
eq(true, status) -- nvim_input() did not fail.
eq("E117:", v_errnum) -- v:errmsg was updated.
end)
+
+ it('does not crash even if trans_special result is largest #11788, #12287', function()
+ command("call nvim_input('<M-'.nr2char(0x40000000).'>')")
+ eq(1, eval('1'))
+ end)
end)
describe('nvim_paste', function()
@@ -447,13 +576,35 @@ describe('API', function()
eq({0,7,1,0}, funcs.getpos('.'))
eq(false, nvim('get_option', 'paste'))
end)
+ it('Replace-mode', function()
+ -- Within single line
+ nvim('put', {'aabbccdd', 'eeffgghh', 'iijjkkll'}, "c", true, false)
+ command('normal l')
+ command('startreplace')
+ nvim('paste', '123456', true, -1)
+ expect([[
+ a123456d
+ eeffgghh
+ iijjkkll]])
+ command('%delete _')
+ -- Across lines
+ nvim('put', {'aabbccdd', 'eeffgghh', 'iijjkkll'}, "c", true, false)
+ command('normal l')
+ command('startreplace')
+ nvim('paste', '123\n456', true, -1)
+ expect([[
+ a123
+ 456d
+ eeffgghh
+ iijjkkll]])
+ end)
it('crlf=false does not break lines at CR, CRLF', function()
nvim('paste', 'line 1\r\n\r\rline 2\nline 3\rline 4\r', false, -1)
expect('line 1\r\n\r\rline 2\nline 3\rline 4\r')
eq({0,3,14,0}, funcs.getpos('.'))
end)
it('vim.paste() failure', function()
- nvim('execute_lua', 'vim.paste = (function(lines, phase) error("fake fail") end)', {})
+ nvim('exec_lua', 'vim.paste = (function(lines, phase) error("fake fail") end)', {})
eq([[Error executing lua: [string "<nvim>"]:1: fake fail]],
pcall_err(request, 'nvim_paste', 'line 1\nline 2\nline 3', false, 1))
end)
@@ -677,7 +828,7 @@ describe('API', function()
ok(nil ~= string.find(rv, 'noequalalways\n'..
'\tLast set from API client %(channel id %d+%)'))
- nvim('execute_lua', 'vim.api.nvim_set_option("equalalways", true)', {})
+ nvim('exec_lua', 'vim.api.nvim_set_option("equalalways", true)', {})
status, rv = pcall(nvim, 'command_output',
'verbose set equalalways?')
eq(true, status)
@@ -1662,7 +1813,7 @@ describe('API', function()
eq({id=1}, meths.get_current_buf())
end)
- it("doesn't cause BufEnter or BufWinEnter autocmds", function()
+ it("does not trigger BufEnter, BufWinEnter", function()
command("let g:fired = v:false")
command("au BufEnter,BufWinEnter * let g:fired = v:true")
@@ -1672,7 +1823,7 @@ describe('API', function()
eq(false, eval('g:fired'))
end)
- it('|scratch-buffer|', function()
+ it('scratch-buffer', function()
eq({id=2}, meths.create_buf(false, true))
eq({id=3}, meths.create_buf(true, true))
eq({id=4}, meths.create_buf(true, true))
@@ -1680,7 +1831,7 @@ describe('API', function()
eq(' 1 %a "[No Name]" line 1\n'..
' 3 h "[Scratch]" line 0\n'..
' 4 h "[Scratch]" line 0',
- meths.command_output("ls"))
+ meths.exec('ls', true))
-- current buffer didn't change
eq({id=1}, meths.get_current_buf())
@@ -1699,6 +1850,7 @@ describe('API', function()
eq('nofile', meths.buf_get_option(b, 'buftype'))
eq('hide', meths.buf_get_option(b, 'bufhidden'))
eq(false, meths.buf_get_option(b, 'swapfile'))
+ eq(false, meths.buf_get_option(b, 'modeline'))
end
--
@@ -1714,8 +1866,9 @@ describe('API', function()
eq('nofile', meths.buf_get_option(edited_buf, 'buftype'))
eq('hide', meths.buf_get_option(edited_buf, 'bufhidden'))
eq(false, meths.buf_get_option(edited_buf, 'swapfile'))
+ eq(false, meths.buf_get_option(edited_buf, 'modeline'))
- -- scratch buffer can be wiped without error
+ -- Scratch buffer can be wiped without error.
command('bwipe')
screen:expect([[
^ |
@@ -1730,4 +1883,27 @@ describe('API', function()
command('silent! call nvim_create_buf(0, 1)')
end)
end)
+
+ describe('nvim_get_runtime_file', function()
+ it('works', function()
+ eq({}, meths.get_runtime_file("bork.borkbork", false))
+ eq({}, meths.get_runtime_file("bork.borkbork", true))
+ eq(1, #meths.get_runtime_file("autoload/msgpack.vim", false))
+ eq(1, #meths.get_runtime_file("autoload/msgpack.vim", true))
+ local val = meths.get_runtime_file("autoload/remote/*.vim", true)
+ eq(2, #val)
+ local p = helpers.alter_slashes
+ if endswith(val[1], "define.vim") then
+ ok(endswith(val[1], p("autoload/remote/define.vim")))
+ ok(endswith(val[2], p("autoload/remote/host.vim")))
+ else
+ ok(endswith(val[1], p("autoload/remote/host.vim")))
+ ok(endswith(val[2], p("autoload/remote/define.vim")))
+ end
+ val = meths.get_runtime_file("autoload/remote/*.vim", false)
+ eq(1, #val)
+ ok(endswith(val[1], p("autoload/remote/define.vim"))
+ or endswith(val[1], p("autoload/remote/host.vim")))
+ end)
+ end)
end)
diff --git a/test/functional/api/window_spec.lua b/test/functional/api/window_spec.lua
index 17e0d3235c..8c7c3208c0 100644
--- a/test/functional/api/window_spec.lua
+++ b/test/functional/api/window_spec.lua
@@ -55,8 +55,8 @@ describe('API/win', function()
end)
it('validates args', function()
- eq('Invalid buffer id', pcall_err(window, 'set_buf', nvim('get_current_win'), 23))
- eq('Invalid window id', pcall_err(window, 'set_buf', 23, nvim('get_current_buf')))
+ eq('Invalid buffer id: 23', pcall_err(window, 'set_buf', nvim('get_current_win'), 23))
+ eq('Invalid window id: 23', pcall_err(window, 'set_buf', 23, nvim('get_current_buf')))
end)
end)
@@ -73,7 +73,7 @@ describe('API/win', function()
it('does not leak memory when using invalid window ID with invalid pos',
function()
- eq('Invalid window id', pcall_err(meths.win_set_cursor, 1, {"b\na"}))
+ eq('Invalid window id: 1', pcall_err(meths.win_set_cursor, 1, {"b\na"}))
end)
it('updates the screen, and also when the window is unfocused', function()
diff --git a/test/functional/autocmd/autocmd_spec.lua b/test/functional/autocmd/autocmd_spec.lua
index 805db9dd78..e62d3bb66b 100644
--- a/test/functional/autocmd/autocmd_spec.lua
+++ b/test/functional/autocmd/autocmd_spec.lua
@@ -1,6 +1,7 @@
local helpers = require('test.functional.helpers')(after_each)
local Screen = require('test.functional.ui.screen')
+local assert_visible = helpers.assert_visible
local dedent = helpers.dedent
local eq = helpers.eq
local eval = helpers.eval
@@ -18,26 +19,86 @@ local source = helpers.source
describe('autocmd', function()
before_each(clear)
- it(':tabnew triggers events in the correct order', function()
+ it(':tabnew, :split, :close events order, <afile>', function()
local expected = {
- 'WinLeave',
- 'TabLeave',
- 'WinEnter',
- 'TabNew',
- 'TabEnter',
- 'BufLeave',
- 'BufEnter'
+ {'WinLeave', ''},
+ {'TabLeave', ''},
+ {'WinEnter', ''},
+ {'TabNew', 'testfile1'}, -- :tabnew
+ {'TabEnter', ''},
+ {'BufLeave', ''},
+ {'BufEnter', 'testfile1'}, -- :split
+ {'WinLeave', 'testfile1'},
+ {'WinEnter', 'testfile1'},
+ {'WinLeave', 'testfile1'},
+ {'WinClosed', '1002'}, -- :close, WinClosed <afile> = window-id
+ {'WinEnter', 'testfile1'},
+ {'WinLeave', 'testfile1'}, -- :bdelete
+ {'WinEnter', 'testfile1'},
+ {'BufLeave', 'testfile1'},
+ {'BufEnter', 'testfile2'},
+ {'WinClosed', '1000'},
}
- command('let g:foo = []')
- command('autocmd BufEnter * :call add(g:foo, "BufEnter")')
- command('autocmd BufLeave * :call add(g:foo, "BufLeave")')
- command('autocmd TabEnter * :call add(g:foo, "TabEnter")')
- command('autocmd TabLeave * :call add(g:foo, "TabLeave")')
- command('autocmd TabNew * :call add(g:foo, "TabNew")')
- command('autocmd WinEnter * :call add(g:foo, "WinEnter")')
- command('autocmd WinLeave * :call add(g:foo, "WinLeave")')
- command('tabnew')
- assert.same(expected, eval('g:foo'))
+ command('let g:evs = []')
+ command('autocmd BufEnter * :call add(g:evs, ["BufEnter", expand("<afile>")])')
+ command('autocmd BufLeave * :call add(g:evs, ["BufLeave", expand("<afile>")])')
+ command('autocmd TabEnter * :call add(g:evs, ["TabEnter", expand("<afile>")])')
+ command('autocmd TabLeave * :call add(g:evs, ["TabLeave", expand("<afile>")])')
+ command('autocmd TabNew * :call add(g:evs, ["TabNew", expand("<afile>")])')
+ command('autocmd WinEnter * :call add(g:evs, ["WinEnter", expand("<afile>")])')
+ command('autocmd WinLeave * :call add(g:evs, ["WinLeave", expand("<afile>")])')
+ command('autocmd WinClosed * :call add(g:evs, ["WinClosed", expand("<afile>")])')
+ command('tabnew testfile1')
+ command('split')
+ command('close')
+ command('new testfile2')
+ command('bdelete 1')
+ eq(expected, eval('g:evs'))
+ end)
+
+ it('WinClosed is non-recursive', function()
+ command('let g:triggered = 0')
+ command('autocmd WinClosed * :let g:triggered+=1 | :bdelete 2')
+ command('new testfile2')
+ command('new testfile3')
+
+ -- All 3 buffers are visible.
+ assert_visible(1, true)
+ assert_visible(2, true)
+ assert_visible(3, true)
+
+ -- Trigger WinClosed, which also deletes buffer/window 2.
+ command('bdelete 1')
+
+ -- Buffers 1 and 2 were closed but WinClosed was triggered only once.
+ eq(1, eval('g:triggered'))
+ assert_visible(1, false)
+ assert_visible(2, false)
+ assert_visible(3, true)
+ end)
+
+ it('WinClosed from a different tabpage', function()
+ command('let g:evs = []')
+ command('edit tesfile1')
+ command('autocmd WinClosed <buffer> :call add(g:evs, ["WinClosed", expand("<abuf>")])')
+ local buf1 = eval("bufnr('%')")
+ command('new')
+ local buf2 = eval("bufnr('%')")
+ command('autocmd WinClosed <buffer> :call add(g:evs, ["WinClosed", expand("<abuf>")])'
+ -- Attempt recursion.
+ ..' | bdelete '..buf2)
+ command('tabedit testfile2')
+ command('tabedit testfile3')
+ command('bdelete '..buf2)
+ -- Non-recursive: only triggered once.
+ eq({
+ {'WinClosed', '2'},
+ }, eval('g:evs'))
+ command('bdelete '..buf1)
+ eq({
+ {'WinClosed', '2'},
+ {'WinClosed', '1'},
+ }, eval('g:evs'))
end)
it('v:vim_did_enter is 1 after VimEnter', function()
@@ -219,7 +280,7 @@ describe('autocmd', function()
eq(7, eval('g:test'))
-- API calls are blocked when aucmd_win is not in scope
- eq('Vim(call):E5555: API call: Invalid window id',
+ eq('Vim(call):E5555: API call: Invalid window id: 1001',
pcall_err(command, "call nvim_set_current_win(g:winid)"))
-- second time aucmd_win is needed, a different code path is invoked
@@ -257,7 +318,7 @@ describe('autocmd', function()
eq(0, eval('g:had_value'))
eq(7, eval('g:test'))
- eq('Vim(call):E5555: API call: Invalid window id',
+ eq('Vim(call):E5555: API call: Invalid window id: 1001',
pcall_err(command, "call nvim_set_current_win(g:winid)"))
end)
diff --git a/test/functional/autocmd/cmdline_spec.lua b/test/functional/autocmd/cmdline_spec.lua
index 51b7b819e9..8ec06dc148 100644
--- a/test/functional/autocmd/cmdline_spec.lua
+++ b/test/functional/autocmd/cmdline_spec.lua
@@ -33,7 +33,7 @@ describe('cmdline autocommands', function()
eq({'notification', 'CmdlineEnter', {{cmdtype=':', cmdlevel=1}}}, next_msg())
-- note: feed('bork<c-c>') might not consume 'bork'
- -- due to out-of-band interupt handling
+ -- due to out-of-band interrupt handling
feed('bork<esc>')
eq({'notification', 'CmdlineLeave',
{{cmdtype=':', cmdlevel=1, abort=true}}}, next_msg())
diff --git a/test/functional/autocmd/tabclose_spec.lua b/test/functional/autocmd/tabclose_spec.lua
index b7c33dc3d8..92d860c628 100644
--- a/test/functional/autocmd/tabclose_spec.lua
+++ b/test/functional/autocmd/tabclose_spec.lua
@@ -11,10 +11,10 @@ describe('TabClosed', function()
repeat
nvim('command', 'tabnew')
until nvim('eval', 'tabpagenr()') == 6 -- current tab is now 6
- eq("tabclosed:6:6:5", nvim('command_output', 'tabclose')) -- close last 6, current tab is now 5
- eq("tabclosed:5:5:4", nvim('command_output', 'close')) -- close last window on tab, closes tab
- eq("tabclosed:2:2:3", nvim('command_output', '2tabclose')) -- close tab 2, current tab is now 3
- eq("tabclosed:1:1:2\ntabclosed:1:1:1", nvim('command_output', 'tabonly')) -- close tabs 1 and 2
+ eq("tabclosed:6:6:5", nvim('exec', 'tabclose', true)) -- close last 6, current tab is now 5
+ eq("tabclosed:5:5:4", nvim('exec', 'close', true)) -- close last window on tab, closes tab
+ eq("tabclosed:2:2:3", nvim('exec', '2tabclose', true)) -- close tab 2, current tab is now 3
+ eq("tabclosed:1:1:2\ntabclosed:1:1:1", nvim('exec', 'tabonly', true)) -- close tabs 1 and 2
end)
it('is triggered when closing a window via bdelete from another tab', function()
@@ -23,7 +23,7 @@ describe('TabClosed', function()
nvim('command', '1tabedit Xtestfile')
nvim('command', 'normal! 1gt')
eq({1, 3}, nvim('eval', '[tabpagenr(), tabpagenr("$")]'))
- eq("tabclosed:2:2:1\ntabclosed:2:2:1", nvim('command_output', 'bdelete Xtestfile'))
+ eq("tabclosed:2:2:1\ntabclosed:2:2:1", nvim('exec', 'bdelete Xtestfile', true))
eq({1, 1}, nvim('eval', '[tabpagenr(), tabpagenr("$")]'))
end)
@@ -35,7 +35,7 @@ describe('TabClosed', function()
-- Only one tab is closed, and the alternate file is used for the other.
eq({2, 3}, nvim('eval', '[tabpagenr(), tabpagenr("$")]'))
- eq("tabclosed:2:2:2", nvim('command_output', 'bdelete Xtestfile2'))
+ eq("tabclosed:2:2:2", nvim('exec', 'bdelete Xtestfile2', true))
eq('Xtestfile1', nvim('eval', 'bufname("")'))
end)
end)
@@ -48,9 +48,9 @@ describe('TabClosed', function()
nvim('command', 'tabnew')
until nvim('eval', 'tabpagenr()') == 7 -- current tab is now 7
-- sanity check, we shouldn't match on tabs with numbers other than 2
- eq("tabclosed:7:7:6", nvim('command_output', 'tabclose'))
+ eq("tabclosed:7:7:6", nvim('exec', 'tabclose', true))
-- close tab page 2, current tab is now 5
- eq("tabclosed:2:2:5\ntabclosed:match", nvim('command_output', '2tabclose'))
+ eq("tabclosed:2:2:5\ntabclosed:match", nvim('exec', '2tabclose', true))
end)
end)
@@ -59,7 +59,7 @@ describe('TabClosed', function()
nvim('command', 'au! TabClosed * echom "tabclosed:".expand("<afile>").":".expand("<amatch>").":".tabpagenr()')
nvim('command', 'tabedit Xtestfile')
eq({2, 2}, nvim('eval', '[tabpagenr(), tabpagenr("$")]'))
- eq("tabclosed:2:2:1", nvim('command_output', 'close'))
+ eq("tabclosed:2:2:1", nvim('exec', 'close', true))
eq({1, 1}, nvim('eval', '[tabpagenr(), tabpagenr("$")]'))
end)
end)
diff --git a/test/functional/autocmd/tabnewentered_spec.lua b/test/functional/autocmd/tabnewentered_spec.lua
index 59cac07b34..dc2fd3e97d 100644
--- a/test/functional/autocmd/tabnewentered_spec.lua
+++ b/test/functional/autocmd/tabnewentered_spec.lua
@@ -1,5 +1,13 @@
local helpers = require('test.functional.helpers')(after_each)
-local clear, nvim, eq = helpers.clear, helpers.nvim, helpers.eq
+
+local clear = helpers.clear
+local command = helpers.command
+local dedent = helpers.dedent
+local eval = helpers.eval
+local eq = helpers.eq
+local feed = helpers.feed
+local nvim = helpers.nvim
+local redir_exec = helpers.redir_exec
describe('TabNewEntered', function()
describe('au TabNewEntered', function()
@@ -7,15 +15,15 @@ describe('TabNewEntered', function()
it('matches when entering any new tab', function()
clear()
nvim('command', 'au! TabNewEntered * echom "tabnewentered:".tabpagenr().":".bufnr("")')
- eq("tabnewentered:2:2", nvim('command_output', 'tabnew'))
- eq("tabnewentered:3:3", nvim('command_output', 'tabnew test.x2'))
+ eq("tabnewentered:2:2", nvim('exec', 'tabnew', true))
+ eq("tabnewentered:3:3", nvim('exec', 'tabnew test.x2', true))
end)
end)
describe('with FILE as <afile>', function()
it('matches when opening a new tab for FILE', function()
nvim('command', 'au! TabNewEntered Xtest-tabnewentered echom "tabnewentered:match"')
eq('tabnewentered:4:4\ntabnewentered:match',
- nvim('command_output', 'tabnew Xtest-tabnewentered'))
+ nvim('exec', 'tabnew Xtest-tabnewentered', true))
end)
end)
describe('with CTRL-W T', function()
@@ -24,8 +32,553 @@ describe('TabNewEntered', function()
nvim('command', 'au! TabNewEntered * echom "entered"')
nvim('command', 'tabnew test.x2')
nvim('command', 'split')
- eq('entered', nvim('command_output', 'execute "normal \\<C-W>T"'))
+ eq('entered', nvim('exec', 'execute "normal \\<C-W>T"', true))
end)
end)
end)
end)
+
+describe('TabEnter', function()
+ before_each(clear)
+ it('has correct previous tab when entering any new tab', function()
+ command('augroup TEMP')
+ nvim('command', 'au! TabEnter * echom "tabenter:".tabpagenr().":".tabpagenr(\'#\')')
+ command('augroup END')
+ eq("tabenter:2:1", nvim('exec', 'tabnew', true))
+ eq("tabenter:3:2", nvim('exec', 'tabnew test.x2', true))
+ command('augroup! TEMP')
+ end)
+ it('has correct previous tab when entering any preexisting tab', function()
+ command('tabnew')
+ command('tabnew')
+ command('augroup TEMP')
+ nvim('command', 'au! TabEnter * echom "tabenter:".tabpagenr().":".tabpagenr(\'#\')')
+ command('augroup END')
+ eq("tabenter:1:3", nvim('exec', 'tabnext', true))
+ eq("tabenter:2:1", nvim('exec', 'tabnext', true))
+ command('augroup! TEMP')
+ end)
+end)
+
+describe('tabpage/previous', function()
+ before_each(clear)
+ local function switches_to_previous_after_new_tab_creation_at_end(characters)
+ return function()
+ -- Add three tabs for a total of four
+ command('tabnew')
+ command('tabnew')
+ command('tabnew')
+
+ -- The previous tab is now the third.
+ eq(3, eval('tabpagenr(\'#\')'))
+
+ -- Switch to the previous (third) tab
+ feed(characters)
+
+ eq(dedent([=[
+
+
+ Tab page 1
+ [No Name]
+ Tab page 2
+ [No Name]
+ Tab page 3
+ > [No Name]
+ Tab page 4
+ # [No Name]]=]),
+ redir_exec('tabs')
+ )
+
+ -- The previous tab is now the fourth.
+ eq(4, eval('tabpagenr(\'#\')'))
+ end
+ end
+ it('switches to previous via g<Tab> after new tab creation at end',
+ switches_to_previous_after_new_tab_creation_at_end('g<Tab>'))
+ it('switches to previous via <C-W>g<Tab>. after new tab creation at end', switches_to_previous_after_new_tab_creation_at_end('<C-W>g<Tab>'))
+ it('switches to previous via <C-Tab>. after new tab creation at end', switches_to_previous_after_new_tab_creation_at_end('<C-Tab>'))
+ it('switches to previous via :tabn #<CR>. after new tab creation at end', switches_to_previous_after_new_tab_creation_at_end(':tabn #<CR>'))
+
+ local function switches_to_previous_after_new_tab_creation_in_middle(characters)
+ return function()
+ -- Add three tabs for a total of four
+ command('tabnew')
+ command('tabnew')
+ command('tabnew')
+ -- Switch to the second tab
+ command('tabnext 2')
+ -- Add a new tab after the second tab
+ command('tabnew')
+
+ -- The previous tab is now the second.
+ eq(2, eval('tabpagenr(\'#\')'))
+
+ -- Switch to the previous (second) tab
+ feed(characters)
+ eq(dedent([=[
+
+
+ Tab page 1
+ [No Name]
+ Tab page 2
+ > [No Name]
+ Tab page 3
+ # [No Name]
+ Tab page 4
+ [No Name]
+ Tab page 5
+ [No Name]]=]),
+ redir_exec('tabs')
+ )
+
+ -- The previous tab is now the third.
+ eq(3, eval('tabpagenr(\'#\')'))
+ end
+ end
+ it('switches to previous via g<Tab> after new tab creation in middle',
+ switches_to_previous_after_new_tab_creation_in_middle('g<Tab>'))
+ it('switches to previous via <C-W>g<Tab> after new tab creation in middle',
+ switches_to_previous_after_new_tab_creation_in_middle('<C-W>g<Tab>'))
+ it('switches to previous via <C-Tab> after new tab creation in middle',
+ switches_to_previous_after_new_tab_creation_in_middle('<C-Tab>'))
+ it('switches to previous via :tabn #<CR> after new tab creation in middle',
+ switches_to_previous_after_new_tab_creation_in_middle(':tabn #<CR>'))
+
+ local function switches_to_previous_after_switching_to_next_tab(characters)
+ return function()
+ -- Add three tabs for a total of four
+ command('tabnew')
+ command('tabnew')
+ command('tabnew')
+ -- Switch to the next (first) tab
+ command('tabnext')
+
+ -- The previous tab is now the fourth.
+ eq(4, eval('tabpagenr(\'#\')'))
+
+ -- Switch to the previous (fourth) tab
+ feed(characters)
+
+ eq(dedent([=[
+
+
+ Tab page 1
+ # [No Name]
+ Tab page 2
+ [No Name]
+ Tab page 3
+ [No Name]
+ Tab page 4
+ > [No Name]]=]),
+ redir_exec('tabs')
+ )
+
+ -- The previous tab is now the first.
+ eq(1, eval('tabpagenr(\'#\')'))
+ end
+ end
+ it('switches to previous via g<Tab> after switching to next tab',
+ switches_to_previous_after_switching_to_next_tab('g<Tab>'))
+ it('switches to previous via <C-W>g<Tab> after switching to next tab',
+ switches_to_previous_after_switching_to_next_tab('<C-W>g<Tab>'))
+ it('switches to previous via <C-Tab> after switching to next tab',
+ switches_to_previous_after_switching_to_next_tab('<C-Tab>'))
+ it('switches to previous via :tabn #<CR> after switching to next tab',
+ switches_to_previous_after_switching_to_next_tab(':tabn #<CR>'))
+
+ local function switches_to_previous_after_switching_to_last_tab(characters)
+ return function()
+ -- Add three tabs for a total of four
+ command('tabnew')
+ command('tabnew')
+ command('tabnew')
+ -- Switch to the next (first) tab
+ command('tabnext')
+ -- Switch to the last (fourth) tab.
+ command('tablast')
+
+ -- The previous tab is now the second.
+ eq(1, eval('tabpagenr(\'#\')'))
+
+ -- Switch to the previous (second) tab
+ feed(characters)
+
+ eq(dedent([=[
+
+
+ Tab page 1
+ > [No Name]
+ Tab page 2
+ [No Name]
+ Tab page 3
+ [No Name]
+ Tab page 4
+ # [No Name]]=]),
+ redir_exec('tabs')
+ )
+
+ -- The previous tab is now the fourth.
+ eq(4, eval('tabpagenr(\'#\')'))
+ end
+ end
+ it('switches to previous after switching to last tab',
+ switches_to_previous_after_switching_to_last_tab('g<Tab>'))
+ it('switches to previous after switching to last tab',
+ switches_to_previous_after_switching_to_last_tab('<C-W>g<Tab>'))
+ it('switches to previous after switching to last tab',
+ switches_to_previous_after_switching_to_last_tab('<C-Tab>'))
+ it('switches to previous after switching to last tab',
+ switches_to_previous_after_switching_to_last_tab(':tabn #<CR>'))
+
+ local function switches_to_previous_after_switching_to_previous_tab(characters)
+ return function()
+ -- Add three tabs for a total of four
+ command('tabnew')
+ command('tabnew')
+ command('tabnew')
+ -- Switch to the previous (third) tab
+ command('tabprevious')
+
+ -- The previous tab is now the fourth.
+ eq(4, eval('tabpagenr(\'#\')'))
+
+ -- Switch to the previous (fourth) tab
+ feed(characters)
+
+ eq(dedent([=[
+
+
+ Tab page 1
+ [No Name]
+ Tab page 2
+ [No Name]
+ Tab page 3
+ # [No Name]
+ Tab page 4
+ > [No Name]]=]),
+ redir_exec('tabs')
+ )
+
+ -- The previous tab is now the third.
+ eq(3, eval('tabpagenr(\'#\')'))
+ end
+ end
+ it('switches to previous via g<Tab> after switching to previous tab',
+ switches_to_previous_after_switching_to_previous_tab('g<Tab>'))
+ it('switches to previous via <C-W>g<Tab> after switching to previous tab',
+ switches_to_previous_after_switching_to_previous_tab('<C-W>g<Tab>'))
+ it('switches to previous via <C-Tab> after switching to previous tab',
+ switches_to_previous_after_switching_to_previous_tab('<C-Tab>'))
+ it('switches to previous via :tabn #<CR> after switching to previous tab',
+ switches_to_previous_after_switching_to_previous_tab(':tabn #<CR>'))
+
+ local function switches_to_previous_after_switching_to_first_tab(characters)
+ return function()
+ -- Add three tabs for a total of four
+ command('tabnew')
+ command('tabnew')
+ command('tabnew')
+ -- Switch to the previous (third) tab
+ command('tabprevious')
+ -- Switch to the first tab
+ command('tabfirst')
+
+ -- The previous tab is now the third.
+ eq(3, eval('tabpagenr(\'#\')'))
+
+ -- Switch to the previous (third) tab
+ feed(characters)
+
+ eq(dedent([=[
+
+
+ Tab page 1
+ # [No Name]
+ Tab page 2
+ [No Name]
+ Tab page 3
+ > [No Name]
+ Tab page 4
+ [No Name]]=]),
+ redir_exec('tabs')
+ )
+
+ -- The previous tab is now the first.
+ eq(1, eval('tabpagenr(\'#\')'))
+ end
+ end
+ it('switches to previous via g<Tab> after switching to first tab',
+ switches_to_previous_after_switching_to_first_tab('g<Tab>'))
+ it('switches to previous via <C-W>g<Tab> after switching to first tab',
+ switches_to_previous_after_switching_to_first_tab('<C-W>g<Tab>'))
+ it('switches to previous via <C-Tab> after switching to first tab',
+ switches_to_previous_after_switching_to_first_tab('<C-Tab>'))
+ it('switches to previous via :tabn #<CR> after switching to first tab',
+ switches_to_previous_after_switching_to_first_tab(':tabn #<CR>'))
+
+ local function switches_to_previous_after_numbered_tab_switch(characters)
+ return function()
+ -- Add three tabs for a total of four
+ command('tabnew')
+ command('tabnew')
+ command('tabnew')
+ -- Switch to the second tab
+ command('tabnext 2')
+
+ -- The previous tab is now the fourth.
+ eq(4, eval('tabpagenr(\'#\')'))
+
+ -- Switch to the previous (fourth) tab
+ feed(characters)
+
+ eq(dedent([=[
+
+
+ Tab page 1
+ [No Name]
+ Tab page 2
+ # [No Name]
+ Tab page 3
+ [No Name]
+ Tab page 4
+ > [No Name]]=]),
+ redir_exec('tabs')
+ )
+
+ -- The previous tab is now the second.
+ eq(2, eval('tabpagenr(\'#\')'))
+ end
+ end
+ it('switches to previous via g<Tab> after numbered tab switch',
+ switches_to_previous_after_numbered_tab_switch('g<Tab>'))
+ it('switches to previous via <C-W>g<Tab> after numbered tab switch',
+ switches_to_previous_after_numbered_tab_switch('<C-W>g<Tab>'))
+ it('switches to previous via <C-Tab> after numbered tab switch',
+ switches_to_previous_after_numbered_tab_switch('<C-Tab>'))
+ it('switches to previous via :tabn #<CR> after numbered tab switch',
+ switches_to_previous_after_numbered_tab_switch(':tabn #<CR>'))
+
+ local function switches_to_previous_after_switching_to_previous(characters1, characters2)
+ return function()
+ -- Add three tabs for a total of four
+ command('tabnew')
+ command('tabnew')
+ command('tabnew')
+ -- Switch to the second tab
+ command('tabnext 2')
+ -- Switch to the previous (fourth) tab
+ feed(characters1)
+
+ -- The previous tab is now the second.
+ eq(2, eval('tabpagenr(\'#\')'))
+
+ -- Switch to the previous (second) tab
+ feed(characters2)
+
+ eq(dedent([=[
+
+
+ Tab page 1
+ [No Name]
+ Tab page 2
+ > [No Name]
+ Tab page 3
+ [No Name]
+ Tab page 4
+ # [No Name]]=]),
+ redir_exec('tabs')
+ )
+
+ -- The previous tab is now the fourth.
+ eq(4, eval('tabpagenr(\'#\')'))
+ end
+ end
+ it('switches to previous via g<Tab> after switching to previous via g<Tab>',
+ switches_to_previous_after_switching_to_previous('g<Tab>', 'g<Tab>'))
+ it('switches to previous via <C-W>g<Tab> after switching to previous via g<Tab>',
+ switches_to_previous_after_switching_to_previous('g<Tab>', '<C-W>g<Tab>'))
+ it('switches to previous via <C-Tab> after switching to previous via g<Tab>',
+ switches_to_previous_after_switching_to_previous('g<Tab>', '<C-Tab>'))
+ it('switches to previous via :tabn #<CR> after switching to previous via g<Tab>',
+ switches_to_previous_after_switching_to_previous('g<Tab>', ':tabn #<CR>'))
+ it('switches to previous via g<Tab> after switching to previous via <C-W>g<Tab>',
+ switches_to_previous_after_switching_to_previous('<C-W>g<Tab>', 'g<Tab>'))
+ it('switches to previous via <C-W>g<Tab> after switching to previous via <C-W>g<Tab>',
+ switches_to_previous_after_switching_to_previous('<C-W>g<Tab>', '<C-W>g<Tab>'))
+ it('switches to previous via <C-Tab> after switching to previous via <C-W>g<Tab>',
+ switches_to_previous_after_switching_to_previous('<C-W>g<Tab>', '<C-Tab>'))
+ it('switches to previous via :tabn #<CR> after switching to previous via <C-W>g<Tab>',
+ switches_to_previous_after_switching_to_previous('<C-W>g<Tab>', ':tabn #<CR>'))
+ it('switches to previous via g<Tab> after switching to previous via <C-Tab>',
+ switches_to_previous_after_switching_to_previous('<C-Tab>', 'g<Tab>'))
+ it('switches to previous via <C-W>g<Tab> after switching to previous via <C-Tab>',
+ switches_to_previous_after_switching_to_previous('<C-Tab>', '<C-W>g<Tab>'))
+ it('switches to previous via <C-Tab> after switching to previous via <C-Tab>',
+ switches_to_previous_after_switching_to_previous('<C-Tab>', '<C-Tab>'))
+ it('switches to previous via :tabn #<CR> after switching to previous via <C-Tab>',
+ switches_to_previous_after_switching_to_previous('<C-Tab>', ':tabn #<CR>'))
+ it('switches to previous via g<Tab> after switching to previous via :tabn #<CR>',
+ switches_to_previous_after_switching_to_previous(':tabn #<CR>', 'g<Tab>'))
+ it('switches to previous via <C-W>g<Tab> after switching to previous via :tabn #<CR>',
+ switches_to_previous_after_switching_to_previous(':tabn #<CR>', '<C-W>g<Tab>'))
+ it('switches to previous via <C-Tab> after switching to previous via <C-Tab>',
+ switches_to_previous_after_switching_to_previous(':tabn #<CR>', '<C-Tab>'))
+ it('switches to previous via :tabn #<CR> after switching to previous via :tabn #<CR>',
+ switches_to_previous_after_switching_to_previous(':tabn #<CR>', ':tabn #<CR>'))
+
+ local function does_not_switch_to_previous_after_closing_current_tab(characters)
+ return function()
+ -- Add three tabs for a total of four
+ command('tabnew')
+ command('tabnew')
+ command('tabnew')
+ -- Close the current (fourth tab)
+ command('wincmd c')
+
+ -- The previous tab is now the "zeroth" -- there isn't one.
+ eq(0, eval('tabpagenr(\'#\')'))
+
+ -- At this point, switching to the "previous" (i.e. fourth) tab would mean
+ -- switching to either a dangling or a null pointer.
+ feed(characters)
+
+ eq(dedent([=[
+
+
+ Tab page 1
+ [No Name]
+ Tab page 2
+ [No Name]
+ Tab page 3
+ > [No Name]]=]),
+ redir_exec('tabs')
+ )
+
+ -- The previous tab is now the "zero".
+ eq(0, eval('tabpagenr(\'#\')'))
+ end
+ end
+ it('does not switch to previous via g<Tab> after closing current tab',
+ does_not_switch_to_previous_after_closing_current_tab('g<Tab>'))
+ it('does not switch to previous via <C-W>g<Tab> after closing current tab',
+ does_not_switch_to_previous_after_closing_current_tab('<C-W>g<Tab>'))
+ it('does not switch to previous via <C-Tab> after closing current tab',
+ does_not_switch_to_previous_after_closing_current_tab('<C-Tab>'))
+ it('does not switch to previous via :tabn #<CR> after closing current tab',
+ does_not_switch_to_previous_after_closing_current_tab(':tabn #<CR>'))
+
+ local function does_not_switch_to_previous_after_entering_operator_pending(characters)
+ return function()
+ -- Add three tabs for a total of four
+ command('tabnew')
+ command('tabnew')
+ command('tabnew')
+
+ -- The previous tab is now the third.
+ eq(3, eval('tabpagenr(\'#\')'))
+
+ -- Enter operator pending mode.
+ feed('d')
+ eq('no', eval('mode(1)'))
+
+ -- At this point switching to the previous tab should have no effect
+ -- other than leaving operator pending mode.
+ feed(characters)
+
+ -- Attempting to switch tabs returns us to normal mode.
+ eq('n', eval('mode()'))
+
+ -- The current tab is still the fourth.
+ eq(4, eval('tabpagenr()'))
+
+ -- The previous tab is still the third.
+ eq(3, eval('tabpagenr(\'#\')'))
+ end
+ end
+ it('does not switch to previous via g<Tab> after entering operator pending',
+ does_not_switch_to_previous_after_entering_operator_pending('g<Tab>'))
+ -- NOTE: When in operator pending mode, attempting to switch to previous has
+ -- the following effect:
+ -- - Ctrl-W exits operator pending mode
+ -- - g<Tab> switches to the previous tab
+ -- In other words, the effect of "<C-W>g<Tab>" is to switch to the
+ -- previous tab even from operator pending mode, but only thanks to the
+ -- fact that the suffix after "<C-W>" in "<C-W>g<Tab>" just happens to
+ -- be the same as the normal mode command to switch to the previous tab.
+ -- it('does not switch to previous via <C-W>g<Tab> after entering operator pending',
+ -- does_not_switch_to_previous_after_entering_operator_pending('<C-W>g<Tab>'))
+ it('does not switch to previous via <C-Tab> after entering operator pending',
+ does_not_switch_to_previous_after_entering_operator_pending('<C-Tab>'))
+ -- NOTE: When in operator pending mode, pressing : leaves operator pending
+ -- mode and enters command mode, so :tabn #<CR> does in fact switch
+ -- tabs.
+ -- it('does not switch to previous via :tabn #<CR> after entering operator pending',
+ -- does_not_switch_to_previous_after_entering_operator_pending(':tabn #<CR>'))
+
+ local function cmdline_win_prevents_tab_switch(characters, completion_visible)
+ return function()
+ -- Add three tabs for a total of four
+ command('tabnew')
+ command('tabnew')
+ command('tabnew')
+
+ -- The previous tab is now the third.
+ eq(3, eval('tabpagenr(\'#\')'))
+
+ -- Edit : command line in command-line window
+ feed('q:')
+
+ local cmdline_win_id = eval('win_getid()')
+
+ -- At this point switching to the previous tab should have no effect.
+ feed(characters)
+
+ -- Attempting to switch tabs maintains the current window.
+ eq(cmdline_win_id, eval('win_getid()'))
+ eq(completion_visible, eval('complete_info().pum_visible'))
+
+ -- The current tab is still the fourth.
+ eq(4, eval('tabpagenr()'))
+
+ -- The previous tab is still the third.
+ eq(3, eval('tabpagenr(\'#\')'))
+ end
+ end
+ it('cmdline-win prevents tab switch via g<Tab>',
+ cmdline_win_prevents_tab_switch('g<Tab>', 0))
+ it('cmdline-win prevents tab switch via <C-W>g<Tab>',
+ cmdline_win_prevents_tab_switch('<C-W>g<Tab>', 1))
+ it('cmdline-win prevents tab switch via <C-Tab>',
+ cmdline_win_prevents_tab_switch('<C-Tab>', 0))
+ it('cmdline-win prevents tab switch via :tabn #<CR>',
+ cmdline_win_prevents_tab_switch(':tabn #<CR>', 0))
+
+ it(':tabs indicates correct prevtab curwin', function()
+ -- Add three tabs for a total of four
+ command('tabnew')
+ command('tabnew')
+ command('split')
+ command('vsplit')
+ feed('<C-w>p')
+ command('tabnew')
+
+ -- The previous tab is now the three.
+ eq(3, eval('tabpagenr(\'#\')'))
+
+ eq(dedent([=[
+
+
+ Tab page 1
+ [No Name]
+ Tab page 2
+ [No Name]
+ Tab page 3
+ [No Name]
+ # [No Name]
+ [No Name]
+ Tab page 4
+ > [No Name]]=]),
+ redir_exec('tabs')
+ )
+ end)
+end)
diff --git a/test/functional/autocmd/textyankpost_spec.lua b/test/functional/autocmd/textyankpost_spec.lua
index 8c23b72cff..3898d59e58 100644
--- a/test/functional/autocmd/textyankpost_spec.lua
+++ b/test/functional/autocmd/textyankpost_spec.lua
@@ -27,7 +27,8 @@ describe('TextYankPost', function()
operator = 'y',
regcontents = { 'foo\nbar' },
regname = '',
- regtype = 'V'
+ regtype = 'V',
+ visual = false
}, eval('g:event'))
eq(1, eval('g:count'))
@@ -40,7 +41,8 @@ describe('TextYankPost', function()
operator = 'y',
regcontents = { 'baz ' },
regname = '',
- regtype = 'v'
+ regtype = 'v',
+ visual = false
}, eval('g:event'))
eq(2, eval('g:count'))
@@ -50,7 +52,8 @@ describe('TextYankPost', function()
operator = 'y',
regcontents = { 'foo', 'baz' },
regname = '',
- regtype = "\0223" -- ^V + block width
+ regtype = "\0223", -- ^V + block width
+ visual = true
}, eval('g:event'))
eq(3, eval('g:count'))
end)
@@ -62,7 +65,8 @@ describe('TextYankPost', function()
operator = 'y',
regcontents = { 'foo\nbar' },
regname = '',
- regtype = 'V'
+ regtype = 'V',
+ visual = false
}, eval('g:event'))
command('set debug=msg')
@@ -92,7 +96,8 @@ describe('TextYankPost', function()
operator = 'y',
regcontents = { 'foo\nbar' },
regname = '',
- regtype = 'V'
+ regtype = 'V',
+ visual = false
}, eval('g:event'))
eq(1, eval('g:count'))
eq({ 'foo\nbar' }, funcs.getreg('+',1,1))
@@ -105,7 +110,8 @@ describe('TextYankPost', function()
operator = 'd',
regcontents = { 'foo' },
regname = '',
- regtype = 'v'
+ regtype = 'v',
+ visual = false
}, eval('g:event'))
eq(1, eval('g:count'))
@@ -115,7 +121,8 @@ describe('TextYankPost', function()
operator = 'd',
regcontents = { '\nbar' },
regname = '',
- regtype = 'V'
+ regtype = 'V',
+ visual = false
}, eval('g:event'))
eq(2, eval('g:count'))
@@ -125,7 +132,8 @@ describe('TextYankPost', function()
operator = 'c',
regcontents = { 'baz' },
regname = '',
- regtype = 'v'
+ regtype = 'v',
+ visual = false
}, eval('g:event'))
eq(3, eval('g:count'))
end)
@@ -153,7 +161,8 @@ describe('TextYankPost', function()
operator = 'y',
regcontents = { 'bar' },
regname = 'b',
- regtype = 'v'
+ regtype = 'v',
+ visual = false
}, eval('g:event'))
feed('"*yy')
@@ -162,7 +171,8 @@ describe('TextYankPost', function()
operator = 'y',
regcontents = { 'foo\nbar' },
regname = '*',
- regtype = 'V'
+ regtype = 'V',
+ visual = false
}, eval('g:event'))
command("set clipboard=unnamed")
@@ -174,7 +184,8 @@ describe('TextYankPost', function()
operator = 'y',
regcontents = { 'foo\nbar' },
regname = '',
- regtype = 'V'
+ regtype = 'V',
+ visual = false
}, eval('g:event'))
feed('"*yy')
@@ -183,7 +194,8 @@ describe('TextYankPost', function()
operator = 'y',
regcontents = { 'foo\nbar' },
regname = '*',
- regtype = 'V'
+ regtype = 'V',
+ visual = false
}, eval('g:event'))
end)
@@ -194,7 +206,8 @@ describe('TextYankPost', function()
operator = 'd',
regcontents = { 'foo\nbar' },
regname = '+',
- regtype = 'V'
+ regtype = 'V',
+ visual = false
}, eval('g:event'))
eq(1, eval('g:count'))
@@ -204,7 +217,8 @@ describe('TextYankPost', function()
operator = 'y',
regcontents = { 'baz text' },
regname = '',
- regtype = 'V'
+ regtype = 'V',
+ visual = false
}, eval('g:event'))
eq(2, eval('g:count'))
@@ -214,7 +228,8 @@ describe('TextYankPost', function()
operator = 'y',
regcontents = { 'baz ' },
regname = '',
- regtype = 'v'
+ regtype = 'v',
+ visual = false
}, eval('g:event'))
eq(3, eval('g:count'))
@@ -224,7 +239,8 @@ describe('TextYankPost', function()
operator = 'd',
regcontents = { 'baz text' },
regname = '',
- regtype = 'V'
+ regtype = 'V',
+ visual = false
}, eval('g:event'))
eq(4, eval('g:count'))
end)
diff --git a/test/functional/autoread/focus_spec.lua b/test/functional/autoread/focus_spec.lua
new file mode 100644
index 0000000000..1d52e9948f
--- /dev/null
+++ b/test/functional/autoread/focus_spec.lua
@@ -0,0 +1,58 @@
+local helpers = require('test.functional.helpers')(after_each)
+local thelpers = require('test.functional.terminal.helpers')
+local lfs = require('lfs')
+local clear = helpers.clear
+local nvim_prog = helpers.nvim_prog
+local feed_command = helpers.feed_command
+local feed_data = thelpers.feed_data
+
+if helpers.pending_win32(pending) then return end
+
+describe('autoread TUI FocusGained/FocusLost', function()
+ local screen
+
+ before_each(function()
+ clear()
+ screen = thelpers.screen_setup(0, '["'..nvim_prog
+ ..'", "-u", "NONE", "-i", "NONE", "--cmd", "set noswapfile noshowcmd noruler"]')
+ end)
+
+ it('external file change', function()
+ local path = 'xtest-foo'
+ local expected_addition = [[
+ line 1
+ line 2
+ line 3
+ line 4
+ ]]
+
+ helpers.write_file(path, '')
+ lfs.touch(path, os.time() - 10)
+ feed_command('edit '..path)
+ feed_data('\027[O')
+
+ screen:expect{grid=[[
+ {1: } |
+ {4:~ }|
+ {4:~ }|
+ {4:~ }|
+ {5:xtest-foo }|
+ :edit xtest-foo |
+ {3:-- TERMINAL --} |
+ ]]}
+
+ helpers.write_file(path, expected_addition)
+
+ feed_data('\027[I')
+
+ screen:expect{grid=[[
+ {1:l}ine 1 |
+ line 2 |
+ line 3 |
+ line 4 |
+ {5:xtest-foo }|
+ "xtest-foo" 4L, 28C |
+ {3:-- TERMINAL --} |
+ ]]}
+ end)
+end)
diff --git a/test/functional/cmdline/ctrl_r_spec.lua b/test/functional/cmdline/ctrl_r_spec.lua
index d2dad23e98..a0f3955282 100644
--- a/test/functional/cmdline/ctrl_r_spec.lua
+++ b/test/functional/cmdline/ctrl_r_spec.lua
@@ -15,7 +15,7 @@ describe('cmdline CTRL-R', function()
-- <CR> inserted between lines, NOT after the final line.
eq('line1abc\rline2somemoretext', funcs.getcmdline())
- -- Yank 2 lines characterwise, then paste to cmdline.
+ -- Yank 2 lines charwise, then paste to cmdline.
feed([[<C-\><C-N>gg05lyvj:<C-R>0]])
-- <CR> inserted between lines, NOT after the final line.
eq('abc\rline2', funcs.getcmdline())
diff --git a/test/functional/cmdline/history_spec.lua b/test/functional/cmdline/history_spec.lua
index 20f9cf06a2..ee2d36f642 100644
--- a/test/functional/cmdline/history_spec.lua
+++ b/test/functional/cmdline/history_spec.lua
@@ -6,7 +6,7 @@ describe('history support code', function()
before_each(clear)
it('correctly clears start of the history', function()
- -- Regression test: check absense of the memory leak when clearing start of
+ -- Regression test: check absence of the memory leak when clearing start of
-- the history using ex_getln.c/clr_history().
eq(1, funcs.histadd(':', 'foo'))
eq(1, funcs.histdel(':'))
@@ -14,7 +14,7 @@ describe('history support code', function()
end)
it('correctly clears end of the history', function()
- -- Regression test: check absense of the memory leak when clearing end of
+ -- Regression test: check absence of the memory leak when clearing end of
-- the history using ex_getln.c/clr_history().
meths.set_option('history', 1)
eq(1, funcs.histadd(':', 'foo'))
diff --git a/test/functional/core/fileio_spec.lua b/test/functional/core/fileio_spec.lua
index e6bce85b8a..f4c476560d 100644
--- a/test/functional/core/fileio_spec.lua
+++ b/test/functional/core/fileio_spec.lua
@@ -9,9 +9,12 @@ local nvim_prog = helpers.nvim_prog
local request = helpers.request
local retry = helpers.retry
local rmdir = helpers.rmdir
+local mkdir = helpers.mkdir
local sleep = helpers.sleep
local read_file = helpers.read_file
local trim = helpers.trim
+local currentdir = helpers.funcs.getcwd
+local iswin = helpers.iswin
describe('fileio', function()
before_each(function()
@@ -24,6 +27,7 @@ describe('fileio', function()
os.remove('Xtest_startup_file2')
os.remove('Xtest_тест.md')
rmdir('Xtest_startup_swapdir')
+ rmdir('Xtest_backupdir')
end)
it('fsync() codepaths #8304', function()
@@ -88,6 +92,27 @@ describe('fileio', function()
eq('foo', bar_contents);
end)
+ it('backup with full path #11214', function()
+ clear()
+ mkdir('Xtest_backupdir')
+ command('set backup')
+ command('set backupdir=Xtest_backupdir//')
+ command('write Xtest_startup_file1')
+ feed('ifoo<esc>')
+ command('write')
+ feed('Abar<esc>')
+ command('write')
+
+ -- Backup filename = fullpath, separators replaced with "%".
+ local backup_file_name = string.gsub(currentdir()..'/Xtest_startup_file1',
+ iswin() and '[:/\\]' or '/', '%%') .. '~'
+ local foo_contents = trim(read_file('Xtest_backupdir/'..backup_file_name))
+ local foobar_contents = trim(read_file('Xtest_startup_file1'))
+
+ eq('foobar', foobar_contents);
+ eq('foo', foo_contents);
+ end)
+
it('readfile() on multibyte filename #10586', function()
clear()
local text = {
diff --git a/test/functional/core/job_spec.lua b/test/functional/core/job_spec.lua
index 9c37e55f42..57e6f4fd63 100644
--- a/test/functional/core/job_spec.lua
+++ b/test/functional/core/job_spec.lua
@@ -26,6 +26,7 @@ describe('jobs', function()
before_each(function()
clear()
+
channel = nvim('get_api_info')[1]
nvim('set_var', 'channel', channel)
source([[
@@ -48,6 +49,57 @@ describe('jobs', function()
]])
end)
+ it('must specify env option as a dict', function()
+ command("let g:job_opts.env = v:true")
+ local _, err = pcall(function()
+ if iswin() then
+ nvim('command', "let j = jobstart('set', g:job_opts)")
+ else
+ nvim('command', "let j = jobstart('env', g:job_opts)")
+ end
+ end)
+ ok(string.find(err, "E475: Invalid argument: env") ~= nil)
+ end)
+
+ it('append environment #env', function()
+ nvim('command', "let $VAR = 'abc'")
+ nvim('command', "let g:job_opts.env = {'TOTO': 'hello world'}")
+ if iswin() then
+ nvim('command', [[call jobstart('echo %TOTO% %VAR%', g:job_opts)]])
+ else
+ nvim('command', [[call jobstart('echo $TOTO $VAR', g:job_opts)]])
+ end
+
+ expect_msg_seq({
+ {'notification', 'stdout', {0, {'hello world abc', ''}}},
+ })
+ end)
+
+ it('replace environment #env', function()
+ nvim('command', "let $VAR = 'abc'")
+ nvim('command', "let g:job_opts.env = {'TOTO': 'hello world'}")
+ nvim('command', "let g:job_opts.clear_env = 1")
+
+ -- libuv ensures that certain "required" environment variables are
+ -- preserved if the user doesn't provide them in a custom environment
+ -- https://github.com/libuv/libuv/blob/635e0ce6073c5fbc96040e336b364c061441b54b/src/win/process.c#L672
+ -- https://github.com/libuv/libuv/blob/635e0ce6073c5fbc96040e336b364c061441b54b/src/win/process.c#L48-L60
+ --
+ -- Rather than expecting a completely empty environment, ensure that $VAR
+ -- is *not* in the environment but $TOTO is.
+ if iswin() then
+ nvim('command', [[call jobstart('echo %TOTO% %VAR%', g:job_opts)]])
+ expect_msg_seq({
+ {'notification', 'stdout', {0, {'hello world %VAR%', ''}}}
+ })
+ else
+ nvim('command', [[call jobstart('echo $TOTO $VAR', g:job_opts)]])
+ expect_msg_seq({
+ {'notification', 'stdout', {0, {'hello world', ''}}}
+ })
+ end
+ end)
+
it('uses &shell and &shellcmdflag if passed a string', function()
nvim('command', "let $VAR = 'abc'")
if iswin() then
@@ -133,11 +185,10 @@ describe('jobs', function()
return eval([[jobstart('')]])
end
local executable_jobid = new_job()
- local nonexecutable_jobid = eval("jobstart(['"..(iswin()
- and './test/functional/fixtures'
- or './test/functional/fixtures/non_executable.txt').."'])")
- eq(-1, nonexecutable_jobid)
- -- Should _not_ throw an error.
+
+ local exe = iswin() and './test/functional/fixtures' or './test/functional/fixtures/non_executable.txt'
+ eq("Vim:E475: Invalid value for argument cmd: '"..exe.."' is not executable",
+ pcall_err(eval, "jobstart(['"..exe.."'])"))
eq("", eval("v:errmsg"))
-- Non-executable job should not increment the job ids. #5465
eq(executable_jobid + 1, new_job())
@@ -202,8 +253,7 @@ describe('jobs', function()
if helpers.isCI('travis') and os.getenv('CC') == 'gcc-4.9'
and helpers.is_os('mac') then
-- XXX: Hangs Travis macOS since e9061117a5b8f195c3f26a5cb94e18ddd7752d86.
- pending("[Hangs on Travis macOS. #5002]", function() end)
- return
+ pending("[Hangs on Travis macOS. #5002]")
end
nvim('command', "let j = jobstart(['cat', '-'], g:job_opts)")
@@ -256,16 +306,16 @@ describe('jobs', function()
end))
end)
- it('disallows jobsend/stop on a non-existent job', function()
+ it('disallows jobsend on a non-existent job', function()
eq(false, pcall(eval, "jobsend(-1, 'lol')"))
- eq(false, pcall(eval, "jobstop(-1)"))
+ eq(0, eval('jobstop(-1)'))
end)
- it('disallows jobstop twice on the same job', function()
+ it('jobstop twice on the stopped or exited job return 0', function()
nvim('command', "let j = jobstart(['cat', '-'], g:job_opts)")
neq(0, eval('j'))
- eq(true, pcall(eval, "jobstop(j)"))
- eq(false, pcall(eval, "jobstop(j)"))
+ eq(1, eval("jobstop(j)"))
+ eq(0, eval("jobstop(j)"))
end)
it('will not leak memory if we leave a job running', function()
@@ -869,6 +919,13 @@ describe('jobs', function()
end)
end)
+ it('jobstop on same id before stopped', function()
+ nvim('command', 'let j = jobstart(["cat", "-"], g:job_opts)')
+ neq(0, eval('j'))
+
+ eq({1, 0}, eval('[jobstop(j), jobstop(j)]'))
+ end)
+
describe('running tty-test program', function()
if helpers.pending_win32(pending) then return end
local function next_chunk()
@@ -963,9 +1020,6 @@ describe("pty process teardown", function()
|
]])
end)
- after_each(function()
- screen:detach()
- end)
it("does not prevent/delay exit. #4798 #4900", function()
if helpers.pending_win32(pending) then return end
diff --git a/test/functional/core/main_spec.lua b/test/functional/core/main_spec.lua
index b793e531c9..37a9f0b836 100644
--- a/test/functional/core/main_spec.lua
+++ b/test/functional/core/main_spec.lua
@@ -67,7 +67,7 @@ describe('Command-line option', function()
|
|
]], {
- [1] = {foreground = 4210943},
+ [1] = {foreground = tonumber('0x4040ff'), fg_indexed=true},
[2] = {bold = true, reverse = true}
})
feed('i:cq<CR>')
diff --git a/test/functional/core/startup_spec.lua b/test/functional/core/startup_spec.lua
index cc10d36a10..9b0668f9e6 100644
--- a/test/functional/core/startup_spec.lua
+++ b/test/functional/core/startup_spec.lua
@@ -3,6 +3,7 @@ local Screen = require('test.functional.ui.screen')
local clear = helpers.clear
local command = helpers.command
+local ok = helpers.ok
local eq = helpers.eq
local matches = helpers.matches
local eval = helpers.eval
@@ -17,6 +18,7 @@ local rmdir = helpers.rmdir
local sleep = helpers.sleep
local iswin = helpers.iswin
local write_file = helpers.write_file
+local meths = helpers.meths
describe('startup', function()
before_each(function()
@@ -277,6 +279,32 @@ describe('startup', function()
[4] = {bold = true, foreground = Screen.colors.Blue1},
}})
end)
+
+ it('fixed hang issue with --headless (#11386)', function()
+ local expected = ''
+ local period = 100
+ for i = 1, period - 1 do
+ expected = expected .. i .. '\r\n'
+ end
+ expected = expected .. period
+ eq(
+ expected,
+ -- FIXME(codehex): We should really set a timeout for the system function.
+ -- If this test fails, there will be a waiting input state.
+ funcs.system({nvim_prog, '-u', 'NONE', '-c',
+ 'for i in range(1, 100) | echo i | endfor | quit',
+ '--headless'
+ })
+ )
+ end)
+
+ it("get command line arguments from v:argv", function()
+ local out = funcs.system({ nvim_prog, '-u', 'NONE', '-i', 'NONE', '--headless',
+ '--cmd', nvim_set,
+ '-c', [[echo v:argv[-1:] len(v:argv) > 1]],
+ '+q' })
+ eq('[\'+q\'] 1', out)
+ end)
end)
describe('sysinit', function()
@@ -330,4 +358,36 @@ describe('sysinit', function()
eq('loaded 1 xdg 0 vim 1',
eval('printf("loaded %d xdg %d vim %d", g:loaded, get(g:, "xdg", 0), get(g:, "vim", 0))'))
end)
+
+ it('fixed hang issue with -D (#12647)', function()
+ local screen
+ screen = Screen.new(60, 6)
+ screen:attach()
+ command([[let g:id = termopen('"]]..nvim_prog..
+ [[" -u NONE -i NONE --cmd "set noruler" -D')]])
+ screen:expect([[
+ ^ |
+ Entering Debug mode. Type "cont" to continue. |
+ cmd: augroup nvim_terminal |
+ > |
+ <" -u NONE -i NONE --cmd "set noruler" -D 1,0-1 All|
+ |
+ ]])
+ command([[call chansend(g:id, "cont\n")]])
+ screen:expect([[
+ ^ |
+ ~ |
+ [No Name] |
+ |
+ <" -u NONE -i NONE --cmd "set noruler" -D 1,0-1 All|
+ |
+ ]])
+ end)
+end)
+
+describe('clean', function()
+ clear()
+ ok(string.match(meths.get_option('runtimepath'), funcs.stdpath('config')) ~= nil)
+ clear('--clean')
+ ok(string.match(meths.get_option('runtimepath'), funcs.stdpath('config')) == nil)
end)
diff --git a/test/functional/eval/api_functions_spec.lua b/test/functional/eval/api_functions_spec.lua
index 4fbd08f102..ccd97fc8c7 100644
--- a/test/functional/eval/api_functions_spec.lua
+++ b/test/functional/eval/api_functions_spec.lua
@@ -44,7 +44,7 @@ describe('eval-API', function()
eq('Vim(call):E5555: API call: Wrong type for argument 1, expecting Buffer', err)
err = exc_exec('call nvim_buf_line_count(17)')
- eq('Vim(call):E5555: API call: Invalid buffer id', err)
+ eq('Vim(call):E5555: API call: Invalid buffer id: 17', err)
end)
@@ -144,7 +144,6 @@ describe('eval-API', function()
{5:~ }|
|
]])
- screen:detach()
end)
it('cannot be called from sandbox', function()
diff --git a/test/functional/eval/buf_functions_spec.lua b/test/functional/eval/buf_functions_spec.lua
index 37f4c89bfd..06841a4521 100644
--- a/test/functional/eval/buf_functions_spec.lua
+++ b/test/functional/eval/buf_functions_spec.lua
@@ -31,10 +31,12 @@ for _, func in ipairs({'bufname(%s)', 'bufnr(%s)', 'bufwinnr(%s)',
it('errors out when receives v:true/v:false/v:null', function()
-- Not compatible with Vim: in Vim it always results in buffer not found
-- without any error messages.
- for _, var in ipairs({'v:true', 'v:false', 'v:null'}) do
- eq('Vim(call):E5300: Expected a Number or a String',
+ for _, var in ipairs({'v:true', 'v:false'}) do
+ eq('Vim(call):E5299: Expected a Number or a String, Boolean found',
exc_exec('call ' .. func:format(var)))
end
+ eq('Vim(call):E5300: Expected a Number or a String',
+ exc_exec('call ' .. func:format('v:null')))
end)
it('errors out when receives invalid argument', function()
eq('Vim(call):E745: Expected a Number or a String, List found',
diff --git a/test/functional/eval/ctx_functions_spec.lua b/test/functional/eval/ctx_functions_spec.lua
index c81dad9645..f23adbc556 100644
--- a/test/functional/eval/ctx_functions_spec.lua
+++ b/test/functional/eval/ctx_functions_spec.lua
@@ -6,7 +6,7 @@ local command = helpers.command
local eq = helpers.eq
local eval = helpers.eval
local feed = helpers.feed
-local map = helpers.map
+local map = helpers.tbl_map
local nvim = helpers.nvim
local parse_context = helpers.parse_context
local redir_exec = helpers.redir_exec
diff --git a/test/functional/eval/environ_spec.lua b/test/functional/eval/environ_spec.lua
index 4c2adcf1bf..54d2dc960b 100644
--- a/test/functional/eval/environ_spec.lua
+++ b/test/functional/eval/environ_spec.lua
@@ -10,6 +10,7 @@ describe('environment variables', function()
eq("", environ()['EMPTY_VAR'])
eq(nil, environ()['DOES_NOT_EXIST'])
end)
+
it('exists() handles empty env variable', function()
clear({env={EMPTY_VAR=""}})
eq(1, exists('$EMPTY_VAR'))
diff --git a/test/functional/eval/fnamemodify_spec.lua b/test/functional/eval/fnamemodify_spec.lua
index fe6b50a544..d54a6db417 100644
--- a/test/functional/eval/fnamemodify_spec.lua
+++ b/test/functional/eval/fnamemodify_spec.lua
@@ -3,8 +3,14 @@ local clear = helpers.clear
local eq = helpers.eq
local iswin = helpers.iswin
local fnamemodify = helpers.funcs.fnamemodify
+local getcwd = helpers.funcs.getcwd
local command = helpers.command
local write_file = helpers.write_file
+local alter_slashes = helpers.alter_slashes
+
+local function eq_slashconvert(expected, got)
+ eq(alter_slashes(expected), alter_slashes(got))
+end
describe('fnamemodify()', function()
setup(function()
@@ -17,7 +23,7 @@ describe('fnamemodify()', function()
os.remove('Xtest-fnamemodify.txt')
end)
- it('works', function()
+ it('handles the root path', function()
local root = helpers.pathroot()
eq(root, fnamemodify([[/]], ':p:h'))
eq(root, fnamemodify([[/]], ':p'))
@@ -36,4 +42,115 @@ describe('fnamemodify()', function()
it(':8 works', function()
eq('Xtest-fnamemodify.txt', fnamemodify([[Xtest-fnamemodify.txt]], ':8'))
end)
+
+ it('handles examples from ":help filename-modifiers"', function()
+ local filename = "src/version.c"
+ local cwd = getcwd()
+
+ eq_slashconvert(cwd .. '/src/version.c', fnamemodify(filename, ':p'))
+
+ eq_slashconvert('src/version.c', fnamemodify(filename, ':p:.'))
+ eq_slashconvert(cwd .. '/src', fnamemodify(filename, ':p:h'))
+ eq_slashconvert(cwd .. '', fnamemodify(filename, ':p:h:h'))
+ eq('version.c', fnamemodify(filename, ':p:t'))
+ eq_slashconvert(cwd .. '/src/version', fnamemodify(filename, ':p:r'))
+
+ eq_slashconvert(cwd .. '/src/main.c', fnamemodify(filename, ':s?version?main?:p'))
+
+ local converted_cwd = cwd:gsub('/', '\\')
+ eq(converted_cwd .. '\\src\\version.c', fnamemodify(filename, ':p:gs?/?\\\\?'))
+
+ eq('src', fnamemodify(filename, ':h'))
+ eq('version.c', fnamemodify(filename, ':t'))
+ eq_slashconvert('src/version', fnamemodify(filename, ':r'))
+ eq('version', fnamemodify(filename, ':t:r'))
+ eq('c', fnamemodify(filename, ':e'))
+
+ eq_slashconvert('src/main.c', fnamemodify(filename, ':s?version?main?'))
+ end)
+
+ it('handles advanced examples from ":help filename-modifiers"', function()
+ local filename = "src/version.c.gz"
+
+ eq('gz', fnamemodify(filename, ':e'))
+ eq('c.gz', fnamemodify(filename, ':e:e'))
+ eq('c.gz', fnamemodify(filename, ':e:e:e'))
+
+ eq('c', fnamemodify(filename, ':e:e:r'))
+
+ eq_slashconvert('src/version.c', fnamemodify(filename, ':r'))
+ eq('c', fnamemodify(filename, ':r:e'))
+
+ eq_slashconvert('src/version', fnamemodify(filename, ':r:r'))
+ eq_slashconvert('src/version', fnamemodify(filename, ':r:r:r'))
+ end)
+
+ it('handles :h', function()
+ eq('.', fnamemodify('hello.txt', ':h'))
+
+ eq_slashconvert('path/to', fnamemodify('path/to/hello.txt', ':h'))
+ end)
+
+ it('handles :t', function()
+ eq('hello.txt', fnamemodify('hello.txt', ':t'))
+ eq_slashconvert('hello.txt', fnamemodify('path/to/hello.txt', ':t'))
+ end)
+
+ it('handles :r', function()
+ eq('hello', fnamemodify('hello.txt', ':r'))
+ eq_slashconvert('path/to/hello', fnamemodify('path/to/hello.txt', ':r'))
+ end)
+
+ it('handles :e', function()
+ eq('txt', fnamemodify('hello.txt', ':e'))
+ eq_slashconvert('txt', fnamemodify('path/to/hello.txt', ':e'))
+ end)
+
+ it('handles regex replacements', function()
+ eq('content-there-here.txt', fnamemodify('content-here-here.txt', ':s/here/there/'))
+ eq('content-there-there.txt', fnamemodify('content-here-here.txt', ':gs/here/there/'))
+ end)
+
+ it('handles shell escape', function()
+ local expected
+
+ if iswin() then
+ -- we expand with double-quotes on Windows
+ expected = [["hello there! quote ' newline]] .. '\n' .. [["]]
+ else
+ expected = [['hello there! quote '\'' newline]] .. '\n' .. [[']]
+ end
+
+ eq(expected, fnamemodify("hello there! quote ' newline\n", ':S'))
+ end)
+
+ it('can combine :e and :r', function()
+ -- simple, single extension filename
+ eq('c', fnamemodify('a.c', ':e'))
+ eq('c', fnamemodify('a.c', ':e:e'))
+ eq('c', fnamemodify('a.c', ':e:e:r'))
+ eq('c', fnamemodify('a.c', ':e:e:r:r'))
+
+ -- multi extension filename
+ eq('rb', fnamemodify('a.spec.rb', ':e:r'))
+ eq('rb', fnamemodify('a.spec.rb', ':e:r:r'))
+
+ eq('spec', fnamemodify('a.spec.rb', ':e:e:r'))
+ eq('spec', fnamemodify('a.spec.rb', ':e:e:r:r'))
+
+ eq('spec', fnamemodify('a.b.spec.rb', ':e:e:r'))
+ eq('b.spec', fnamemodify('a.b.spec.rb', ':e:e:e:r'))
+ eq('b', fnamemodify('a.b.spec.rb', ':e:e:e:r:r'))
+
+ eq('spec', fnamemodify('a.b.spec.rb', ':r:e'))
+ eq('b', fnamemodify('a.b.spec.rb', ':r:r:e'))
+
+ -- extraneous :e expansions
+ eq('c', fnamemodify('a.b.c.d.e', ':r:r:e'))
+ eq('b.c', fnamemodify('a.b.c.d.e', ':r:r:e:e'))
+
+ -- :e never includes the whole filename, so "a.b":e:e:e --> "b"
+ eq('b.c', fnamemodify('a.b.c.d.e', ':r:r:e:e:e'))
+ eq('b.c', fnamemodify('a.b.c.d.e', ':r:r:e:e:e:e'))
+ end)
end)
diff --git a/test/functional/eval/input_spec.lua b/test/functional/eval/input_spec.lua
index e774b939f7..14c02f9eb2 100644
--- a/test/functional/eval/input_spec.lua
+++ b/test/functional/eval/input_spec.lua
@@ -462,7 +462,7 @@ describe('confirm()', function()
-- With shortmess-=F
command('set shortmess-=F')
feed(':edit foo<cr>')
- check_and_clear('"foo" [New File] |\n')
+ check_and_clear('"foo" [New] |\n')
-- With shortmess+=F
command('set shortmess+=F')
diff --git a/test/functional/eval/let_spec.lua b/test/functional/eval/let_spec.lua
index 63e18f943f..5bc703b567 100644
--- a/test/functional/eval/let_spec.lua
+++ b/test/functional/eval/let_spec.lua
@@ -59,10 +59,6 @@ describe(':let', function()
end)
it("multibyte env var to child process #8398 #9267", function()
- if (not helpers.iswin()) and helpers.isCI() then
- -- Fails on non-Windows CI. Buffering/timing issue?
- pending('fails on unix CI', function() end)
- end
local cmd_get_child_env = "let g:env_from_child = system(['"..nvim_dir.."/printenv-test', 'NVIM_TEST'])"
command("let $NVIM_TEST = 'AìaB'")
command(cmd_get_child_env)
@@ -79,4 +75,19 @@ describe(':let', function()
command(cmd_get_child_env)
eq(eval('$NVIM_TEST'), eval('g:env_from_child'))
end)
+
+ it("release of list assigned to l: variable does not trigger assertion #12387, #12430", function()
+ source([[
+ func! s:f()
+ let l:x = [1]
+ let g:x = l:
+ endfunc
+ for _ in range(2)
+ call s:f()
+ endfor
+ call garbagecollect()
+ call feedkeys('i', 't')
+ ]])
+ eq(1, eval('1'))
+ end)
end)
diff --git a/test/functional/eval/map_functions_spec.lua b/test/functional/eval/map_functions_spec.lua
index 2747a94570..275c72d212 100644
--- a/test/functional/eval/map_functions_spec.lua
+++ b/test/functional/eval/map_functions_spec.lua
@@ -13,6 +13,7 @@ describe('maparg()', function()
local foo_bar_map_table = {
lhs='foo',
+ script=0,
silent=0,
rhs='bar',
expr=0,
@@ -147,6 +148,7 @@ describe('maparg()', function()
mode = 'n',
noremap = 1,
nowait = 0,
+ script=0,
sid = 0,
silent = 0,
lnum = 0,
diff --git a/test/functional/eval/null_spec.lua b/test/functional/eval/null_spec.lua
index afe999e1fa..db0a706319 100644
--- a/test/functional/eval/null_spec.lua
+++ b/test/functional/eval/null_spec.lua
@@ -47,10 +47,8 @@ describe('NULL', function()
-- Subjectable behaviour
- -- FIXME Should return 1
- null_expr_test('is equal to empty list', 'L == []', 0, 0)
- -- FIXME Should return 1
- null_expr_test('is equal to empty list (reverse order)', '[] == L', 0, 0)
+ null_expr_test('is equal to empty list', 'L == []', 0, 1)
+ null_expr_test('is equal to empty list (reverse order)', '[] == L', 0, 1)
-- Correct behaviour
null_expr_test('can be indexed with error message for empty list', 'L[0]',
diff --git a/test/functional/eval/sort_spec.lua b/test/functional/eval/sort_spec.lua
index 82557575ce..e1cc2c2924 100644
--- a/test/functional/eval/sort_spec.lua
+++ b/test/functional/eval/sort_spec.lua
@@ -14,7 +14,7 @@ before_each(clear)
describe('sort()', function()
it('errors out when sorting special values', function()
- eq('Vim(call):E907: Using a special value as a Float',
+ eq('Vim(call):E362: Using a boolean value as a Float',
exc_exec('call sort([v:true, v:false], "f")'))
end)
@@ -30,6 +30,7 @@ describe('sort()', function()
errors[err] = true
end
eq({
+ ['E362: Using a boolean value as a Float']=true,
['E891: Using a Funcref as a Float']=true,
['E892: Using a String as a Float']=true,
['E893: Using a List as a Float']=true,
diff --git a/test/functional/eval/system_spec.lua b/test/functional/eval/system_spec.lua
index 0a478fd05c..8b18eff451 100644
--- a/test/functional/eval/system_spec.lua
+++ b/test/functional/eval/system_spec.lua
@@ -8,6 +8,7 @@ local command = helpers.command
local exc_exec = helpers.exc_exec
local iswin = helpers.iswin
local os_kill = helpers.os_kill
+local pcall_err = helpers.pcall_err
local Screen = require('test.functional.ui.screen')
@@ -32,8 +33,9 @@ describe('system()', function()
return nvim_dir..'/printargs-test' .. (iswin() and '.exe' or '')
end
- it('sets v:shell_error if cmd[0] is not executable', function()
- call('system', { 'this-should-not-exist' })
+ it('throws error if cmd[0] is not executable', function()
+ eq("Vim:E475: Invalid value for argument cmd: 'this-should-not-exist' is not executable",
+ pcall_err(call, 'system', { 'this-should-not-exist' }))
eq(-1, eval('v:shell_error'))
end)
@@ -48,7 +50,8 @@ describe('system()', function()
eq(0, eval('v:shell_error'))
-- Provoke a non-zero v:shell_error.
- call('system', { 'this-should-not-exist' })
+ eq("Vim:E475: Invalid value for argument cmd: 'this-should-not-exist' is not executable",
+ pcall_err(call, 'system', { 'this-should-not-exist' }))
local old_val = eval('v:shell_error')
eq(-1, old_val)
@@ -84,7 +87,7 @@ describe('system()', function()
it('does NOT run in shell', function()
if iswin() then
- eq("%PATH%\n", eval("system(['powershell', '-NoProfile', '-NoLogo', '-ExecutionPolicy', 'RemoteSigned', '-Command', 'echo', '%PATH%'])"))
+ eq("%PATH%\n", eval("system(['powershell', '-NoProfile', '-NoLogo', '-ExecutionPolicy', 'RemoteSigned', '-Command', 'Write-Output', '%PATH%'])"))
else
eq("* $PATH %PATH%\n", eval("system(['echo', '*', '$PATH', '%PATH%'])"))
end
@@ -121,10 +124,6 @@ describe('system()', function()
screen:attach()
end)
- after_each(function()
- screen:detach()
- end)
-
if iswin() then
local function test_more()
eq('root = true', eval([[get(split(system('"more" ".editorconfig"'), "\n"), 0, '')]]))
@@ -133,7 +132,7 @@ describe('system()', function()
eval([[system('"ping" "-n" "1" "127.0.0.1"')]])
eq(0, eval('v:shell_error'))
eq('"a b"\n', eval([[system('cmd /s/c "cmd /s/c "cmd /s/c "echo "a b""""')]]))
- eq('"a b"\n', eval([[system('powershell -NoProfile -NoLogo -ExecutionPolicy RemoteSigned -Command echo ''\^"a b\^"''')]]))
+ eq('"a b"\n', eval([[system('powershell -NoProfile -NoLogo -ExecutionPolicy RemoteSigned -Command Write-Output ''\^"a b\^"''')]]))
end
it('with shell=cmd.exe', function()
@@ -169,9 +168,9 @@ describe('system()', function()
it('works with powershell', function()
helpers.set_shell_powershell()
- eq('a\nb\n', eval([[system('echo a b')]]))
+ eq('a\nb\n', eval([[system('Write-Output a b')]]))
eq('C:\\\n', eval([[system('cd c:\; (Get-Location).Path')]]))
- eq('a b\n', eval([[system('echo "a b"')]]))
+ eq('a b\n', eval([[system('Write-Output "a b"')]]))
end)
end
diff --git a/test/functional/eval/timer_spec.lua b/test/functional/eval/timer_spec.lua
index 2ccb9cfbac..ef7df69fdb 100644
--- a/test/functional/eval/timer_spec.lua
+++ b/test/functional/eval/timer_spec.lua
@@ -111,7 +111,13 @@ describe('timers', function()
curbufmeths.set_lines(0, -1, true, {"ITEM 1", "ITEM 2"})
source([[
+ let g:cont = 0
func! AddItem(timer)
+ if !g:cont
+ return
+ endif
+ call timer_stop(a:timer)
+
call nvim_buf_set_lines(0, 2, 2, v:true, ['ITEM 3'])
" Meant to test for what Vim tests in Test_peek_and_get_char.
@@ -121,7 +127,7 @@ describe('timers', function()
endfunc
]])
nvim_async("command", "let g:c2 = getchar()")
- nvim_async("command", "call timer_start("..load_adjust(100)..", 'AddItem')")
+ nvim_async("command", "call timer_start("..load_adjust(100)..", 'AddItem', {'repeat': -1})")
screen:expect([[
ITEM 1 |
@@ -131,6 +137,7 @@ describe('timers', function()
{1:~ }|
^ |
]])
+ nvim_async("command", "let g:cont = 1")
screen:expect([[
ITEM 1 |
@@ -222,12 +229,17 @@ describe('timers', function()
source([[
let g:val = 0
func! MyHandler(timer)
+ while !g:val
+ return
+ endwhile
+ call timer_stop(a:timer)
+
echo "evil"
redraw
- let g:val = 1
+ let g:val = 2
endfunc
]])
- command("call timer_start(100, 'MyHandler', {'repeat': 1})")
+ command("call timer_start(100, 'MyHandler', {'repeat': -1})")
feed(":good")
screen:expect([[
|
@@ -237,6 +249,7 @@ describe('timers', function()
{0:~ }|
:good^ |
]])
+ command('let g:val = 1')
screen:expect{grid=[[
|
@@ -247,6 +260,6 @@ describe('timers', function()
:good^ |
]], intermediate=true, timeout=load_adjust(200)}
- eq(1, eval('g:val'))
+ eq(2, eval('g:val'))
end)
end)
diff --git a/test/functional/eval/uniq_spec.lua b/test/functional/eval/uniq_spec.lua
index 0e7a013e32..5cdba0a0f6 100644
--- a/test/functional/eval/uniq_spec.lua
+++ b/test/functional/eval/uniq_spec.lua
@@ -11,7 +11,7 @@ before_each(clear)
describe('uniq()', function()
it('errors out when processing special values', function()
- eq('Vim(call):E907: Using a special value as a Float',
+ eq('Vim(call):E362: Using a boolean value as a Float',
exc_exec('call uniq([v:true, v:false], "f")'))
end)
diff --git a/test/functional/eval/wait_spec.lua b/test/functional/eval/wait_spec.lua
index ad7d1574cb..ee95e02a7f 100644
--- a/test/functional/eval/wait_spec.lua
+++ b/test/functional/eval/wait_spec.lua
@@ -58,8 +58,11 @@ describe('wait()', function()
endfunction
]])
- nvim('set_var', 'counter', 0)
- eq(-1, call('wait', 20, 'Count() >= 5', 99999))
+ -- XXX: flaky (#11137)
+ helpers.retry(nil, nil, function()
+ nvim('set_var', 'counter', 0)
+ eq(-1, call('wait', 20, 'Count() >= 5', 99999))
+ end)
nvim('set_var', 'counter', 0)
eq(0, call('wait', 10000, 'Count() >= 5', 5))
diff --git a/test/functional/ex_cmds/dict_notifications_spec.lua b/test/functional/ex_cmds/dict_notifications_spec.lua
index 48e7e05e4c..5c67431221 100644
--- a/test/functional/ex_cmds/dict_notifications_spec.lua
+++ b/test/functional/ex_cmds/dict_notifications_spec.lua
@@ -357,4 +357,18 @@ describe('VimL dictionary notifications', function()
eq(2, eval('1+1')) -- Still alive?
end)
+ it('does not cause use-after-free when unletting from callback', function()
+ source([[
+ let g:called = 0
+ function W(...) abort
+ unlet g:d
+ let g:called = 1
+ endfunction
+ let g:d = {}
+ call dictwatcheradd(g:d, '*', function('W'))
+ let g:d.foo = 123
+ ]])
+ eq(1, eval('g:called'))
+ end)
+
end)
diff --git a/test/functional/ex_cmds/drop_spec.lua b/test/functional/ex_cmds/drop_spec.lua
index d6da0d8e88..ef53fe75e3 100644
--- a/test/functional/ex_cmds/drop_spec.lua
+++ b/test/functional/ex_cmds/drop_spec.lua
@@ -19,10 +19,6 @@ describe(":drop", function()
command("set laststatus=2 shortmess-=F")
end)
- after_each(function()
- screen:detach()
- end)
-
it("works like :e when called with only one window open", function()
feed_command("drop tmp1.vim")
screen:expect([[
@@ -35,7 +31,7 @@ describe(":drop", function()
{0:~ }|
{0:~ }|
{1:tmp1.vim }|
- "tmp1.vim" [New File] |
+ "tmp1.vim" [New] |
]])
end)
@@ -74,7 +70,7 @@ describe(":drop", function()
{0:~ }{2:│}{0:~ }|
{0:~ }{2:│}{0:~ }|
{2:tmp2 [+] tmp1 }|
- "tmp3" [New File] |
+ "tmp3" [New] |
]])
end)
diff --git a/test/functional/ex_cmds/echo_spec.lua b/test/functional/ex_cmds/echo_spec.lua
index 10c7230896..404dc39ad2 100644
--- a/test/functional/ex_cmds/echo_spec.lua
+++ b/test/functional/ex_cmds/echo_spec.lua
@@ -11,31 +11,57 @@ local dedent = helpers.dedent
local command = helpers.command
local exc_exec = helpers.exc_exec
local redir_exec = helpers.redir_exec
+local matches = helpers.matches
+
+describe(':echo :echon :echomsg :echoerr', function()
+ local fn_tbl = {'String', 'StringN', 'StringMsg', 'StringErr'}
+ local function assert_same_echo_dump(expected, input, use_eval)
+ for _,v in pairs(fn_tbl) do
+ eq(expected, use_eval and eval(v..'('..input..')') or funcs[v](input))
+ end
+ end
+ local function assert_matches_echo_dump(expected, input, use_eval)
+ for _,v in pairs(fn_tbl) do
+ matches(expected, use_eval and eval(v..'('..input..')') or funcs[v](input))
+ end
+ end
-describe(':echo', function()
before_each(function()
clear()
source([[
function String(s)
return execute('echo a:s')[1:]
endfunction
+ function StringMsg(s)
+ return execute('echomsg a:s')[1:]
+ endfunction
+ function StringN(s)
+ return execute('echon a:s')
+ endfunction
+ function StringErr(s)
+ try
+ execute 'echoerr a:s'
+ catch
+ return substitute(v:exception, '^Vim(echoerr):', '', '')
+ endtry
+ endfunction
]])
end)
describe('used to represent floating-point values', function()
it('dumps NaN values', function()
- eq('str2float(\'nan\')', eval('String(str2float(\'nan\'))'))
+ assert_same_echo_dump("str2float('nan')", "str2float('nan')", true)
end)
it('dumps infinite values', function()
- eq('str2float(\'inf\')', eval('String(str2float(\'inf\'))'))
- eq('-str2float(\'inf\')', eval('String(str2float(\'-inf\'))'))
+ assert_same_echo_dump("str2float('inf')", "str2float('inf')", true)
+ assert_same_echo_dump("-str2float('inf')", "str2float('-inf')", true)
end)
it('dumps regular values', function()
- eq('1.5', funcs.String(1.5))
- eq('1.56e-20', funcs.String(1.56000e-020))
- eq('0.0', eval('String(0.0)'))
+ assert_same_echo_dump('1.5', 1.5)
+ assert_same_echo_dump('1.56e-20', 1.56000e-020)
+ assert_same_echo_dump('0.0', '0.0', true)
end)
it('dumps special v: values', function()
@@ -45,69 +71,81 @@ describe(':echo', function()
eq('v:true', funcs.String(true))
eq('v:false', funcs.String(false))
eq('v:null', funcs.String(NIL))
+ eq('v:true', eval('StringMsg(v:true)'))
+ eq('v:false', eval('StringMsg(v:false)'))
+ eq('v:null', eval('StringMsg(v:null)'))
+ eq('v:true', funcs.StringMsg(true))
+ eq('v:false', funcs.StringMsg(false))
+ eq('v:null', funcs.StringMsg(NIL))
+ eq('v:true', eval('StringErr(v:true)'))
+ eq('v:false', eval('StringErr(v:false)'))
+ eq('v:null', eval('StringErr(v:null)'))
+ eq('v:true', funcs.StringErr(true))
+ eq('v:false', funcs.StringErr(false))
+ eq('v:null', funcs.StringErr(NIL))
end)
it('dumps values with at most six digits after the decimal point',
function()
- eq('1.234568e-20', funcs.String(1.23456789123456789123456789e-020))
- eq('1.234568', funcs.String(1.23456789123456789123456789))
+ assert_same_echo_dump('1.234568e-20', 1.23456789123456789123456789e-020)
+ assert_same_echo_dump('1.234568', 1.23456789123456789123456789)
end)
it('dumps values with at most seven digits before the decimal point',
function()
- eq('1234567.891235', funcs.String(1234567.89123456789123456789))
- eq('1.234568e7', funcs.String(12345678.9123456789123456789))
+ assert_same_echo_dump('1234567.891235', 1234567.89123456789123456789)
+ assert_same_echo_dump('1.234568e7', 12345678.9123456789123456789)
end)
it('dumps negative values', function()
- eq('-1.5', funcs.String(-1.5))
- eq('-1.56e-20', funcs.String(-1.56000e-020))
- eq('-1.234568e-20', funcs.String(-1.23456789123456789123456789e-020))
- eq('-1.234568', funcs.String(-1.23456789123456789123456789))
- eq('-1234567.891235', funcs.String(-1234567.89123456789123456789))
- eq('-1.234568e7', funcs.String(-12345678.9123456789123456789))
+ assert_same_echo_dump('-1.5', -1.5)
+ assert_same_echo_dump('-1.56e-20', -1.56000e-020)
+ assert_same_echo_dump('-1.234568e-20', -1.23456789123456789123456789e-020)
+ assert_same_echo_dump('-1.234568', -1.23456789123456789123456789)
+ assert_same_echo_dump('-1234567.891235', -1234567.89123456789123456789)
+ assert_same_echo_dump('-1.234568e7', -12345678.9123456789123456789)
end)
end)
describe('used to represent numbers', function()
it('dumps regular values', function()
- eq('0', funcs.String(0))
- eq('-1', funcs.String(-1))
- eq('1', funcs.String(1))
+ assert_same_echo_dump('0', 0)
+ assert_same_echo_dump('-1', -1)
+ assert_same_echo_dump('1', 1)
end)
it('dumps large values', function()
- eq('2147483647', funcs.String(2^31-1))
- eq('-2147483648', funcs.String(-2^31))
+ assert_same_echo_dump('2147483647', 2^31-1)
+ assert_same_echo_dump('-2147483648', -2^31)
end)
end)
describe('used to represent strings', function()
it('dumps regular strings', function()
- eq('test', funcs.String('test'))
+ assert_same_echo_dump('test', 'test')
end)
it('dumps empty strings', function()
- eq('', funcs.String(''))
+ assert_same_echo_dump('', '')
end)
- it('dumps strings with \' inside', function()
- eq('\'\'\'', funcs.String('\'\'\''))
- eq('a\'b\'\'', funcs.String('a\'b\'\''))
- eq('\'b\'\'d', funcs.String('\'b\'\'d'))
- eq('a\'b\'c\'d', funcs.String('a\'b\'c\'d'))
+ it("dumps strings with ' inside", function()
+ assert_same_echo_dump("'''", "'''")
+ assert_same_echo_dump("a'b''", "a'b''")
+ assert_same_echo_dump("'b''d", "'b''d")
+ assert_same_echo_dump("a'b'c'd", "a'b'c'd")
end)
it('dumps NULL strings', function()
- eq('', eval('String($XXX_UNEXISTENT_VAR_XXX)'))
+ assert_same_echo_dump('', '$XXX_UNEXISTENT_VAR_XXX', true)
end)
it('dumps NULL lists', function()
- eq('[]', eval('String(v:_null_list)'))
+ assert_same_echo_dump('[]', 'v:_null_list', true)
end)
it('dumps NULL dictionaries', function()
- eq('{}', eval('String(v:_null_dict)'))
+ assert_same_echo_dump('{}', 'v:_null_dict', true)
end)
end)
@@ -129,15 +167,27 @@ describe(':echo', function()
it('dumps references to built-in functions', function()
eq('function', eval('String(function("function"))'))
+ eq("function('function')", eval('StringMsg(function("function"))'))
+ eq("function('function')", eval('StringErr(function("function"))'))
end)
it('dumps references to user functions', function()
eq('Test1', eval('String(function("Test1"))'))
eq('g:Test3', eval('String(function("g:Test3"))'))
+ eq("function('Test1')", eval("StringMsg(function('Test1'))"))
+ eq("function('g:Test3')", eval("StringMsg(function('g:Test3'))"))
+ eq("function('Test1')", eval("StringErr(function('Test1'))"))
+ eq("function('g:Test3')", eval("StringErr(function('g:Test3'))"))
end)
it('dumps references to script functions', function()
eq('<SNR>2_Test2', eval('String(Test2_f)'))
+ eq("function('<SNR>2_Test2')", eval('StringMsg(Test2_f)'))
+ eq("function('<SNR>2_Test2')", eval('StringErr(Test2_f)'))
+ end)
+
+ it('dump references to lambdas', function()
+ assert_matches_echo_dump("function%('<lambda>%d+'%)", '{-> 1234}', true)
end)
it('dumps partials with self referencing a partial', function()
@@ -156,19 +206,23 @@ describe(':echo', function()
end)
it('dumps automatically created partials', function()
- eq('function(\'<SNR>2_Test2\', {\'f\': function(\'<SNR>2_Test2\')})',
- eval('String({"f": Test2_f}.f)'))
- eq('function(\'<SNR>2_Test2\', [1], {\'f\': function(\'<SNR>2_Test2\', [1])})',
- eval('String({"f": function(Test2_f, [1])}.f)'))
+ assert_same_echo_dump(
+ "function('<SNR>2_Test2', {'f': function('<SNR>2_Test2')})",
+ '{"f": Test2_f}.f',
+ true)
+ assert_same_echo_dump(
+ "function('<SNR>2_Test2', [1], {'f': function('<SNR>2_Test2', [1])})",
+ '{"f": function(Test2_f, [1])}.f',
+ true)
end)
it('dumps manually created partials', function()
- eq('function(\'Test3\', [1, 2], {})',
- eval('String(function("Test3", [1, 2], {}))'))
- eq('function(\'Test3\', {})',
- eval('String(function("Test3", {}))'))
- eq('function(\'Test3\', [1, 2])',
- eval('String(function("Test3", [1, 2]))'))
+ assert_same_echo_dump("function('Test3', [1, 2], {})",
+ "function('Test3', [1, 2], {})", true)
+ assert_same_echo_dump("function('Test3', [1, 2])",
+ "function('Test3', [1, 2])", true)
+ assert_same_echo_dump("function('Test3', {})",
+ "function('Test3', {})", true)
end)
it('does not crash or halt when dumping partials with reference cycles in self',
@@ -225,15 +279,19 @@ describe(':echo', function()
describe('used to represent lists', function()
it('dumps empty list', function()
- eq('[]', funcs.String({}))
+ assert_same_echo_dump('[]', {})
+ end)
+
+ it('dumps non-empty list', function()
+ assert_same_echo_dump('[1, 2]', {1,2})
end)
it('dumps nested lists', function()
- eq('[[[[[]]]]]', funcs.String({{{{{}}}}}))
+ assert_same_echo_dump('[[[[[]]]]]', {{{{{}}}}})
end)
it('dumps nested non-empty lists', function()
- eq('[1, [[3, [[5], 4]], 2]]', funcs.String({1, {{3, {{5}, 4}}, 2}}))
+ assert_same_echo_dump('[1, [[3, [[5], 4]], 2]]', {1, {{3, {{5}, 4}}, 2}})
end)
it('does not error when dumping recursive lists', function()
@@ -252,18 +310,18 @@ describe(':echo', function()
describe('used to represent dictionaries', function()
it('dumps empty dictionary', function()
- eq('{}', eval('String({})'))
+ assert_same_echo_dump('{}', '{}', true)
end)
it('dumps list with two same empty dictionaries, also in partials', function()
command('let d = {}')
- eq('[{}, {}]', eval('String([d, d])'))
+ assert_same_echo_dump('[{}, {}]', '[d, d]', true)
eq('[function(\'tr\', {}), {}]', eval('String([function("tr", d), d])'))
eq('[{}, function(\'tr\', {})]', eval('String([d, function("tr", d)])'))
end)
it('dumps non-empty dictionary', function()
- eq('{\'t\'\'est\': 1}', funcs.String({['t\'est']=1}))
+ assert_same_echo_dump("{'t''est': 1}", {["t'est"]=1})
end)
it('does not error when dumping recursive dictionaries', function()
@@ -297,11 +355,20 @@ describe(':echo', function()
eq('<8e>', funcs.String(chr(0x8e)))
eq('<c2>', funcs.String(('«'):sub(1, 1)))
eq('«', funcs.String(('«'):sub(1, 2)))
+
+ eq('<80>', funcs.StringMsg(chr(0x80)))
+ eq('<81>', funcs.StringMsg(chr(0x81)))
+ eq('<8e>', funcs.StringMsg(chr(0x8e)))
+ eq('<c2>', funcs.StringMsg(('«'):sub(1, 1)))
+ eq('«', funcs.StringMsg(('«'):sub(1, 2)))
end)
it('displays ASCII control characters using ^X notation', function()
eq('^C', funcs.String(ctrl('c')))
eq('^A', funcs.String(ctrl('a')))
eq('^F', funcs.String(ctrl('f')))
+ eq('^C', funcs.StringMsg(ctrl('c')))
+ eq('^A', funcs.StringMsg(ctrl('a')))
+ eq('^F', funcs.StringMsg(ctrl('f')))
end)
it('prints CR, NL and tab as-is', function()
eq('\n', funcs.String('\n'))
@@ -311,11 +378,15 @@ describe(':echo', function()
it('prints non-printable UTF-8 in <> notation', function()
-- SINGLE SHIFT TWO, unicode control
eq('<8e>', funcs.String(funcs.nr2char(0x8E)))
+ eq('<8e>', funcs.StringMsg(funcs.nr2char(0x8E)))
-- Surrogate pair: U+1F0A0 PLAYING CARD BACK is represented in UTF-16 as
-- 0xD83C 0xDCA0. This is not valid in UTF-8.
eq('<d83c>', funcs.String(funcs.nr2char(0xD83C)))
eq('<dca0>', funcs.String(funcs.nr2char(0xDCA0)))
eq('<d83c><dca0>', funcs.String(funcs.nr2char(0xD83C) .. funcs.nr2char(0xDCA0)))
+ eq('<d83c>', funcs.StringMsg(funcs.nr2char(0xD83C)))
+ eq('<dca0>', funcs.StringMsg(funcs.nr2char(0xDCA0)))
+ eq('<d83c><dca0>', funcs.StringMsg(funcs.nr2char(0xD83C) .. funcs.nr2char(0xDCA0)))
end)
end)
end)
diff --git a/test/functional/ex_cmds/highlight_spec.lua b/test/functional/ex_cmds/highlight_spec.lua
index 25968b8204..1cd6759a53 100644
--- a/test/functional/ex_cmds/highlight_spec.lua
+++ b/test/functional/ex_cmds/highlight_spec.lua
@@ -13,10 +13,6 @@ describe(':highlight', function()
screen:attach()
end)
- after_each(function()
- screen:detach()
- end)
-
it('invalid color name', function()
eq('Vim(highlight):E421: Color name or number not recognized: ctermfg=#181818',
exc_exec("highlight normal ctermfg=#181818"))
diff --git a/test/functional/ex_cmds/ls_spec.lua b/test/functional/ex_cmds/ls_spec.lua
index f7bacd7386..9853084c47 100644
--- a/test/functional/ex_cmds/ls_spec.lua
+++ b/test/functional/ex_cmds/ls_spec.lua
@@ -31,6 +31,18 @@ describe(':ls', function()
-- Terminal buffer [F]inished.
eq('\n 3 %aF', string.match(ls_output, '\n *3....'))
end)
+
+ retry(nil, 5000, function()
+ local ls_output = eval('execute("ls R")')
+ -- Just the [R]unning terminal buffer.
+ eq('\n 2 #aR ', string.match(ls_output, '^\n *2 ... '))
+ end)
+
+ retry(nil, 5000, function()
+ local ls_output = eval('execute("ls F")')
+ -- Just the [F]inished terminal buffer.
+ eq('\n 3 %aF ', string.match(ls_output, '^\n *3 ... '))
+ end)
end)
end)
diff --git a/test/functional/ex_cmds/mksession_spec.lua b/test/functional/ex_cmds/mksession_spec.lua
index 0f7860740e..949724bb53 100644
--- a/test/functional/ex_cmds/mksession_spec.lua
+++ b/test/functional/ex_cmds/mksession_spec.lua
@@ -6,6 +6,8 @@ local command = helpers.command
local get_pathsep = helpers.get_pathsep
local eq = helpers.eq
local funcs = helpers.funcs
+local matches = helpers.matches
+local pesc = helpers.pesc
local rmdir = helpers.rmdir
local file_prefix = 'Xtest-functional-ex_cmds-mksession_spec'
@@ -24,6 +26,27 @@ describe(':mksession', function()
rmdir(tab_dir)
end)
+ it('restores same :terminal buf in splits', function()
+ -- If the same :terminal is displayed in multiple windows, :mksession
+ -- should restore it as such.
+
+ -- Create two windows showing the same :terminal buffer.
+ command('terminal')
+ command('split')
+ command('terminal')
+ command('split')
+ command('mksession '..session_file)
+
+ -- Create a new test instance of Nvim.
+ command('qall!')
+ clear()
+ -- Restore session.
+ command('source '..session_file)
+
+ eq({3,3,2},
+ {funcs.winbufnr(1), funcs.winbufnr(2), funcs.winbufnr(3)})
+ end)
+
it('restores tab-local working directories', function()
local tmpfile_base = file_prefix .. '-tmpfile'
local cwd_dir = funcs.getcwd()
@@ -48,7 +71,7 @@ describe(':mksession', function()
eq(cwd_dir .. get_pathsep() .. tab_dir, funcs.getcwd())
end)
- it('restores buffers when using tab-local working directories', function()
+ it('restores buffers with tab-local CWD', function()
local tmpfile_base = file_prefix .. '-tmpfile'
local cwd_dir = funcs.getcwd()
local session_path = cwd_dir .. get_pathsep() .. session_file
@@ -70,4 +93,24 @@ describe(':mksession', function()
command('tabnext 2')
eq(cwd_dir .. get_pathsep() .. tmpfile_base .. '2', funcs.expand('%:p'))
end)
+
+ it('restores CWD for :terminal buffers #11288', function()
+ local cwd_dir = funcs.fnamemodify('.', ':p:~'):gsub([[[\/]*$]], '')
+ cwd_dir = cwd_dir:gsub([[\]], '/') -- :mksession always uses unix slashes.
+ local session_path = cwd_dir..'/'..session_file
+
+ command('cd '..tab_dir)
+ command('terminal echo $PWD')
+ command('cd '..cwd_dir)
+ command('mksession '..session_path)
+ command('qall!')
+
+ -- Create a new test instance of Nvim.
+ clear()
+ command('silent source '..session_path)
+
+ local expected_cwd = cwd_dir..'/'..tab_dir
+ matches('^term://'..pesc(expected_cwd)..'//%d+:', funcs.expand('%'))
+ command('qall!')
+ end)
end)
diff --git a/test/functional/ex_cmds/profile_spec.lua b/test/functional/ex_cmds/profile_spec.lua
index f185db192a..2b92f8d0de 100644
--- a/test/functional/ex_cmds/profile_spec.lua
+++ b/test/functional/ex_cmds/profile_spec.lua
@@ -6,6 +6,9 @@ local eval = helpers.eval
local command = helpers.command
local eq, neq = helpers.eq, helpers.neq
local tempfile = helpers.tmpname()
+local source = helpers.source
+local matches = helpers.matches
+local read_file = helpers.read_file
-- tmpname() also creates the file on POSIX systems. Remove it again.
-- We just need the name, ignoring any race conditions.
@@ -32,20 +35,67 @@ describe(':profile', function()
end
end)
- it('dump', function()
- eq(0, eval('v:profiling'))
- command('profile start ' .. tempfile)
- eq(1, eval('v:profiling'))
- assert_file_exists_not(tempfile)
- command('profile dump')
- assert_file_exists(tempfile)
+ describe('dump', function()
+ it('works', function()
+ eq(0, eval('v:profiling'))
+ command('profile start ' .. tempfile)
+ eq(1, eval('v:profiling'))
+ assert_file_exists_not(tempfile)
+ command('profile dump')
+ assert_file_exists(tempfile)
+ end)
+
+ it('not resetting the profile', function()
+ source([[
+ function! Test()
+ endfunction
+ ]])
+ command('profile start ' .. tempfile)
+ assert_file_exists_not(tempfile)
+ command('profile func Test')
+ command('call Test()')
+ command('profile dump')
+ assert_file_exists(tempfile)
+ local profile = read_file(tempfile)
+ matches('Called 1 time', profile)
+ command('call Test()')
+ command('profile dump')
+ assert_file_exists(tempfile)
+ profile = read_file(tempfile)
+ matches('Called 2 time', profile)
+ command('profile stop')
+ end)
end)
- it('stop', function()
- command('profile start ' .. tempfile)
- assert_file_exists_not(tempfile)
- command('profile stop')
- assert_file_exists(tempfile)
- eq(0, eval('v:profiling'))
+ describe('stop', function()
+ it('works', function()
+ command('profile start ' .. tempfile)
+ assert_file_exists_not(tempfile)
+ command('profile stop')
+ assert_file_exists(tempfile)
+ eq(0, eval('v:profiling'))
+ end)
+
+ it('resetting the profile', function()
+ source([[
+ function! Test()
+ endfunction
+ ]])
+ command('profile start ' .. tempfile)
+ assert_file_exists_not(tempfile)
+ command('profile func Test')
+ command('call Test()')
+ command('profile stop')
+ assert_file_exists(tempfile)
+ local profile = read_file(tempfile)
+ matches('Called 1 time', profile)
+ command('profile start ' .. tempfile)
+ command('profile func Test')
+ command('call Test()')
+ command('profile stop')
+ assert_file_exists(tempfile)
+ profile = read_file(tempfile)
+ matches('Called 1 time', profile)
+ end)
end)
end)
diff --git a/test/functional/ex_cmds/script_spec.lua b/test/functional/ex_cmds/script_spec.lua
index 4e57d2755d..0a772c559b 100644
--- a/test/functional/ex_cmds/script_spec.lua
+++ b/test/functional/ex_cmds/script_spec.lua
@@ -22,7 +22,7 @@ describe('script_get-based command', function()
%s %s
endif
]])):format(cmd, garbage)))
- eq('', meths.command_output('messages'))
+ eq('', meths.exec('messages', true))
if check_neq then
neq(0, exc_exec(dedent([[
%s %s
@@ -37,7 +37,7 @@ describe('script_get-based command', function()
EOF
endif
]])):format(cmd, garbage)))
- eq('', meths.command_output('messages'))
+ eq('', meths.exec('messages', true))
if check_neq then
eq(true, pcall(source, (dedent([[
let g:exc = 0
diff --git a/test/functional/ex_cmds/sign_spec.lua b/test/functional/ex_cmds/sign_spec.lua
index 9d08a66625..891cfe1670 100644
--- a/test/functional/ex_cmds/sign_spec.lua
+++ b/test/functional/ex_cmds/sign_spec.lua
@@ -16,8 +16,8 @@ describe('sign', function()
nvim('command', 'sign place 34 line=3 name=Foo buffer='..buf2)
-- now unplace without specifying a buffer
nvim('command', 'sign unplace 34')
- eq("--- Signs ---\n", nvim('command_output', 'sign place buffer='..buf1))
- eq("--- Signs ---\n", nvim('command_output', 'sign place buffer='..buf2))
+ eq("--- Signs ---\n", nvim('exec', 'sign place buffer='..buf1, true))
+ eq("--- Signs ---\n", nvim('exec', 'sign place buffer='..buf2, true))
end)
end)
end)
diff --git a/test/functional/ex_cmds/write_spec.lua b/test/functional/ex_cmds/write_spec.lua
index 3f54ff6f41..4f526ddfca 100644
--- a/test/functional/ex_cmds/write_spec.lua
+++ b/test/functional/ex_cmds/write_spec.lua
@@ -41,7 +41,7 @@ describe(':write', function()
command("silent !ln -s test_bkc_file.txt test_bkc_link.txt")
end
if eval('v:shell_error') ~= 0 then
- pending('Cannot create symlink', function()end)
+ pending('Cannot create symlink')
end
source([[
edit test_bkc_link.txt
@@ -61,7 +61,7 @@ describe(':write', function()
command("silent !ln -s test_bkc_file.txt test_bkc_link.txt")
end
if eval('v:shell_error') ~= 0 then
- pending('Cannot create symlink', function()end)
+ pending('Cannot create symlink')
end
source([[
edit test_bkc_link.txt
@@ -75,8 +75,7 @@ describe(':write', function()
it("appends FIFO file", function()
-- mkfifo creates read-only .lnk files on Windows
if iswin() or eval("executable('mkfifo')") == 0 then
- pending('missing "mkfifo" command', function()end)
- return
+ pending('missing "mkfifo" command')
end
local text = "some fifo text from write_spec"
diff --git a/test/functional/example_spec.lua b/test/functional/example_spec.lua
index 883fe4ba63..f07f88b2b6 100644
--- a/test/functional/example_spec.lua
+++ b/test/functional/example_spec.lua
@@ -3,7 +3,10 @@
local helpers = require('test.functional.helpers')(after_each)
local Screen = require('test.functional.ui.screen')
-local clear, feed = helpers.clear, helpers.feed
+local clear = helpers.clear
+local command = helpers.command
+local eq = helpers.eq
+local feed = helpers.feed
describe('example', function()
local screen
@@ -33,4 +36,37 @@ describe('example', function()
|
]])
end)
+
+ it('override UI event-handler', function()
+ -- Example: override the "tabline_update" UI event handler.
+ --
+ -- screen.lua defines default handlers for UI events, but tests
+ -- may sometimes want to override a handler.
+
+ -- The UI must declare that it wants to handle the UI events.
+ -- For this example, we enable `ext_tabline`:
+ screen:detach()
+ screen = Screen.new(25, 5)
+ screen:attach({rgb=true, ext_tabline=true})
+
+ -- From ":help ui" we find that `tabline_update` receives `curtab` and
+ -- `tabs` objects. So we declare the UI handler like this:
+ local event_tabs, event_curtab
+ function screen:_handle_tabline_update(curtab, tabs)
+ event_curtab, event_tabs = curtab, tabs
+ end
+
+ -- Create a tabpage...
+ command('tabedit foo')
+
+ -- Use screen:expect{condition=…} to check the result.
+ screen:expect{condition=function()
+ eq({ id = 2 }, event_curtab)
+ eq({
+ {tab = { id = 1 }, name = '[No Name]'},
+ {tab = { id = 2 }, name = 'foo'},
+ },
+ event_tabs)
+ end}
+ end)
end)
diff --git a/test/functional/fixtures/CMakeLists.txt b/test/functional/fixtures/CMakeLists.txt
index dbcb157956..270540de2e 100644
--- a/test/functional/fixtures/CMakeLists.txt
+++ b/test/functional/fixtures/CMakeLists.txt
@@ -1,12 +1,12 @@
-add_executable(tty-test tty-test.c)
+add_executable(tty-test EXCLUDE_FROM_ALL tty-test.c)
target_link_libraries(tty-test ${LIBUV_LIBRARIES})
-add_executable(shell-test shell-test.c)
-add_executable(printargs-test printargs-test.c)
-add_executable(printenv-test printenv-test.c)
+add_executable(shell-test EXCLUDE_FROM_ALL shell-test.c)
+add_executable(printargs-test EXCLUDE_FROM_ALL printargs-test.c)
+add_executable(printenv-test EXCLUDE_FROM_ALL printenv-test.c)
if(WIN32)
set_target_properties(printenv-test PROPERTIES LINK_FLAGS -municode)
endif()
-add_executable(streams-test streams-test.c)
+add_executable(streams-test EXCLUDE_FROM_ALL streams-test.c)
target_link_libraries(streams-test ${LIBUV_LIBRARIES})
diff --git a/test/functional/fixtures/api_level_6.mpack b/test/functional/fixtures/api_level_6.mpack
index b348f86beb..1c939d5931 100644
--- a/test/functional/fixtures/api_level_6.mpack
+++ b/test/functional/fixtures/api_level_6.mpack
Binary files differ
diff --git a/test/functional/fixtures/fake-lsp-server.lua b/test/functional/fixtures/fake-lsp-server.lua
new file mode 100644
index 0000000000..dca7f35923
--- /dev/null
+++ b/test/functional/fixtures/fake-lsp-server.lua
@@ -0,0 +1,450 @@
+local protocol = require 'vim.lsp.protocol'
+
+
+-- Logs to $NVIM_LOG_FILE.
+--
+-- TODO(justinmk): remove after https://github.com/neovim/neovim/pull/7062
+local function log(loglevel, area, msg)
+ vim.fn.writefile(
+ {string.format('%s %s: %s', loglevel, area, msg)},
+ vim.env.NVIM_LOG_FILE,
+ 'a')
+end
+
+local function message_parts(sep, ...)
+ local parts = {}
+ for i = 1, select("#", ...) do
+ local arg = select(i, ...)
+ if arg ~= nil then
+ table.insert(parts, arg)
+ end
+ end
+ return table.concat(parts, sep)
+end
+
+-- Assert utility methods
+
+local function assert_eq(a, b, ...)
+ if not vim.deep_equal(a, b) then
+ error(message_parts(": ",
+ ..., "assert_eq failed",
+ string.format("left == %q, right == %q", vim.inspect(a), vim.inspect(b))
+ ))
+ end
+end
+
+local function format_message_with_content_length(encoded_message)
+ return table.concat {
+ 'Content-Length: '; tostring(#encoded_message); '\r\n\r\n';
+ encoded_message;
+ }
+end
+
+local function read_message()
+ local line = io.read("*l")
+ local length = line:lower():match("content%-length:%s*(%d+)")
+ return vim.fn.json_decode(io.read(2 + length):sub(2))
+end
+
+local function send(payload)
+ io.stdout:write(format_message_with_content_length(vim.fn.json_encode(payload)))
+end
+
+local function respond(id, err, result)
+ assert(type(id) == 'number', "id must be a number")
+ send { jsonrpc = "2.0"; id = id, error = err, result = result }
+end
+
+local function notify(method, params)
+ assert(type(method) == 'string', "method must be a string")
+ send { method = method, params = params or {} }
+end
+
+local function expect_notification(method, params, ...)
+ local message = read_message()
+ assert_eq(method, message.method,
+ ..., "expect_notification", "method")
+ assert_eq(params, message.params,
+ ..., "expect_notification", method, "params")
+ assert_eq({jsonrpc = "2.0"; method=method, params=params}, message,
+ ..., "expect_notification", "message")
+end
+
+local function expect_request(method, callback, ...)
+ local req = read_message()
+ assert_eq(method, req.method,
+ ..., "expect_request", "method")
+ local err, result = callback(req.params)
+ respond(req.id, err, result)
+end
+
+io.stderr:setvbuf("no")
+
+local function skeleton(config)
+ local on_init = assert(config.on_init)
+ local body = assert(config.body)
+ expect_request("initialize", function(params)
+ return nil, on_init(params)
+ end)
+ expect_notification("initialized", {})
+ body()
+ expect_request("shutdown", function()
+ return nil, {}
+ end)
+ expect_notification("exit", nil)
+end
+
+-- The actual tests.
+
+local tests = {}
+
+function tests.basic_init()
+ skeleton {
+ on_init = function(_params)
+ return { capabilities = {} }
+ end;
+ body = function()
+ notify('test')
+ end;
+ }
+end
+
+function tests.basic_check_capabilities()
+ skeleton {
+ on_init = function(params)
+ local expected_capabilities = protocol.make_client_capabilities()
+ assert_eq(params.capabilities, expected_capabilities)
+ return {
+ capabilities = {
+ textDocumentSync = protocol.TextDocumentSyncKind.Full;
+ }
+ }
+ end;
+ body = function()
+ end;
+ }
+end
+
+function tests.basic_finish()
+ skeleton {
+ on_init = function(params)
+ local expected_capabilities = protocol.make_client_capabilities()
+ assert_eq(params.capabilities, expected_capabilities)
+ return {
+ capabilities = {
+ textDocumentSync = protocol.TextDocumentSyncKind.Full;
+ }
+ }
+ end;
+ body = function()
+ expect_notification("finish")
+ notify('finish')
+ end;
+ }
+end
+
+function tests.basic_check_buffer_open()
+ skeleton {
+ on_init = function(params)
+ local expected_capabilities = protocol.make_client_capabilities()
+ assert_eq(params.capabilities, expected_capabilities)
+ return {
+ capabilities = {
+ textDocumentSync = protocol.TextDocumentSyncKind.Full;
+ }
+ }
+ end;
+ body = function()
+ notify('start')
+ expect_notification('textDocument/didOpen', {
+ textDocument = {
+ languageId = "";
+ text = table.concat({"testing"; "123"}, "\n") .. '\n';
+ uri = "file://";
+ version = 0;
+ };
+ })
+ expect_notification("finish")
+ notify('finish')
+ end;
+ }
+end
+
+function tests.basic_check_buffer_open_and_change()
+ skeleton {
+ on_init = function(params)
+ local expected_capabilities = protocol.make_client_capabilities()
+ assert_eq(params.capabilities, expected_capabilities)
+ return {
+ capabilities = {
+ textDocumentSync = protocol.TextDocumentSyncKind.Full;
+ }
+ }
+ end;
+ body = function()
+ notify('start')
+ expect_notification('textDocument/didOpen', {
+ textDocument = {
+ languageId = "";
+ text = table.concat({"testing"; "123"}, "\n") .. '\n';
+ uri = "file://";
+ version = 0;
+ };
+ })
+ expect_notification('textDocument/didChange', {
+ textDocument = {
+ uri = "file://";
+ version = 3;
+ };
+ contentChanges = {
+ { text = table.concat({"testing"; "boop"}, "\n") .. '\n'; };
+ }
+ })
+ expect_notification("finish")
+ notify('finish')
+ end;
+ }
+end
+
+function tests.basic_check_buffer_open_and_change_noeol()
+ skeleton {
+ on_init = function(params)
+ local expected_capabilities = protocol.make_client_capabilities()
+ assert_eq(params.capabilities, expected_capabilities)
+ return {
+ capabilities = {
+ textDocumentSync = protocol.TextDocumentSyncKind.Full;
+ }
+ }
+ end;
+ body = function()
+ notify('start')
+ expect_notification('textDocument/didOpen', {
+ textDocument = {
+ languageId = "";
+ text = table.concat({"testing"; "123"}, "\n");
+ uri = "file://";
+ version = 0;
+ };
+ })
+ expect_notification('textDocument/didChange', {
+ textDocument = {
+ uri = "file://";
+ version = 3;
+ };
+ contentChanges = {
+ { text = table.concat({"testing"; "boop"}, "\n"); };
+ }
+ })
+ expect_notification("finish")
+ notify('finish')
+ end;
+ }
+end
+function tests.basic_check_buffer_open_and_change_multi()
+ skeleton {
+ on_init = function(params)
+ local expected_capabilities = protocol.make_client_capabilities()
+ assert_eq(params.capabilities, expected_capabilities)
+ return {
+ capabilities = {
+ textDocumentSync = protocol.TextDocumentSyncKind.Full;
+ }
+ }
+ end;
+ body = function()
+ notify('start')
+ expect_notification('textDocument/didOpen', {
+ textDocument = {
+ languageId = "";
+ text = table.concat({"testing"; "123"}, "\n") .. '\n';
+ uri = "file://";
+ version = 0;
+ };
+ })
+ expect_notification('textDocument/didChange', {
+ textDocument = {
+ uri = "file://";
+ version = 3;
+ };
+ contentChanges = {
+ { text = table.concat({"testing"; "321"}, "\n") .. '\n'; };
+ }
+ })
+ expect_notification('textDocument/didChange', {
+ textDocument = {
+ uri = "file://";
+ version = 4;
+ };
+ contentChanges = {
+ { text = table.concat({"testing"; "boop"}, "\n") .. '\n'; };
+ }
+ })
+ expect_notification("finish")
+ notify('finish')
+ end;
+ }
+end
+
+function tests.basic_check_buffer_open_and_change_multi_and_close()
+ skeleton {
+ on_init = function(params)
+ local expected_capabilities = protocol.make_client_capabilities()
+ assert_eq(params.capabilities, expected_capabilities)
+ return {
+ capabilities = {
+ textDocumentSync = protocol.TextDocumentSyncKind.Full;
+ }
+ }
+ end;
+ body = function()
+ notify('start')
+ expect_notification('textDocument/didOpen', {
+ textDocument = {
+ languageId = "";
+ text = table.concat({"testing"; "123"}, "\n") .. '\n';
+ uri = "file://";
+ version = 0;
+ };
+ })
+ expect_notification('textDocument/didChange', {
+ textDocument = {
+ uri = "file://";
+ version = 3;
+ };
+ contentChanges = {
+ { text = table.concat({"testing"; "321"}, "\n") .. '\n'; };
+ }
+ })
+ expect_notification('textDocument/didChange', {
+ textDocument = {
+ uri = "file://";
+ version = 4;
+ };
+ contentChanges = {
+ { text = table.concat({"testing"; "boop"}, "\n") .. '\n'; };
+ }
+ })
+ expect_notification('textDocument/didClose', {
+ textDocument = {
+ uri = "file://";
+ };
+ })
+ expect_notification("finish")
+ notify('finish')
+ end;
+ }
+end
+
+function tests.basic_check_buffer_open_and_change_incremental()
+ skeleton {
+ on_init = function(params)
+ local expected_capabilities = protocol.make_client_capabilities()
+ assert_eq(params.capabilities, expected_capabilities)
+ return {
+ capabilities = {
+ textDocumentSync = protocol.TextDocumentSyncKind.Incremental;
+ }
+ }
+ end;
+ body = function()
+ notify('start')
+ expect_notification('textDocument/didOpen', {
+ textDocument = {
+ languageId = "";
+ text = table.concat({"testing"; "123"}, "\n") .. '\n';
+ uri = "file://";
+ version = 0;
+ };
+ })
+ expect_notification('textDocument/didChange', {
+ textDocument = {
+ uri = "file://";
+ version = 3;
+ };
+ contentChanges = {
+ {
+ range = {
+ start = { line = 1; character = 0; };
+ ["end"] = { line = 2; character = 0; };
+ };
+ rangeLength = 4;
+ text = "boop\n";
+ };
+ }
+ })
+ expect_notification("finish")
+ notify('finish')
+ end;
+ }
+end
+
+function tests.basic_check_buffer_open_and_change_incremental_editing()
+ skeleton {
+ on_init = function(params)
+ local expected_capabilities = protocol.make_client_capabilities()
+ assert_eq(params.capabilities, expected_capabilities)
+ return {
+ capabilities = {
+ textDocumentSync = protocol.TextDocumentSyncKind.Incremental;
+ }
+ }
+ end;
+ body = function()
+ notify('start')
+ expect_notification('textDocument/didOpen', {
+ textDocument = {
+ languageId = "";
+ text = table.concat({"testing"; "123"}, "\n");
+ uri = "file://";
+ version = 0;
+ };
+ })
+ expect_notification('textDocument/didChange', {
+ textDocument = {
+ uri = "file://";
+ version = 3;
+ };
+ contentChanges = {
+ {
+ range = {
+ start = { line = 0; character = 0; };
+ ["end"] = { line = 1; character = 0; };
+ };
+ rangeLength = 4;
+ text = "testing\n\n";
+ };
+ }
+ })
+ expect_notification("finish")
+ notify('finish')
+ end;
+ }
+end
+
+function tests.invalid_header()
+ io.stdout:write("Content-length: \r\n")
+end
+
+-- Tests will be indexed by TEST_NAME
+
+local kill_timer = vim.loop.new_timer()
+kill_timer:start(_G.TIMEOUT or 1e3, 0, function()
+ kill_timer:stop()
+ kill_timer:close()
+ log('ERROR', 'LSP', 'TIMEOUT')
+ io.stderr:write("TIMEOUT")
+ os.exit(100)
+end)
+
+local test_name = _G.TEST_NAME -- lualint workaround
+assert(type(test_name) == 'string', 'TEST_NAME must be specified.')
+local status, err = pcall(assert(tests[test_name], "Test not found"))
+kill_timer:stop()
+kill_timer:close()
+if not status then
+ log('ERROR', 'LSP', tostring(err))
+ io.stderr:write(err)
+ os.exit(101)
+end
+os.exit(0)
diff --git a/test/functional/fixtures/printenv-test.c b/test/functional/fixtures/printenv-test.c
index 5ac076f653..0e68129543 100644
--- a/test/functional/fixtures/printenv-test.c
+++ b/test/functional/fixtures/printenv-test.c
@@ -44,7 +44,7 @@ int main(int argc, char **argv)
utf8_len,
NULL,
NULL);
- fprintf(stderr, "%s", utf8_value);
+ fprintf(stdout, "%s", utf8_value);
free(utf8_value);
#else
char *value = getenv(argv[1]);
@@ -52,8 +52,8 @@ int main(int argc, char **argv)
fprintf(stderr, "env var not found: %s", argv[1]);
return 1;
}
- // Print to stderr to avoid buffering.
- fprintf(stderr, "%s", value);
+ fprintf(stdout, "%s", value);
#endif
+ fflush(stdout);
return 0;
}
diff --git a/test/functional/helpers.lua b/test/functional/helpers.lua
index b29161e34c..e8435cd3b7 100644
--- a/test/functional/helpers.lua
+++ b/test/functional/helpers.lua
@@ -1,4 +1,5 @@
require('coxpcall')
+local busted = require('busted')
local luv = require('luv')
local lfs = require('lfs')
local mpack = require('mpack')
@@ -14,9 +15,9 @@ local check_cores = global_helpers.check_cores
local check_logs = global_helpers.check_logs
local dedent = global_helpers.dedent
local eq = global_helpers.eq
-local filter = global_helpers.filter
+local filter = global_helpers.tbl_filter
local is_os = global_helpers.is_os
-local map = global_helpers.map
+local map = global_helpers.tbl_map
local ok = global_helpers.ok
local sleep = global_helpers.sleep
local tbl_contains = global_helpers.tbl_contains
@@ -28,17 +29,15 @@ local module = {
}
local start_dir = lfs.currentdir()
--- XXX: NVIM_PROG takes precedence, QuickBuild sets it.
module.nvim_prog = (
- os.getenv('NVIM_PROG')
- or os.getenv('NVIM_PRG')
+ os.getenv('NVIM_PRG')
or global_helpers.test_build_dir .. '/bin/nvim'
)
-- Default settings for the test session.
module.nvim_set = (
- 'set shortmess+=IS background=light noswapfile noautoindent'
+ 'set shortmess+=IS background=light noswapfile noautoindent startofline'
..' laststatus=1 undodir=. directory=. viewdir=. backupdir=.'
- ..' belloff= wildoptions-=pum noshowcmd noruler nomore')
+ ..' belloff= wildoptions-=pum noshowcmd noruler nomore redrawdebug=invalid')
module.nvim_argv = {
module.nvim_prog, '-u', 'NONE', '-i', 'NONE',
'--cmd', module.nvim_set, '--embed'}
@@ -187,7 +186,12 @@ function module.expect_msg_seq(...)
if status then
return result
end
- final_error = cat_err(final_error, result)
+ local message = result
+ if type(result) == "table" then
+ -- 'eq' returns several things
+ message = result.message
+ end
+ final_error = cat_err(final_error, message)
end
error(final_error)
end
@@ -385,7 +389,7 @@ function module.retry(max, max_ms, fn)
end
luv.update_time() -- Update cached value of luv.now() (libuv: uv_now()).
if (max and tries >= max) or (luv.now() - start_time > timeout) then
- error("\nretry() attempts: "..tostring(tries).."\n"..tostring(result))
+ busted.fail(string.format("retry() attempts: %d\n%s", tries, tostring(result)), 2)
end
tries = tries + 1
luv.sleep(20) -- Avoid hot loop...
@@ -437,6 +441,7 @@ function module.new_argv(...)
'NVIM_LOG_FILE',
'NVIM_RPLUGIN_MANIFEST',
'GCOV_ERROR_FILE',
+ 'TMPDIR',
}) do
if not env_tbl[k] then
env_tbl[k] = os.getenv(k)
@@ -499,10 +504,21 @@ function module.source(code)
return fname
end
+function module.has_powershell()
+ return module.eval('executable("'..(iswin() and 'powershell' or 'pwsh')..'")') == 1
+end
+
function module.set_shell_powershell()
+ local shell = iswin() and 'powershell' or 'pwsh'
+ assert(module.has_powershell())
+ local cmd = 'Remove-Item -Force '..table.concat(iswin()
+ and {'alias:cat', 'alias:echo', 'alias:sleep'}
+ or {'alias:echo'}, ',')..';'
module.source([[
- set shell=powershell shellquote=( shellpipe=\| shellredir=> shellxquote=
- let &shellcmdflag = '-NoLogo -NoProfile -ExecutionPolicy RemoteSigned -Command Remove-Item -Force alias:sleep; Remove-Item -Force alias:cat;'
+ let &shell = ']]..shell..[['
+ set shellquote= shellpipe=\| shellxquote=
+ let &shellredir = '| Out-File -Encoding UTF8'
+ let &shellcmdflag = '-NoLogo -NoProfile -ExecutionPolicy RemoteSigned -Command ]]..cmd..[['
]])
end
@@ -543,6 +559,11 @@ function module.wait()
session:request('nvim_eval', '1')
end
+function module.buf_lines(bufnr)
+ return module.exec_lua("return vim.api.nvim_buf_get_lines((...), 0, -1, false)", bufnr)
+end
+
+--@see buf_lines()
function module.curbuf_contents()
module.wait() -- Before inspecting the buffer, process all input.
return table.concat(module.curbuf('get_lines', 0, -1, true), '\n')
@@ -576,6 +597,19 @@ function module.assert_alive()
assert(2 == module.eval('1+1'), 'crash? request failed')
end
+-- Asserts that buffer is loaded and visible in the current tabpage.
+function module.assert_visible(bufnr, visible)
+ assert(type(visible) == 'boolean')
+ eq(visible, module.bufmeths.is_loaded(bufnr))
+ if visible then
+ assert(-1 ~= module.funcs.bufwinnr(bufnr),
+ 'expected buffer to be visible in current tabpage: '..tostring(bufnr))
+ else
+ assert(-1 == module.funcs.bufwinnr(bufnr),
+ 'expected buffer NOT visible in current tabpage: '..tostring(bufnr))
+ end
+end
+
local function do_rmdir(path)
local mode, errmsg, errcode = lfs.attributes(path, 'mode')
if mode == nil then
@@ -699,7 +733,7 @@ module.curwinmeths = module.create_callindex(module.curwin)
module.curtabmeths = module.create_callindex(module.curtab)
function module.exec_lua(code, ...)
- return module.meths.execute_lua(code, {...})
+ return module.meths.exec_lua(code, {...})
end
function module.redir_exec(cmd)
@@ -734,7 +768,7 @@ function module.new_pipename()
end
function module.missing_provider(provider)
- if provider == 'ruby' or provider == 'node' then
+ if provider == 'ruby' or provider == 'node' or provider == 'perl' then
local prog = module.funcs['provider#' .. provider .. '#Detect']()
return prog == '' and (provider .. ' not detected') or false
elseif provider == 'python' or provider == 'python3' then
@@ -760,7 +794,7 @@ function module.alter_slashes(obj)
end
return ret
else
- assert(false, 'Could only alter slashes for tables of strings and strings')
+ assert(false, 'expected string or table of strings, got '..type(obj))
end
end
diff --git a/test/functional/insert/insert_spec.lua b/test/functional/insert/insert_spec.lua
index 427954f5a6..330cfbd830 100644
--- a/test/functional/insert/insert_spec.lua
+++ b/test/functional/insert/insert_spec.lua
@@ -37,5 +37,11 @@ describe('insert-mode', function()
command('iunmap <M-l>')
feed('0i<M-l>')
eq({ 0, 1, 2, 0, }, funcs.getpos('.'))
+ -- Unmapped ALT-chord has same `undo` characteristics as ESC+<key>
+ command('0,$d')
+ feed('ahello<M-.>')
+ expect('hellohello')
+ feed('u')
+ expect('hello')
end)
end)
diff --git a/test/functional/legacy/021_control_wi_spec.lua b/test/functional/legacy/021_control_wi_spec.lua
index 87d9deed7a..94871433cd 100644
--- a/test/functional/legacy/021_control_wi_spec.lua
+++ b/test/functional/legacy/021_control_wi_spec.lua
@@ -18,7 +18,7 @@ describe('CTRL-W CTRL-I', function()
start found wrong line
test text]])
- -- Search for the second occurence of start and append to register
+ -- Search for the second occurrence of start and append to register
feed_command('/start')
feed('2[<C-i>')
feed_command('yank A')
diff --git a/test/functional/legacy/022_line_ending_spec.lua b/test/functional/legacy/022_line_ending_spec.lua
deleted file mode 100644
index fb4b782011..0000000000
--- a/test/functional/legacy/022_line_ending_spec.lua
+++ /dev/null
@@ -1,25 +0,0 @@
--- Tests for file with some lines ending in CTRL-M, some not
-
-local helpers = require('test.functional.helpers')(after_each)
-local clear, feed = helpers.clear, helpers.feed
-local feed_command, expect = helpers.feed_command, helpers.expect
-
-describe('line ending', function()
- setup(clear)
-
- it('is working', function()
- feed('i', [[
- this lines ends in a<C-V><C-M>
- this one doesn't
- this one does<C-V><C-M>
- and the last one doesn't]], '<ESC>')
-
- feed_command('set ta tx')
- feed_command('e!')
-
- expect("this lines ends in a\r\n"..
- "this one doesn't\n"..
- "this one does\r\n"..
- "and the last one doesn't")
- end)
-end)
diff --git a/test/functional/legacy/041_writing_and_reading_hundred_kbyte_spec.lua b/test/functional/legacy/041_writing_and_reading_hundred_kbyte_spec.lua
deleted file mode 100644
index b526d82519..0000000000
--- a/test/functional/legacy/041_writing_and_reading_hundred_kbyte_spec.lua
+++ /dev/null
@@ -1,43 +0,0 @@
--- Test for writing and reading a file of over 100 Kbyte
-
-local helpers = require('test.functional.helpers')(after_each)
-
-local clear, feed, insert = helpers.clear, helpers.feed, helpers.insert
-local command, expect = helpers.command, helpers.expect
-local wait = helpers.wait
-
-describe('writing and reading a file of over 100 Kbyte', function()
- setup(clear)
-
- it('is working', function()
- insert([[
- This is the start
- This is the leader
- This is the middle
- This is the trailer
- This is the end]])
-
- feed('kY3000p2GY3000p')
- wait()
-
- command('w! test.out')
- command('%d')
- command('e! test.out')
- command('yank A')
- command('3003yank A')
- command('6005yank A')
- command('%d')
- command('0put a')
- command('$d')
- command('w!')
-
- expect([[
- This is the start
- This is the middle
- This is the end]])
- end)
-
- teardown(function()
- os.remove('test.out')
- end)
-end)
diff --git a/test/functional/legacy/045_folding_spec.lua b/test/functional/legacy/045_folding_spec.lua
index 6ca1176aea..1e5239ceac 100644
--- a/test/functional/legacy/045_folding_spec.lua
+++ b/test/functional/legacy/045_folding_spec.lua
@@ -14,9 +14,6 @@ describe('folding', function()
screen = Screen.new(20, 8)
screen:attach()
end)
- after_each(function()
- screen:detach()
- end)
it('creation, opening, moving (to the end) and closing', function()
insert([[
diff --git a/test/functional/legacy/063_match_and_matchadd_spec.lua b/test/functional/legacy/063_match_and_matchadd_spec.lua
index 518d79861b..a4f5d6ac5b 100644
--- a/test/functional/legacy/063_match_and_matchadd_spec.lua
+++ b/test/functional/legacy/063_match_and_matchadd_spec.lua
@@ -14,6 +14,10 @@ describe('063: Test for ":match", "matchadd()" and related functions', function(
it('is working', function()
local screen = Screen.new(40, 5)
screen:attach()
+ screen:set_default_attr_ids({
+ [0] = {bold = true, foreground = Screen.colors.Blue},
+ [1] = {background = Screen.colors.Red},
+ })
-- Check that "matcharg()" returns the correct group and pattern if a match
-- is defined.
@@ -126,22 +130,22 @@ describe('063: Test for ":match", "matchadd()" and related functions', function(
command("call matchaddpos('MyGroup1', [[1, 5], [1, 8, 3]], 10, 3)")
screen:expect([[
abcd{1:e}fg{1:hij}klmnop^q |
- ~ |
- ~ |
- ~ |
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
|
- ]], {[1] = {background = Screen.colors.Red}}, {{bold = true, foreground = Screen.colors.Blue}})
+ ]])
command("call clearmatches()")
command("call setline(1, 'abcdΣabcdef')")
command("call matchaddpos('MyGroup1', [[1, 4, 2], [1, 9, 2]])")
screen:expect([[
abc{1:dΣ}ab{1:cd}e^f |
- ~ |
- ~ |
- ~ |
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
|
- ]],{[1] = {background = Screen.colors.Red}}, {{bold = true, foreground = Screen.colors.Blue}})
+ ]])
end)
end)
diff --git a/test/functional/legacy/075_maparg_spec.lua b/test/functional/legacy/075_maparg_spec.lua
index 0164f5077a..ee2b041b51 100644
--- a/test/functional/legacy/075_maparg_spec.lua
+++ b/test/functional/legacy/075_maparg_spec.lua
@@ -49,9 +49,9 @@ describe('maparg()', function()
-- Assert buffer contents.
expect([[
is<F4>foo
- {'lnum': 0, 'silent': 0, 'noremap': 0, 'lhs': 'foo<C-V>', 'mode': ' ', 'nowait': 0, 'expr': 0, 'sid': 0, 'rhs': 'is<F4>foo', 'buffer': 0}
- {'lnum': 0, 'silent': 1, 'noremap': 1, 'lhs': 'bar', 'mode': 'v', 'nowait': 0, 'expr': 1, 'sid': 0, 'rhs': 'isbar', 'buffer': 1}
- {'lnum': 0, 'silent': 0, 'noremap': 0, 'lhs': 'foo', 'mode': ' ', 'nowait': 1, 'expr': 0, 'sid': 0, 'rhs': 'bar', 'buffer': 1}
+ {'lnum': 0, 'script': 0, 'silent': 0, 'noremap': 0, 'lhs': 'foo<C-V>', 'mode': ' ', 'nowait': 0, 'expr': 0, 'sid': 0, 'rhs': 'is<F4>foo', 'buffer': 0}
+ {'lnum': 0, 'script': 1, 'silent': 1, 'noremap': 1, 'lhs': 'bar', 'mode': 'v', 'nowait': 0, 'expr': 1, 'sid': 0, 'rhs': 'isbar', 'buffer': 1}
+ {'lnum': 0, 'script': 0, 'silent': 0, 'noremap': 0, 'lhs': 'foo', 'mode': ' ', 'nowait': 1, 'expr': 0, 'sid': 0, 'rhs': 'bar', 'buffer': 1}
xrx
yRy
abcd]])
diff --git a/test/functional/legacy/077_mf_hash_grow_spec.lua b/test/functional/legacy/077_mf_hash_grow_spec.lua
deleted file mode 100644
index 4719a3ecbf..0000000000
--- a/test/functional/legacy/077_mf_hash_grow_spec.lua
+++ /dev/null
@@ -1,52 +0,0 @@
--- Inserts 2 million lines with consecutive integers starting from 1
--- (essentially, the output of GNU's seq 1 2000000), writes them to Xtest
--- and calculates its cksum.
--- We need 2 million lines to trigger a call to mf_hash_grow(). If it would mess
--- up the lines the checksum would differ.
--- cksum is part of POSIX and so should be available on most Unixes.
--- If it isn't available then the test will be skipped.
-
-local helpers = require('test.functional.helpers')(after_each)
-
-local feed = helpers.feed
-local wait = helpers.wait
-local clear = helpers.clear
-local expect = helpers.expect
-local command = helpers.command
-
-describe('mf_hash_grow()', function()
- setup(clear)
-
- -- Check to see if cksum exists, otherwise skip the test
- local null = helpers.iswin() and 'nul' or '/dev/null'
- if os.execute('cksum --help >' .. null .. ' 2>&1') ~= 0 then
- pending('was not tested because cksum was not found', function() end)
- else
- it('is working', function()
- command('set fileformat=unix undolevels=-1')
-
- -- Fill the buffer with numbers 1 - 2000000
- command('let i = 1')
- command('while i <= 2000000 | call append(i, range(i, i + 99)) | let i += 100 | endwhile')
-
- -- Delete empty first line, save to Xtest, and clear buffer
- feed('ggdd<cr>')
- wait()
- command('w! Xtest')
- feed('ggdG<cr>')
- wait()
-
- -- Calculate the cksum of Xtest and delete first line
- command('r !cksum Xtest')
- feed('ggdd<cr>')
-
- -- Assert correct output of cksum.
- expect([[
- 3678979763 14888896 Xtest]])
- end)
- end
-
- teardown(function()
- os.remove('Xtest')
- end)
-end)
diff --git a/test/functional/legacy/084_curswant_spec.lua b/test/functional/legacy/084_curswant_spec.lua
deleted file mode 100644
index 42cb2fc56d..0000000000
--- a/test/functional/legacy/084_curswant_spec.lua
+++ /dev/null
@@ -1,49 +0,0 @@
--- Tests for curswant not changing when setting an option.
-
-local helpers = require('test.functional.helpers')(after_each)
-local insert, source = helpers.insert, helpers.source
-local clear, expect = helpers.clear, helpers.expect
-
-describe('curswant', function()
- setup(clear)
-
- -- luacheck: ignore 621 (Indentation)
- it('is working', function()
- insert([[
- start target options
- tabstop
- timeoutlen
- ttimeoutlen
- end target options]])
-
- source([[
- /^start target options$/+1,/^end target options$/-1 yank
- let target_option_names = split(@0)
- function TestCurswant(option_name)
- normal! ggf8j
- let curswant_before = winsaveview().curswant
- execute 'let' '&'.a:option_name '=' '&'.a:option_name
- let curswant_after = winsaveview().curswant
- return [a:option_name, curswant_before, curswant_after]
- endfunction
-
- new
- put =['1234567890', '12345']
- 1 delete _
- let result = []
- for option_name in target_option_names
- call add(result, TestCurswant(option_name))
- endfor
-
- new
- put =map(copy(result), 'join(v:val, '' '')')
- 1 delete _
- ]])
-
- -- Assert buffer contents.
- expect([[
- tabstop 7 4
- timeoutlen 7 7
- ttimeoutlen 7 7]])
- end)
-end)
diff --git a/test/functional/legacy/095_regexp_multibyte_spec.lua b/test/functional/legacy/095_regexp_multibyte_spec.lua
index 845ebaaad7..fad0dc8023 100644
--- a/test/functional/legacy/095_regexp_multibyte_spec.lua
+++ b/test/functional/legacy/095_regexp_multibyte_spec.lua
@@ -1,5 +1,5 @@
-- Test for regexp patterns with multi-byte support, using utf-8.
--- See test64 for the non-multi-byte tests.
+-- See test_regexp_latin.vim for the non-multi-byte tests.
-- A pattern that gives the expected result produces OK, so that we know it was
-- actually tried.
diff --git a/test/functional/legacy/097_glob_path_spec.lua b/test/functional/legacy/097_glob_path_spec.lua
index ccd93fed60..dd5a26ad3b 100644
--- a/test/functional/legacy/097_glob_path_spec.lua
+++ b/test/functional/legacy/097_glob_path_spec.lua
@@ -52,7 +52,7 @@ describe('glob() and globpath()', function()
command([[$put =glob('Xxx\{')]])
command([[$put =glob('Xxx\$')]])
- command('silent w! Xxx{')
+ command('silent w! Xxx\\{')
command([[w! Xxx\$]])
command([[$put =glob('Xxx\{')]])
command([[$put =glob('Xxx\$')]])
diff --git a/test/functional/legacy/098_scrollbind_spec.lua b/test/functional/legacy/098_scrollbind_spec.lua
deleted file mode 100644
index d22aefdcbc..0000000000
--- a/test/functional/legacy/098_scrollbind_spec.lua
+++ /dev/null
@@ -1,48 +0,0 @@
--- Test for 'scrollbind' causing an unexpected scroll of one of the windows.
-
-local helpers = require('test.functional.helpers')(after_each)
-local source = helpers.source
-local clear, expect = helpers.clear, helpers.expect
-
-describe('scrollbind', function()
- setup(clear)
-
- it('is working', function()
- source([[
- set laststatus=0
- let g:totalLines = &lines * 20
- let middle = g:totalLines / 2
- wincmd n
- wincmd o
- for i in range(1, g:totalLines)
- call setline(i, 'LINE ' . i)
- endfor
- exe string(middle)
- normal zt
- normal M
- aboveleft vert new
- for i in range(1, g:totalLines)
- call setline(i, 'line ' . i)
- endfor
- exe string(middle)
- normal zt
- normal M
- setl scb | wincmd p
- setl scb
- wincmd w
- let topLineLeft = line('w0')
- wincmd p
- let topLineRight = line('w0')
- setl noscrollbind
- wincmd p
- setl noscrollbind
- q!
- %del _
- call setline(1, 'Difference between the top lines (left - right): ' . string(topLineLeft - topLineRight))
- brewind
- ]])
-
- -- Assert buffer contents.
- expect("Difference between the top lines (left - right): 0")
- end)
-end)
diff --git a/test/functional/legacy/104_let_assignment_spec.lua b/test/functional/legacy/104_let_assignment_spec.lua
deleted file mode 100644
index a03bb026f6..0000000000
--- a/test/functional/legacy/104_let_assignment_spec.lua
+++ /dev/null
@@ -1,54 +0,0 @@
--- Tests for :let.
-
-local helpers = require('test.functional.helpers')(after_each)
-local clear, source = helpers.clear, helpers.source
-local command, expect = helpers.command, helpers.expect
-
-describe(':let', function()
- setup(clear)
-
- it('is working', function()
- command('set runtimepath+=test/functional/fixtures')
-
- -- Test to not autoload when assigning. It causes internal error.
- source([[
- try
- let Test104#numvar = function('tr')
- $put ='OK: ' . string(Test104#numvar)
- catch
- $put ='FAIL: ' . v:exception
- endtry
- let a = 1
- let b = 2
- for letargs in ['a b', '{0 == 1 ? "a" : "b"}', '{0 == 1 ? "a" : "b"} a', 'a {0 == 1 ? "a" : "b"}']
- try
- redir => messages
- execute 'let' letargs
- redir END
- $put ='OK:'
- $put =split(substitute(messages, '\n', '\0 ', 'g'), '\n')
- catch
- $put ='FAIL: ' . v:exception
- redir END
- endtry
- endfor]])
-
- -- Remove empty line
- command('1d')
-
- -- Assert buffer contents.
- expect([[
- OK: function('tr')
- OK:
- a #1
- b #2
- OK:
- b #2
- OK:
- b #2
- a #1
- OK:
- a #1
- b #2]])
- end)
-end)
diff --git a/test/functional/legacy/assert_spec.lua b/test/functional/legacy/assert_spec.lua
index 8df2d89b70..d48b8882af 100644
--- a/test/functional/legacy/assert_spec.lua
+++ b/test/functional/legacy/assert_spec.lua
@@ -3,6 +3,7 @@ local nvim, call = helpers.meths, helpers.call
local clear, eq = helpers.clear, helpers.eq
local source, command = helpers.source, helpers.command
local exc_exec = helpers.exc_exec
+local eval = helpers.eval
local function expected_errors(errors)
eq(errors, nvim.get_vvar('errors'))
@@ -37,6 +38,9 @@ describe('assert function:', function()
call assert_equal(4, n)
let l = [1, 2, 3]
call assert_equal([1, 2, 3], l)
+ call assert_equal(v:_null_list, v:_null_list)
+ call assert_equal(v:_null_list, [])
+ call assert_equal([], v:_null_list)
fu Func()
endfu
let F1 = function('Func')
@@ -47,18 +51,18 @@ describe('assert function:', function()
end)
it('should not change v:errors when expected is equal to actual', function()
- call('assert_equal', '', '')
- call('assert_equal', 'string', 'string')
+ eq(0, call('assert_equal', '', ''))
+ eq(0, call('assert_equal', 'string', 'string'))
expected_empty()
end)
it('should change v:errors when expected is not equal to actual', function()
- call('assert_equal', 0, {0})
+ eq(1, call('assert_equal', 0, {0}))
expected_errors({'Expected 0 but got [0]'})
end)
it('should change v:errors when expected is not equal to actual', function()
- call('assert_equal', 0, "0")
+ eq(1, call('assert_equal', 0, "0"))
expected_errors({"Expected 0 but got '0'"})
end)
@@ -91,18 +95,23 @@ describe('assert function:', function()
call('assert_equal', 'foo', 'bar', 'testing')
expected_errors({"testing: Expected 'foo' but got 'bar'"})
end)
+
+ it('should shorten a long message', function()
+ call ('assert_equal', 'XxxxxxxxxxxxxxxxxxxxxxX', 'XyyyyyyyyyyyyyyyyyyyyyyyyyX')
+ expected_errors({"Expected 'X\\[x occurs 21 times]X' but got 'X\\[y occurs 25 times]X'"})
+ end)
end)
-- assert_notequal({expected}, {actual}[, {msg}])
describe('assert_notequal', function()
it('should not change v:errors when expected differs from actual', function()
- call('assert_notequal', 'foo', 4)
- call('assert_notequal', {1, 2, 3}, 'foo')
+ eq(0, call('assert_notequal', 'foo', 4))
+ eq(0, call('assert_notequal', {1, 2, 3}, 'foo'))
expected_empty()
end)
it('should change v:errors when expected is equal to actual', function()
- call('assert_notequal', 'foo', 'foo')
+ eq(1, call('assert_notequal', 'foo', 'foo'))
expected_errors({"Expected not equal to 'foo'"})
end)
end)
@@ -110,13 +119,13 @@ describe('assert function:', function()
-- assert_false({actual}, [, {msg}])
describe('assert_false', function()
it('should not change v:errors when actual is false', function()
- call('assert_false', 0)
- call('assert_false', false)
+ eq(0, call('assert_false', 0))
+ eq(0, call('assert_false', false))
expected_empty()
end)
it('should change v:errors when actual is not false', function()
- call('assert_false', 1)
+ eq(1, call('assert_false', 1))
expected_errors({'Expected False but got 1'})
end)
@@ -129,14 +138,14 @@ describe('assert function:', function()
-- assert_true({actual}, [, {msg}])
describe('assert_true', function()
it('should not change v:errors when actual is true', function()
- call('assert_true', 1)
- call('assert_true', -1) -- In Vim script, non-zero Numbers are TRUE.
- call('assert_true', true)
+ eq(0, call('assert_true', 1))
+ eq(0, call('assert_true', -1)) -- In Vim script, non-zero Numbers are TRUE.
+ eq(0, call('assert_true', true))
expected_empty()
end)
it('should change v:errors when actual is not true', function()
- call('assert_true', 1.5)
+ eq(1, call('assert_true', 1.5))
expected_errors({'Expected True but got 1.5'})
end)
end)
@@ -233,20 +242,31 @@ describe('assert function:', function()
-- assert_fails({cmd}, [, {error}])
describe('assert_fails', function()
it('should change v:errors when error does not match v:errmsg', function()
- command([[call assert_fails('xxx', {})]])
+ eq(1, eval([[assert_fails('xxx', {})]]))
command([[call assert_match("Expected {} but got 'E731:", v:errors[0])]])
expected_errors({"Expected {} but got 'E731: using Dictionary as a String'"})
end)
it('should not change v:errors when cmd errors', function()
- call('assert_fails', 'NonexistentCmd')
+ eq(0, eval([[assert_fails('NonexistentCmd')]]))
expected_empty()
end)
it('should change v:errors when cmd succeeds', function()
- call('assert_fails', 'call empty("")')
+ eq(1, eval([[assert_fails('call empty("")', '')]]))
expected_errors({'command did not fail: call empty("")'})
end)
+
+ it('can specify and get a message about what failed', function()
+ eq(1, eval([[assert_fails('xxx', {}, 'stupid')]]))
+ command([[call assert_match("stupid: Expected {} but got 'E731:", v:errors[0])]])
+ expected_errors({"stupid: Expected {} but got 'E731: using Dictionary as a String'"})
+ end)
+
+ it('can specify and get a message even when cmd succeeds', function()
+ eq(1, eval([[assert_fails('echo', '', 'echo command')]]))
+ expected_errors({'command did not fail: echo command'})
+ end)
end)
-- assert_inrange({lower}, {upper}, {actual}[, {msg}])
@@ -277,7 +297,7 @@ describe('assert function:', function()
-- assert_report({msg})
describe('assert_report()', function()
it('should add a message to v:errors', function()
- command("call assert_report('something is wrong')")
+ eq(1, call('assert_report', 'something is wrong'))
command("call assert_match('something is wrong', v:errors[0])")
command('call remove(v:errors, 0)')
expected_empty()
@@ -291,7 +311,7 @@ describe('assert function:', function()
try
nocommand
catch
- call assert_exception('E492')
+ call assert_equal(0, assert_exception('E492'))
endtry
]])
expected_empty()
@@ -304,9 +324,9 @@ describe('assert function:', function()
catch
try
" illegal argument, get NULL for error
- call assert_exception([])
+ call assert_equal(1, assert_exception([]))
catch
- call assert_exception('E730')
+ call assert_equal(0, assert_exception('E730'))
endtry
endtry
]])
diff --git a/test/functional/legacy/breakindent_spec.lua b/test/functional/legacy/breakindent_spec.lua
deleted file mode 100644
index fd25e809e0..0000000000
--- a/test/functional/legacy/breakindent_spec.lua
+++ /dev/null
@@ -1,214 +0,0 @@
--- Test for breakindent
-
-local helpers = require('test.functional.helpers')(after_each)
-local feed, insert = helpers.feed, helpers.insert
-local clear, feed_command, expect = helpers.clear, helpers.feed_command, helpers.expect
-
-describe('breakindent', function()
- setup(clear)
-
- -- luacheck: ignore 621 (Indentation)
- -- luacheck: ignore 613 (Trailing whitespace in a string)
- -- luacheck: ignore 611 (Line contains only whitespaces)
- it('is working', function()
- insert('dummy text')
-
- feed_command('set wildchar=^E')
- feed_command('10new')
- feed_command('vsp')
- feed_command('vert resize 20')
- feed_command([[put =\"\tabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOP\"]])
- feed_command('set ts=4 sw=4 sts=4 breakindent')
- feed_command('fu! ScreenChar(line, width)')
- feed_command(' let c=""')
- feed_command(' for i in range(1,a:width)')
- feed_command(' let c.=nr2char(screenchar(a:line, i))')
- feed_command(' endfor')
- feed_command([[ let c.="\n"]])
- feed_command(' for i in range(1,a:width)')
- feed_command(' let c.=nr2char(screenchar(a:line+1, i))')
- feed_command(' endfor')
- feed_command([[ let c.="\n"]])
- feed_command(' for i in range(1,a:width)')
- feed_command(' let c.=nr2char(screenchar(a:line+2, i))')
- feed_command(' endfor')
- feed_command(' return c')
- feed_command('endfu')
- feed_command('fu DoRecordScreen()')
- feed_command(' wincmd l')
- feed_command([[ $put =printf(\"\n%s\", g:test)]])
- feed_command(' $put =g:line1')
- feed_command(' wincmd p')
- feed_command('endfu')
- feed_command('set briopt=min:0')
- feed_command('let g:test="Test 1: Simple breakindent"')
- feed_command('let line1=ScreenChar(line("."),8)')
- feed_command('call DoRecordScreen()')
- feed_command('let g:test="Test 2: Simple breakindent + sbr=>>"')
- feed_command('set sbr=>>')
- feed_command('let line1=ScreenChar(line("."),8)')
- feed_command('call DoRecordScreen()')
- feed_command('let g:test ="Test 3: Simple breakindent + briopt:sbr"')
- feed_command('set briopt=sbr,min:0 sbr=++')
- feed_command('let line1=ScreenChar(line("."),8)')
- feed_command('call DoRecordScreen()')
- feed_command('let g:test ="Test 4: Simple breakindent + min width: 18"')
- feed_command('set sbr= briopt=min:18')
- feed_command('let line1=ScreenChar(line("."),8)')
- feed_command('call DoRecordScreen()')
- feed_command('let g:test =" Test 5: Simple breakindent + shift by 2"')
- feed_command('set briopt=shift:2,min:0')
- feed_command('let line1=ScreenChar(line("."),8)')
- feed_command('call DoRecordScreen()')
- feed_command('let g:test=" Test 6: Simple breakindent + shift by -1"')
- feed_command('set briopt=shift:-1,min:0')
- feed_command('let line1=ScreenChar(line("."),8)')
- feed_command('call DoRecordScreen()')
- feed_command('let g:test=" Test 7: breakindent + shift by +1 + nu + sbr=? briopt:sbr"')
- feed_command('set briopt=shift:1,sbr,min:0 nu sbr=? nuw=4')
- feed_command('let line1=ScreenChar(line("."),10)')
- feed_command('call DoRecordScreen()')
- feed_command('let g:test=" Test 8: breakindent + shift:1 + nu + sbr=# list briopt:sbr"')
- feed_command('set briopt=shift:1,sbr,min:0 nu sbr=# list lcs&vi')
- feed_command('let line1=ScreenChar(line("."),10)')
- feed_command('call DoRecordScreen()')
- feed_command([[let g:test=" Test 9: breakindent + shift by +1 + 'nu' + sbr=# list"]])
- feed_command('set briopt-=sbr')
- feed_command('let line1=ScreenChar(line("."),10)')
- feed_command('call DoRecordScreen()')
- feed_command([[let g:test=" Test 10: breakindent + shift by +1 + 'nu' + sbr=~ cpo+=n"]])
- feed_command('set cpo+=n sbr=~ nu nuw=4 nolist briopt=sbr,min:0')
- feed_command('let line1=ScreenChar(line("."),10)')
- feed_command('call DoRecordScreen()')
- feed_command('wincmd p')
- feed_command([[let g:test="\n Test 11: strdisplaywidth when breakindent is on"]])
- feed_command('set cpo-=n sbr=>> nu nuw=4 nolist briopt= ts=4')
- -- Skip leading tab when calculating text width.
- feed_command('let text=getline(2)')
- -- Text wraps 3 times.
- feed_command('let width = strlen(text[1:])+indent(2)*4+strlen(&sbr)*3')
- feed_command('$put =g:test')
- feed_command([[$put =printf(\"strdisplaywidth: %d == calculated: %d\", strdisplaywidth(text), width)]])
- feed_command([[let g:str="\t\t\t\t\t{"]])
- feed_command('let g:test=" Test 12: breakindent + long indent"')
- feed_command('wincmd p')
- feed_command('set all& breakindent linebreak briopt=min:10 nu numberwidth=3 ts=4')
- feed_command('$put =g:str')
- feed('zt')
- feed_command('let line1=ScreenChar(1,10)')
- feed_command('wincmd p')
- feed_command('call DoRecordScreen()')
-
- -- Test, that the string " a\tb\tc\td\te" is correctly displayed in a
- -- 20 column wide window (see bug report
- -- https://groups.google.com/d/msg/vim_dev/ZOdg2mc9c9Y/TT8EhFjEy0IJ ).
- feed_command('only')
- feed_command('vert 20new')
- feed_command('set all& breakindent briopt=min:10')
- feed_command([[call setline(1, [" a\tb\tc\td\te", " z y x w v"])]])
- feed_command([[/^\s*a]])
- feed('fbgjyl')
- feed_command('let line1 = @0')
- feed_command([[?^\s*z]])
- feed('fygjyl')
- feed_command('let line2 = @0')
- feed_command('quit!')
- feed_command([[$put ='Test 13: breakindent with wrapping Tab']])
- feed_command('$put =line1')
- feed_command('$put =line2')
-
- feed_command('let g:test="Test 14: breakindent + visual blockwise delete #1"')
- feed_command('set all& breakindent shada+=nX-test-breakindent.shada')
- feed_command('30vnew')
- feed_command('normal! 3a1234567890')
- feed_command('normal! a abcde')
- feed_command([[exec "normal! 0\<C-V>tex"]])
- feed_command('let line1=ScreenChar(line("."),8)')
- feed_command('call DoRecordScreen()')
-
- feed_command('let g:test="Test 15: breakindent + visual blockwise delete #2"')
- feed_command('%d')
- feed_command('normal! 4a1234567890')
- feed_command([[exec "normal! >>\<C-V>3f0x"]])
- feed_command('let line1=ScreenChar(line("."),20)')
- feed_command('call DoRecordScreen()')
- feed_command('quit!')
-
- -- Assert buffer contents.
- expect([[
-
- abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOP
-
- Test 1: Simple breakindent
- abcd
- qrst
- GHIJ
-
- Test 2: Simple breakindent + sbr=>>
- abcd
- >>qr
- >>EF
-
- Test 3: Simple breakindent + briopt:sbr
- abcd
- ++ qrst
- ++ GHIJ
-
- Test 4: Simple breakindent + min width: 18
- abcd
- qrstuv
- IJKLMN
-
- Test 5: Simple breakindent + shift by 2
- abcd
- qr
- EF
-
- Test 6: Simple breakindent + shift by -1
- abcd
- qrstu
- HIJKL
-
- Test 7: breakindent + shift by +1 + nu + sbr=? briopt:sbr
- 2 ab
- ? m
- ? x
-
- Test 8: breakindent + shift:1 + nu + sbr=# list briopt:sbr
- 2 ^Iabcd
- # opq
- # BCD
-
- Test 9: breakindent + shift by +1 + 'nu' + sbr=# list
- 2 ^Iabcd
- #op
- #AB
-
- Test 10: breakindent + shift by +1 + 'nu' + sbr=~ cpo+=n
- 2 ab
- ~ mn
- ~ yz
-
- Test 11: strdisplaywidth when breakindent is on
- strdisplaywidth: 46 == calculated: 64
- {
-
- Test 12: breakindent + long indent
- 56
-
- ~
- Test 13: breakindent with wrapping Tab
- d
- w
-
- Test 14: breakindent + visual blockwise delete #1
- e
- ~
- ~
-
- Test 15: breakindent + visual blockwise delete #2
- 1234567890
- ~
- ~ ]])
- end)
-end)
diff --git a/test/functional/legacy/delete_spec.lua b/test/functional/legacy/delete_spec.lua
index 9ea3269828..f2ced8942d 100644
--- a/test/functional/legacy/delete_spec.lua
+++ b/test/functional/legacy/delete_spec.lua
@@ -56,7 +56,7 @@ describe('Test for delete()', function()
endif
]])
if eval('v:shell_error') ~= 0 then
- pending('Cannot create symlink', function()end)
+ pending('Cannot create symlink')
end
-- Delete the link, not the file
eq(0, eval("delete('Xlink')"))
diff --git a/test/functional/legacy/eval_spec.lua b/test/functional/legacy/eval_spec.lua
index c5d38d6d05..4198ea8bfe 100644
--- a/test/functional/legacy/eval_spec.lua
+++ b/test/functional/legacy/eval_spec.lua
@@ -360,7 +360,7 @@ describe('eval', function()
abcD3b]])
-- From now on we delete the buffer contents after each expect() to make
- -- the next expect() easier to write. This is neccessary because null
+ -- the next expect() easier to write. This is necessary because null
-- bytes on a line by itself don't play well together with the dedent
-- function used in expect().
command('%delete')
@@ -416,7 +416,7 @@ describe('eval', function()
' abcD3b50')
end)
- -- The tests for setting lists with NLs are split into seperate it() blocks
+ -- The tests for setting lists with NLs are split into separate it() blocks
-- to make the expect() calls easier to write. Otherwise the null byte can
-- make trouble on a line on its own.
it('setting lists with NLs with setreg(), part 1', function()
diff --git a/test/functional/legacy/expand_spec.lua b/test/functional/legacy/expand_spec.lua
index 1b735080f4..f238128b31 100644
--- a/test/functional/legacy/expand_spec.lua
+++ b/test/functional/legacy/expand_spec.lua
@@ -70,6 +70,40 @@ describe('expand file name', function()
call assert_match('\~', expand('%:p'))
bwipe!
endfunc
+
+ func Test_expandcmd()
+ let $FOO = 'Test'
+ call assert_equal('e x/Test/y', expandcmd('e x/$FOO/y'))
+ unlet $FOO
+
+ new
+ edit Xfile1
+ call assert_equal('e Xfile1', expandcmd('e %'))
+ edit Xfile2
+ edit Xfile1
+ call assert_equal('e Xfile2', expandcmd('e #'))
+ edit Xfile2
+ edit Xfile3
+ edit Xfile4
+ let bnum = bufnr('Xfile2')
+ call assert_equal('e Xfile2', expandcmd('e #' . bnum))
+ call setline('.', 'Vim!@#')
+ call assert_equal('e Vim', expandcmd('e <cword>'))
+ call assert_equal('e Vim!@#', expandcmd('e <cWORD>'))
+ enew!
+ edit Xfile.java
+ call assert_equal('e Xfile.py', expandcmd('e %:r.py'))
+ call assert_equal('make abc.java', expandcmd('make abc.%:e'))
+ call assert_equal('make Xabc.java', expandcmd('make %:s?file?abc?'))
+ edit a1a2a3.rb
+ call assert_equal('make b1b2b3.rb a1a2a3 Xfile.o', expandcmd('make %:gs?a?b? %< #<.o'))
+
+ call assert_fails('call expandcmd("make <afile>")', 'E495:')
+ call assert_fails('call expandcmd("make <afile>")', 'E495:')
+ enew
+ call assert_fails('call expandcmd("make %")', 'E499:')
+ close
+ endfunc
]])
end)
@@ -87,4 +121,9 @@ describe('expand file name', function()
call('Test_expand_tilde_filename')
expected_empty()
end)
+
+ it('works with expandcmd()', function()
+ call('Test_expandcmd')
+ expected_empty()
+ end)
end)
diff --git a/test/functional/legacy/memory_usage_spec.lua b/test/functional/legacy/memory_usage_spec.lua
new file mode 100644
index 0000000000..251e6a5ea4
--- /dev/null
+++ b/test/functional/legacy/memory_usage_spec.lua
@@ -0,0 +1,169 @@
+local helpers = require('test.functional.helpers')(after_each)
+local clear = helpers.clear
+local eval = helpers.eval
+local eq = helpers.eq
+local feed_command = helpers.feed_command
+local iswin = helpers.iswin
+local retry = helpers.retry
+local ok = helpers.ok
+local source = helpers.source
+local wait = helpers.wait
+local uname = helpers.uname
+local load_adjust = helpers.load_adjust
+
+local monitor_memory_usage = {
+ memory_usage = function(self)
+ local handle
+ if iswin() then
+ handle = io.popen('wmic process where processid=' ..self.pid..' get WorkingSetSize')
+ else
+ handle = io.popen('ps -o rss= -p '..self.pid)
+ end
+ return tonumber(handle:read('*a'):match('%d+'))
+ end,
+ op = function(self)
+ retry(nil, 10000, function()
+ local val = self.memory_usage(self)
+ if self.max < val then
+ self.max = val
+ end
+ table.insert(self.hist, val)
+ ok(#self.hist > 20)
+ local result = {}
+ for key,value in ipairs(self.hist) do
+ if value ~= self.hist[key + 1] then
+ table.insert(result, value)
+ end
+ end
+ table.remove(self.hist, 1)
+ self.last = self.hist[#self.hist]
+ eq(#result, 1)
+ end)
+ end,
+ dump = function(self)
+ return 'max: '..self.max ..', last: '..self.last
+ end,
+ monitor_memory_usage = function(self, pid)
+ local obj = {
+ pid = pid,
+ max = 0,
+ last = 0,
+ hist = {},
+ }
+ setmetatable(obj, { __index = self })
+ obj:op()
+ return obj
+ end
+}
+setmetatable(monitor_memory_usage,
+{__call = function(self, pid)
+ return monitor_memory_usage.monitor_memory_usage(self, pid)
+end})
+
+describe('memory usage', function()
+ local function check_result(tbl, status, result)
+ if not status then
+ print('')
+ for key, val in pairs(tbl) do
+ print(key, val:dump())
+ end
+ error(result)
+ end
+ end
+
+ local function isasan()
+ local version = eval('execute("version")')
+ return version:match('-fsanitize=[a-z,]*address')
+ end
+
+ before_each(clear)
+
+ --[[
+ Case: if a local variable captures a:000, funccall object will be free
+ just after it finishes.
+ ]]--
+ it('function capture vargs', function()
+ if isasan() then
+ pending('ASAN build is difficult to estimate memory usage')
+ end
+ if iswin() and eval("executable('wmic')") == 0 then
+ pending('missing "wmic" command')
+ elseif eval("executable('ps')") == 0 then
+ pending('missing "ps" command')
+ end
+
+ local pid = eval('getpid()')
+ local before = monitor_memory_usage(pid)
+ source([[
+ func s:f(...)
+ let x = a:000
+ endfunc
+ for _ in range(10000)
+ call s:f(0)
+ endfor
+ ]])
+ wait()
+ local after = monitor_memory_usage(pid)
+ -- Estimate the limit of max usage as 2x initial usage.
+ -- The lower limit can fluctuate a bit, use 97%.
+ check_result({before=before, after=after},
+ pcall(ok, before.last * 97 / 100 < after.max))
+ check_result({before=before, after=after},
+ pcall(ok, before.last * 2 > after.max))
+ -- In this case, garbage collecting is not needed.
+ -- The value might fluctuate a bit, allow for 3% tolerance below and 5% above.
+ -- Based on various test runs.
+ local lower = after.last * 97 / 100
+ local upper = after.last * 105 / 100
+ check_result({before=before, after=after}, pcall(ok, lower < after.max))
+ check_result({before=before, after=after}, pcall(ok, after.max < upper))
+ end)
+
+ --[[
+ Case: if a local variable captures l: dict, funccall object will not be
+ free until garbage collector runs, but after that memory usage doesn't
+ increase so much even when rerun Xtest.vim since system memory caches.
+ ]]--
+ it('function capture lvars', function()
+ if isasan() then
+ pending('ASAN build is difficult to estimate memory usage')
+ end
+ if iswin() and eval("executable('wmic')") == 0 then
+ pending('missing "wmic" command')
+ elseif eval("executable('ps')") == 0 then
+ pending('missing "ps" command')
+ end
+
+ local pid = eval('getpid()')
+ local before = monitor_memory_usage(pid)
+ local fname = source([[
+ if !exists('s:defined_func')
+ func s:f()
+ let x = l:
+ endfunc
+ endif
+ let s:defined_func = 1
+ for _ in range(10000)
+ call s:f()
+ endfor
+ ]])
+ wait()
+ local after = monitor_memory_usage(pid)
+ for _ = 1, 3 do
+ feed_command('so '..fname)
+ wait()
+ end
+ local last = monitor_memory_usage(pid)
+ -- The usage may be a bit less than the last value, use 80%.
+ -- Allow for 20% tolerance at the upper limit. That's very permissive, but
+ -- otherwise the test fails sometimes. On Sourcehut CI with FreeBSD we need to
+ -- be even more permissive.
+ local upper_multiplier = uname() == 'freebsd' and 15 or 12
+ local lower = before.last * 8 / 10
+ local upper = load_adjust((after.max + (after.last - before.last)) * upper_multiplier / 10)
+ check_result({before=before, after=after, last=last},
+ pcall(ok, lower < last.last))
+ check_result({before=before, after=after, last=last},
+ pcall(ok, last.last < upper))
+ end)
+end)
diff --git a/test/functional/legacy/prompt_buffer_spec.lua b/test/functional/legacy/prompt_buffer_spec.lua
new file mode 100644
index 0000000000..513be807be
--- /dev/null
+++ b/test/functional/legacy/prompt_buffer_spec.lua
@@ -0,0 +1,153 @@
+local helpers = require('test.functional.helpers')(after_each)
+local Screen = require('test.functional.ui.screen')
+local feed= helpers.feed
+local source = helpers.source
+local clear = helpers.clear
+local feed_command = helpers.feed_command
+
+describe('prompt buffer', function()
+ local screen
+
+ before_each(function()
+ clear()
+ screen = Screen.new(25, 10)
+ screen:attach()
+ source([[
+ func TextEntered(text)
+ if a:text == "exit"
+ set nomodified
+ stopinsert
+ close
+ else
+ call append(line("$") - 1, 'Command: "' . a:text . '"')
+ set nomodfied
+ call timer_start(20, {id -> TimerFunc(a:text)})
+ endif
+ endfunc
+
+ func TimerFunc(text)
+ call append(line("$") - 1, 'Result: "' . a:text .'"')
+ endfunc
+ ]])
+ feed_command("set noshowmode | set laststatus=0")
+ feed_command("call setline(1, 'other buffer')")
+ feed_command("new")
+ feed_command("set buftype=prompt")
+ feed_command("call prompt_setcallback(bufnr(''), function('TextEntered'))")
+ end)
+
+ after_each(function()
+ screen:detach()
+ end)
+
+ it('works', function()
+ screen:expect([[
+ ^ |
+ ~ |
+ ~ |
+ ~ |
+ [Prompt] |
+ other buffer |
+ ~ |
+ ~ |
+ ~ |
+ |
+ ]])
+ feed("i")
+ feed("hello\n")
+ screen:expect([[
+ % hello |
+ Command: "hello" |
+ Result: "hello" |
+ % ^ |
+ [Prompt] [+] |
+ other buffer |
+ ~ |
+ ~ |
+ ~ |
+ |
+ ]])
+ feed("exit\n")
+ screen:expect([[
+ ^other buffer |
+ ~ |
+ ~ |
+ ~ |
+ ~ |
+ ~ |
+ ~ |
+ ~ |
+ ~ |
+ |
+ ]])
+ end)
+
+ it('editing', function()
+ screen:expect([[
+ ^ |
+ ~ |
+ ~ |
+ ~ |
+ [Prompt] |
+ other buffer |
+ ~ |
+ ~ |
+ ~ |
+ |
+ ]])
+ feed("i")
+ feed("hello<BS><BS>")
+ screen:expect([[
+ % hel^ |
+ ~ |
+ ~ |
+ ~ |
+ [Prompt] [+] |
+ other buffer |
+ ~ |
+ ~ |
+ ~ |
+ |
+ ]])
+ feed("<Left><Left><Left><BS>-")
+ screen:expect([[
+ % -^hel |
+ ~ |
+ ~ |
+ ~ |
+ [Prompt] [+] |
+ other buffer |
+ ~ |
+ ~ |
+ ~ |
+ |
+ ]])
+ feed("<End>x")
+ screen:expect([[
+ % -helx^ |
+ ~ |
+ ~ |
+ ~ |
+ [Prompt] [+] |
+ other buffer |
+ ~ |
+ ~ |
+ ~ |
+ |
+ ]])
+ feed("<C-U>exit\n")
+ screen:expect([[
+ ^other buffer |
+ ~ |
+ ~ |
+ ~ |
+ ~ |
+ ~ |
+ ~ |
+ ~ |
+ ~ |
+ |
+ ]])
+ end)
+
+end)
diff --git a/test/functional/legacy/search_spec.lua b/test/functional/legacy/search_spec.lua
index 3ed06a22e7..a207b176d3 100644
--- a/test/functional/legacy/search_spec.lua
+++ b/test/functional/legacy/search_spec.lua
@@ -17,7 +17,10 @@ describe('search cmdline', function()
screen = Screen.new(20, 3)
screen:attach()
screen:set_default_attr_ids({
- inc = {reverse = true}
+ inc = {reverse = true},
+ err = { foreground = Screen.colors.Grey100, background = Screen.colors.Red },
+ more = { bold = true, foreground = Screen.colors.SeaGreen4 },
+ tilde = { bold = true, foreground = Screen.colors.Blue1 },
})
end)
@@ -404,15 +407,7 @@ describe('search cmdline', function()
end)
it('keeps the view after deleting a char from the search', function()
- screen:detach()
- screen = Screen.new(20, 6)
- screen:attach()
- screen:set_default_attr_ids({
- inc = {reverse = true}
- })
- screen:set_default_attr_ignore({
- {bold=true, reverse=true}, {bold=true, foreground=Screen.colors.Blue1}
- })
+ screen:try_resize(20, 6)
tenlines()
feed('/foo')
@@ -448,14 +443,7 @@ describe('search cmdline', function()
end)
it('restores original view after failed search', function()
- screen:detach()
- screen = Screen.new(40, 3)
- screen:attach()
- screen:set_default_attr_ids({
- inc = {reverse = true},
- err = { foreground = Screen.colors.Grey100, background = Screen.colors.Red },
- more = { bold = true, foreground = Screen.colors.SeaGreen4 },
- })
+ screen:try_resize(40, 3)
tenlines()
feed('0')
feed('/foo')
@@ -484,15 +472,7 @@ describe('search cmdline', function()
it("CTRL-G with 'incsearch' and ? goes in the right direction", function()
-- oldtest: Test_search_cmdline4().
- screen:detach()
- screen = Screen.new(40, 4)
- screen:attach()
- screen:set_default_attr_ids({
- inc = {reverse = true},
- err = { foreground = Screen.colors.Grey100, background = Screen.colors.Red },
- more = { bold = true, foreground = Screen.colors.SeaGreen4 },
- tilde = { bold = true, foreground = Screen.colors.Blue1 },
- })
+ screen:try_resize(40, 4)
command('enew!')
funcs.setline(1, {' 1 the first', ' 2 the second', ' 3 the third'})
command('set laststatus=0 shortmess+=s')
diff --git a/test/functional/lua/api_spec.lua b/test/functional/lua/api_spec.lua
index b1dc5c07fd..896554f7a3 100644
--- a/test/functional/lua/api_spec.lua
+++ b/test/functional/lua/api_spec.lua
@@ -39,7 +39,7 @@ describe('luaeval(vim.api.…)', function()
eq({false, 'Argument "pos" must be a [row, col] array'},
funcs.luaeval('{pcall(vim.api.nvim_win_set_cursor, 0, {1, 2, 3})}'))
-- Used to produce a memory leak due to a bug in nvim_win_set_cursor
- eq({false, 'Invalid window id'},
+ eq({false, 'Invalid window id: -1'},
funcs.luaeval('{pcall(vim.api.nvim_win_set_cursor, -1, {1, 2, 3})}'))
end)
@@ -155,41 +155,41 @@ describe('luaeval(vim.api.…)', function()
it('errors out correctly when working with API', function()
-- Conversion errors
- eq('Vim(call):E5108: Error while calling lua chunk for luaeval(): [string "<VimL compiled string>"]:1: Cannot convert given lua type',
+ eq('Vim(call):E5108: Error executing lua [string "luaeval()"]:1: Cannot convert given lua type',
exc_exec([[call luaeval("vim.api.nvim__id(vim.api.nvim__id)")]]))
- eq('Vim(call):E5108: Error while calling lua chunk for luaeval(): [string "<VimL compiled string>"]:1: Cannot convert given lua table',
+ eq('Vim(call):E5108: Error executing lua [string "luaeval()"]:1: Cannot convert given lua table',
exc_exec([[call luaeval("vim.api.nvim__id({1, foo=42})")]]))
- eq('Vim(call):E5108: Error while calling lua chunk for luaeval(): [string "<VimL compiled string>"]:1: Cannot convert given lua type',
+ eq('Vim(call):E5108: Error executing lua [string "luaeval()"]:1: Cannot convert given lua type',
exc_exec([[call luaeval("vim.api.nvim__id({42, vim.api.nvim__id})")]]))
-- Errors in number of arguments
- eq('Vim(call):E5108: Error while calling lua chunk for luaeval(): [string "<VimL compiled string>"]:1: Expected 1 argument',
+ eq('Vim(call):E5108: Error executing lua [string "luaeval()"]:1: Expected 1 argument',
exc_exec([[call luaeval("vim.api.nvim__id()")]]))
- eq('Vim(call):E5108: Error while calling lua chunk for luaeval(): [string "<VimL compiled string>"]:1: Expected 1 argument',
+ eq('Vim(call):E5108: Error executing lua [string "luaeval()"]:1: Expected 1 argument',
exc_exec([[call luaeval("vim.api.nvim__id(1, 2)")]]))
- eq('Vim(call):E5108: Error while calling lua chunk for luaeval(): [string "<VimL compiled string>"]:1: Expected 2 arguments',
+ eq('Vim(call):E5108: Error executing lua [string "luaeval()"]:1: Expected 2 arguments',
exc_exec([[call luaeval("vim.api.nvim_set_var(1, 2, 3)")]]))
-- Error in argument types
- eq('Vim(call):E5108: Error while calling lua chunk for luaeval(): [string "<VimL compiled string>"]:1: Expected lua string',
+ eq('Vim(call):E5108: Error executing lua [string "luaeval()"]:1: Expected lua string',
exc_exec([[call luaeval("vim.api.nvim_set_var(1, 2)")]]))
- eq('Vim(call):E5108: Error while calling lua chunk for luaeval(): [string "<VimL compiled string>"]:1: Expected lua number',
+ eq('Vim(call):E5108: Error executing lua [string "luaeval()"]:1: Expected lua number',
exc_exec([[call luaeval("vim.api.nvim_buf_get_lines(0, 'test', 1, false)")]]))
- eq('Vim(call):E5108: Error while calling lua chunk for luaeval(): [string "<VimL compiled string>"]:1: Number is not integral',
+ eq('Vim(call):E5108: Error executing lua [string "luaeval()"]:1: Number is not integral',
exc_exec([[call luaeval("vim.api.nvim_buf_get_lines(0, 1.5, 1, false)")]]))
- eq('Vim(call):E5108: Error while calling lua chunk for luaeval(): [string "<VimL compiled string>"]:1: Expected lua table',
+ eq('Vim(call):E5108: Error executing lua [string "luaeval()"]:1: Expected lua table',
exc_exec([[call luaeval("vim.api.nvim__id_float('test')")]]))
- eq('Vim(call):E5108: Error while calling lua chunk for luaeval(): [string "<VimL compiled string>"]:1: Unexpected type',
+ eq('Vim(call):E5108: Error executing lua [string "luaeval()"]:1: Unexpected type',
exc_exec([[call luaeval("vim.api.nvim__id_float({[vim.type_idx]=vim.types.dictionary})")]]))
- eq('Vim(call):E5108: Error while calling lua chunk for luaeval(): [string "<VimL compiled string>"]:1: Expected lua table',
+ eq('Vim(call):E5108: Error executing lua [string "luaeval()"]:1: Expected lua table',
exc_exec([[call luaeval("vim.api.nvim__id_array(1)")]]))
- eq('Vim(call):E5108: Error while calling lua chunk for luaeval(): [string "<VimL compiled string>"]:1: Unexpected type',
+ eq('Vim(call):E5108: Error executing lua [string "luaeval()"]:1: Unexpected type',
exc_exec([[call luaeval("vim.api.nvim__id_array({[vim.type_idx]=vim.types.dictionary})")]]))
- eq('Vim(call):E5108: Error while calling lua chunk for luaeval(): [string "<VimL compiled string>"]:1: Expected lua table',
+ eq('Vim(call):E5108: Error executing lua [string "luaeval()"]:1: Expected lua table',
exc_exec([[call luaeval("vim.api.nvim__id_dictionary(1)")]]))
- eq('Vim(call):E5108: Error while calling lua chunk for luaeval(): [string "<VimL compiled string>"]:1: Unexpected type',
+ eq('Vim(call):E5108: Error executing lua [string "luaeval()"]:1: Unexpected type',
exc_exec([[call luaeval("vim.api.nvim__id_dictionary({[vim.type_idx]=vim.types.array})")]]))
-- TODO: check for errors with Tabpage argument
-- TODO: check for errors with Window argument
diff --git a/test/functional/lua/buffer_updates_spec.lua b/test/functional/lua/buffer_updates_spec.lua
index 990cb97fec..77f8189bb9 100644
--- a/test/functional/lua/buffer_updates_spec.lua
+++ b/test/functional/lua/buffer_updates_spec.lua
@@ -98,7 +98,7 @@ describe('lua: buffer event callbacks', function()
command('undo')
-- plugins can opt in to receive changedtick events, or choose
- -- to only recieve actual changes.
+ -- to only receive actual changes.
check_events({{ "test1", "lines", 1, tick, 3, 4, 5, 13 },
{ "test2", "lines", 1, tick, 3, 4, 5, 13 },
{ "test2", "changedtick", 1, tick+1 } })
@@ -111,7 +111,7 @@ describe('lua: buffer event callbacks', function()
tick = tick + 1
-- plugins can opt in to receive changedtick events, or choose
- -- to only recieve actual changes.
+ -- to only receive actual changes.
check_events({{ "test1", "lines", 1, tick, 6, 7, 9, 16 },
{ "test2", "lines", 1, tick, 6, 7, 9, 16 }})
@@ -203,4 +203,17 @@ describe('lua: buffer event callbacks', function()
{ "test1", "lines", 1, tick+1, 5, 6, 5, 27, 20, 20 }}, exec_lua("return get_events(...)" ))
end)
+ it('has valid cursor position while shifting', function()
+ meths.buf_set_lines(0, 0, -1, true, {'line1'})
+ exec_lua([[
+ vim.api.nvim_buf_attach(0, false, {
+ on_lines = function()
+ vim.api.nvim_set_var('listener_cursor_line', vim.api.nvim_win_get_cursor(0)[1])
+ end,
+ })
+ ]])
+ feed('>>')
+ eq(1, meths.get_var('listener_cursor_line'))
+ end)
+
end)
diff --git a/test/functional/lua/commands_spec.lua b/test/functional/lua/commands_spec.lua
index 26dcbe0534..cbc3aee557 100644
--- a/test/functional/lua/commands_spec.lua
+++ b/test/functional/lua/commands_spec.lua
@@ -13,6 +13,7 @@ local source = helpers.source
local dedent = helpers.dedent
local command = helpers.command
local exc_exec = helpers.exc_exec
+local pcall_err = helpers.pcall_err
local write_file = helpers.write_file
local redir_exec = helpers.redir_exec
local curbufmeths = helpers.curbufmeths
@@ -42,16 +43,16 @@ describe(':lua command', function()
eq({'', 'ETTS', 'TTSE', 'STTE'}, curbufmeths.get_lines(0, 100, false))
end)
it('throws catchable errors', function()
- eq([[Vim(lua):E5104: Error while creating lua chunk: [string "<VimL compiled string>"]:1: unexpected symbol near ')']],
- exc_exec('lua ()'))
- eq([[Vim(lua):E5105: Error while calling lua chunk: [string "<VimL compiled string>"]:1: TEST]],
+ eq([[Vim(lua):E5107: Error loading lua [string ":lua"]:1: unexpected symbol near ')']],
+ pcall_err(command, 'lua ()'))
+ eq([[Vim(lua):E5108: Error executing lua [string ":lua"]:1: TEST]],
exc_exec('lua error("TEST")'))
- eq([[Vim(lua):E5105: Error while calling lua chunk: [string "<VimL compiled string>"]:1: Invalid buffer id]],
+ eq([[Vim(lua):E5108: Error executing lua [string ":lua"]:1: Invalid buffer id: -10]],
exc_exec('lua vim.api.nvim_buf_set_lines(-10, 1, 1, false, {"TEST"})'))
eq({''}, curbufmeths.get_lines(0, 100, false))
end)
it('works with NULL errors', function()
- eq([=[Vim(lua):E5105: Error while calling lua chunk: [NULL]]=],
+ eq([=[Vim(lua):E5108: Error executing lua [NULL]]=],
exc_exec('lua error(nil)'))
end)
it('accepts embedded NLs without heredoc', function()
@@ -74,7 +75,7 @@ describe(':lua command', function()
it('works with long strings', function()
local s = ('x'):rep(100500)
- eq('\nE5104: Error while creating lua chunk: [string "<VimL compiled string>"]:1: unfinished string near \'<eof>\'', redir_exec(('lua vim.api.nvim_buf_set_lines(1, 1, 2, false, {"%s})'):format(s)))
+ eq('\nE5107: Error loading lua [string ":lua"]:1: unfinished string near \'<eof>\'', redir_exec(('lua vim.api.nvim_buf_set_lines(1, 1, 2, false, {"%s})'):format(s)))
eq({''}, curbufmeths.get_lines(0, -1, false))
eq('', redir_exec(('lua vim.api.nvim_buf_set_lines(1, 1, 2, false, {"%s"})'):format(s)))
@@ -82,7 +83,7 @@ describe(':lua command', function()
end)
it('can show multiline error messages', function()
- local screen = Screen.new(50,10)
+ local screen = Screen.new(40,10)
screen:attach()
screen:set_default_attr_ids({
[1] = {bold = true, foreground = Screen.colors.Blue1},
@@ -92,51 +93,51 @@ describe(':lua command', function()
})
feed(':lua error("fail\\nmuch error\\nsuch details")<cr>')
- screen:expect([[
- |
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {2: }|
- {3:E5105: Error while calling lua chunk: [string "<Vi}|
- {3:mL compiled string>"]:1: fail} |
- {3:much error} |
- {3:such details} |
- {4:Press ENTER or type command to continue}^ |
- ]])
+ screen:expect{grid=[[
+ |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {2: }|
+ {3:E5108: Error executing lua [string ":lua}|
+ {3:"]:1: fail} |
+ {3:much error} |
+ {3:such details} |
+ {4:Press ENTER or type command to continue}^ |
+ ]]}
feed('<cr>')
- screen:expect([[
- ^ |
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- |
- ]])
- eq('E5105: Error while calling lua chunk: [string "<VimL compiled string>"]:1: fail\nmuch error\nsuch details', eval('v:errmsg'))
+ screen:expect{grid=[[
+ ^ |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ |
+ ]]}
+ eq('E5108: Error executing lua [string ":lua"]:1: fail\nmuch error\nsuch details', eval('v:errmsg'))
local status, err = pcall(command,'lua error("some error\\nin a\\nAPI command")')
- local expected = 'Vim(lua):E5105: Error while calling lua chunk: [string "<VimL compiled string>"]:1: some error\nin a\nAPI command'
+ local expected = 'Vim(lua):E5108: Error executing lua [string ":lua"]:1: some error\nin a\nAPI command'
eq(false, status)
eq(expected, string.sub(err, -string.len(expected)))
feed(':messages<cr>')
- screen:expect([[
- |
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {2: }|
- {3:E5105: Error while calling lua chunk: [string "<Vi}|
- {3:mL compiled string>"]:1: fail} |
- {3:much error} |
- {3:such details} |
- {4:Press ENTER or type command to continue}^ |
- ]])
+ screen:expect{grid=[[
+ |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {2: }|
+ {3:E5108: Error executing lua [string ":lua}|
+ {3:"]:1: fail} |
+ {3:much error} |
+ {3:such details} |
+ {4:Press ENTER or type command to continue}^ |
+ ]]}
end)
end)
@@ -167,13 +168,13 @@ describe(':luado command', function()
eq({''}, curbufmeths.get_lines(0, -1, false))
end)
it('fails on errors', function()
- eq([[Vim(luado):E5109: Error while creating lua chunk: [string "<VimL compiled string>"]:1: unexpected symbol near ')']],
+ eq([[Vim(luado):E5109: Error loading lua: [string ":luado"]:1: unexpected symbol near ')']],
exc_exec('luado ()'))
- eq([[Vim(luado):E5111: Error while calling lua function: [string "<VimL compiled string>"]:1: attempt to perform arithmetic on global 'liness' (a nil value)]],
+ eq([[Vim(luado):E5111: Error calling lua: [string ":luado"]:1: attempt to perform arithmetic on global 'liness' (a nil value)]],
exc_exec('luado return liness + 1'))
end)
it('works with NULL errors', function()
- eq([=[Vim(luado):E5111: Error while calling lua function: [NULL]]=],
+ eq([=[Vim(luado):E5111: Error calling lua: [NULL]]=],
exc_exec('luado error(nil)'))
end)
it('fails in sandbox when needed', function()
@@ -185,7 +186,7 @@ describe(':luado command', function()
it('works with long strings', function()
local s = ('x'):rep(100500)
- eq('\nE5109: Error while creating lua chunk: [string "<VimL compiled string>"]:1: unfinished string near \'<eof>\'', redir_exec(('luado return "%s'):format(s)))
+ eq('\nE5109: Error loading lua: [string ":luado"]:1: unfinished string near \'<eof>\'', redir_exec(('luado return "%s'):format(s)))
eq({''}, curbufmeths.get_lines(0, -1, false))
eq('', redir_exec(('luado return "%s"'):format(s)))
diff --git a/test/functional/lua/luaeval_spec.lua b/test/functional/lua/luaeval_spec.lua
index 760105df6b..75966393b1 100644
--- a/test/functional/lua/luaeval_spec.lua
+++ b/test/functional/lua/luaeval_spec.lua
@@ -1,13 +1,16 @@
-- Test suite for testing luaeval() function
local helpers = require('test.functional.helpers')(after_each)
+local Screen = require('test.functional.ui.screen')
-local redir_exec = helpers.redir_exec
+local pcall_err = helpers.pcall_err
local exc_exec = helpers.exc_exec
+local exec_lua = helpers.exec_lua
local command = helpers.command
local meths = helpers.meths
local funcs = helpers.funcs
local clear = helpers.clear
local eval = helpers.eval
+local feed = helpers.feed
local NIL = helpers.NIL
local eq = helpers.eq
@@ -184,23 +187,210 @@ describe('luaeval()', function()
it('issues an error in some cases', function()
eq("Vim(call):E5100: Cannot convert given lua table: table should either have a sequence of positive integer keys or contain only string keys",
exc_exec('call luaeval("{1, foo=2}")'))
- eq("Vim(call):E5101: Cannot convert given lua type",
- exc_exec('call luaeval("vim.api.nvim_buf_get_lines")'))
- startswith("Vim(call):E5107: Error while creating lua chunk for luaeval(): ",
+
+ startswith("Vim(call):E5107: Error loading lua [string \"luaeval()\"]:",
exc_exec('call luaeval("1, 2, 3")'))
- startswith("Vim(call):E5108: Error while calling lua chunk for luaeval(): ",
+ startswith("Vim(call):E5108: Error executing lua [string \"luaeval()\"]:",
exc_exec('call luaeval("(nil)()")'))
- eq("Vim(call):E5101: Cannot convert given lua type",
- exc_exec('call luaeval("{42, vim.api}")'))
- eq("Vim(call):E5101: Cannot convert given lua type",
- exc_exec('call luaeval("{foo=42, baz=vim.api}")'))
- -- The following should not crash: conversion error happens inside
- eq("Vim(call):E5101: Cannot convert given lua type",
- exc_exec('call luaeval("vim.api")'))
- -- The following should not show internal error
- eq("\nE5101: Cannot convert given lua type\n0",
- redir_exec('echo luaeval("vim.api")'))
+ end)
+
+ it('should handle sending lua functions to viml', function()
+ eq(true, exec_lua [[
+ can_pass_lua_callback_to_vim_from_lua_result = nil
+
+ vim.fn.call(function()
+ can_pass_lua_callback_to_vim_from_lua_result = true
+ end, {})
+
+ return can_pass_lua_callback_to_vim_from_lua_result
+ ]])
+ end)
+
+ it('run functions even in timers', function()
+ eq(true, exec_lua [[
+ can_pass_lua_callback_to_vim_from_lua_result = nil
+
+ vim.fn.timer_start(50, function()
+ can_pass_lua_callback_to_vim_from_lua_result = true
+ end)
+
+ vim.wait(1000, function()
+ return can_pass_lua_callback_to_vim_from_lua_result
+ end)
+
+ return can_pass_lua_callback_to_vim_from_lua_result
+ ]])
+ end)
+
+ it('can run named functions more than once', function()
+ eq(5, exec_lua [[
+ count_of_vals = 0
+
+ vim.fn.timer_start(5, function()
+ count_of_vals = count_of_vals + 1
+ end, {['repeat'] = 5})
+
+ vim.fn.wait(1000, function()
+ return count_of_vals >= 5
+ end)
+
+ return count_of_vals
+ ]])
+ end)
+
+ it('can handle clashing names', function()
+ eq(1, exec_lua [[
+ local f_loc = function() return 1 end
+
+ local result = nil
+ vim.fn.timer_start(100, function()
+ result = f_loc()
+ end)
+
+ local f_loc = function() return 2 end
+ vim.wait(1000, function() return result ~= nil end)
+
+ return result
+ ]])
+ end)
+
+ it('can handle functions with errors', function()
+ eq(true, exec_lua [[
+ vim.fn.timer_start(10, function()
+ error("dead function")
+ end)
+
+ vim.wait(1000, function() return false end)
+
+ return true
+ ]])
+ end)
+
+ it('should handle passing functions around', function()
+ command [[
+ function VimCanCallLuaCallbacks(Concat, Cb)
+ let message = a:Concat("Hello Vim", "I'm Lua")
+ call a:Cb(message)
+ endfunction
+ ]]
+
+ eq("Hello Vim I'm Lua", exec_lua [[
+ can_pass_lua_callback_to_vim_from_lua_result = ""
+
+ vim.fn.VimCanCallLuaCallbacks(
+ function(greeting, message) return greeting .. " " .. message end,
+ function(message) can_pass_lua_callback_to_vim_from_lua_result = message end
+ )
+
+ return can_pass_lua_callback_to_vim_from_lua_result
+ ]])
+ end)
+
+ it('should handle funcrefs', function()
+ command [[
+ function VimCanCallLuaCallbacks(Concat, Cb)
+ let message = a:Concat("Hello Vim", "I'm Lua")
+ call a:Cb(message)
+ endfunction
+ ]]
+
+ eq("Hello Vim I'm Lua", exec_lua [[
+ can_pass_lua_callback_to_vim_from_lua_result = ""
+
+ vim.funcref('VimCanCallLuaCallbacks')(
+ function(greeting, message) return greeting .. " " .. message end,
+ function(message) can_pass_lua_callback_to_vim_from_lua_result = message end
+ )
+
+ return can_pass_lua_callback_to_vim_from_lua_result
+ ]])
+ end)
+
+ it('should work with metatables using __call', function()
+ eq(1, exec_lua [[
+ local this_is_local_variable = false
+ local callable_table = setmetatable({x = 1}, {
+ __call = function(t, ...)
+ this_is_local_variable = t.x
+ end
+ })
+
+ vim.fn.timer_start(5, callable_table)
+
+ vim.wait(1000, function()
+ return this_is_local_variable
+ end)
+
+ return this_is_local_variable
+ ]])
+ end)
+
+ it('should handle being called from a timer once.', function()
+ eq(3, exec_lua [[
+ local this_is_local_variable = false
+ local callable_table = setmetatable({5, 4, 3, 2, 1}, {
+ __call = function(t, ...) this_is_local_variable = t[3] end
+ })
+
+ vim.fn.timer_start(5, callable_table)
+ vim.wait(1000, function()
+ return this_is_local_variable
+ end)
+
+ return this_is_local_variable
+ ]])
+ end)
+
+ it('should call functions once with __call metamethod', function()
+ eq(true, exec_lua [[
+ local this_is_local_variable = false
+ local callable_table = setmetatable({a = true, b = false}, {
+ __call = function(t, ...) this_is_local_variable = t.a end
+ })
+
+ assert(getmetatable(callable_table).__call)
+ vim.fn.call(callable_table, {})
+
+ return this_is_local_variable
+ ]])
+ end)
+
+ it('should work with lists using __call', function()
+ eq(3, exec_lua [[
+ local this_is_local_variable = false
+ local mt = {
+ __call = function(t, ...)
+ this_is_local_variable = t[3]
+ end
+ }
+ local callable_table = setmetatable({5, 4, 3, 2, 1}, mt)
+
+ -- Call it once...
+ vim.fn.timer_start(5, callable_table)
+ vim.wait(1000, function()
+ return this_is_local_variable
+ end)
+
+ assert(this_is_local_variable)
+ this_is_local_variable = false
+
+ vim.fn.timer_start(5, callable_table)
+ vim.wait(1000, function()
+ return this_is_local_variable
+ end)
+
+ return this_is_local_variable
+ ]])
+ end)
+
+ it('should not work with tables not using __call', function()
+ eq({false, 'Vim:E921: Invalid callback argument'}, exec_lua [[
+ local this_is_local_variable = false
+ local callable_table = setmetatable({x = 1}, {})
+
+ return {pcall(function() vim.fn.timer_start(5, callable_table) end)}
+ ]])
end)
it('correctly converts containers with type_idx', function()
@@ -237,19 +427,99 @@ describe('luaeval()', function()
it('errors out correctly when doing incorrect things in lua', function()
-- Conversion errors
- eq('Vim(call):E5108: Error while calling lua chunk for luaeval(): [string "<VimL compiled string>"]:1: attempt to call field \'xxx_nonexistent_key_xxx\' (a nil value)',
+ eq('Vim(call):E5108: Error executing lua [string "luaeval()"]:1: attempt to call field \'xxx_nonexistent_key_xxx\' (a nil value)',
exc_exec([[call luaeval("vim.xxx_nonexistent_key_xxx()")]]))
- eq('Vim(call):E5108: Error while calling lua chunk for luaeval(): [string "<VimL compiled string>"]:1: ERROR',
+ eq('Vim(call):E5108: Error executing lua [string "luaeval()"]:1: ERROR',
exc_exec([[call luaeval("error('ERROR')")]]))
- eq('Vim(call):E5108: Error while calling lua chunk for luaeval(): [NULL]',
+ eq('Vim(call):E5108: Error executing lua [NULL]',
exc_exec([[call luaeval("error(nil)")]]))
end)
it('does not leak memory when called with too long line',
function()
local s = ('x'):rep(65536)
- eq('Vim(call):E5107: Error while creating lua chunk for luaeval(): [string "<VimL compiled string>"]:1: unexpected symbol near \')\'',
+ eq('Vim(call):E5107: Error loading lua [string "luaeval()"]:1: unexpected symbol near \')\'',
exc_exec([[call luaeval("(']] .. s ..[[' + )")]]))
eq(s, funcs.luaeval('"' .. s .. '"'))
end)
end)
+
+describe('v:lua', function()
+ before_each(function()
+ exec_lua([[
+ function _G.foo(a,b,n)
+ _G.val = n
+ return a+b
+ end
+ mymod = {}
+ function mymod.noisy(name)
+ vim.api.nvim_set_current_line("hey "..name)
+ end
+ function mymod.crashy()
+ nonexistent()
+ end
+ function mymod.omni(findstart, base)
+ if findstart == 1 then
+ return 5
+ else
+ if base == 'st' then
+ return {'stuff', 'steam', 'strange things'}
+ end
+ end
+ end
+ vim.api.nvim_buf_set_option(0, 'omnifunc', 'v:lua.mymod.omni')
+ ]])
+ end)
+
+ it('works in expressions', function()
+ eq(7, eval('v:lua.foo(3,4,v:null)'))
+ eq(true, exec_lua([[return _G.val == vim.NIL]]))
+ eq(NIL, eval('v:lua.mymod.noisy("eval")'))
+ eq("hey eval", meths.get_current_line())
+
+ eq("Vim:E5108: Error executing lua [string \"<nvim>\"]:10: attempt to call global 'nonexistent' (a nil value)",
+ pcall_err(eval, 'v:lua.mymod.crashy()'))
+ end)
+
+ it('works in :call', function()
+ command(":call v:lua.mymod.noisy('command')")
+ eq("hey command", meths.get_current_line())
+ eq("Vim(call):E5108: Error executing lua [string \"<nvim>\"]:10: attempt to call global 'nonexistent' (a nil value)",
+ pcall_err(command, 'call v:lua.mymod.crashy()'))
+ end)
+
+ it('works in func options', function()
+ local screen = Screen.new(60, 8)
+ screen:set_default_attr_ids({
+ [1] = {bold = true, foreground = Screen.colors.Blue1},
+ [2] = {background = Screen.colors.WebGray},
+ [3] = {background = Screen.colors.LightMagenta},
+ [4] = {bold = true},
+ [5] = {bold = true, foreground = Screen.colors.SeaGreen4},
+ })
+ screen:attach()
+ feed('isome st<c-x><c-o>')
+ screen:expect{grid=[[
+ some stuff^ |
+ {1:~ }{2: stuff }{1: }|
+ {1:~ }{3: steam }{1: }|
+ {1:~ }{3: strange things }{1: }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {4:-- Omni completion (^O^N^P) }{5:match 1 of 3} |
+ ]]}
+ end)
+
+ it('throw errors for invalid use', function()
+ eq('Vim(let):E15: Invalid expression: v:lua.func', pcall_err(command, "let g:Func = v:lua.func"))
+ eq('Vim(let):E15: Invalid expression: v:lua', pcall_err(command, "let g:Func = v:lua"))
+ eq("Vim(let):E15: Invalid expression: v:['lua']", pcall_err(command, "let g:Func = v:['lua']"))
+
+ eq("Vim:E15: Invalid expression: v:['lua'].foo()", pcall_err(eval, "v:['lua'].foo()"))
+ eq("Vim(call):E461: Illegal variable name: v:['lua']", pcall_err(command, "call v:['lua'].baar()"))
+
+ eq("Vim(let):E46: Cannot change read-only variable \"v:['lua']\"", pcall_err(command, "let v:['lua'] = 'xx'"))
+ eq("Vim(let):E46: Cannot change read-only variable \"v:lua\"", pcall_err(command, "let v:lua = 'xx'"))
+ end)
+end)
diff --git a/test/functional/lua/overrides_spec.lua b/test/functional/lua/overrides_spec.lua
index f6439001ac..1bccc02847 100644
--- a/test/functional/lua/overrides_spec.lua
+++ b/test/functional/lua/overrides_spec.lua
@@ -54,11 +54,12 @@ describe('print', function()
v_tblout = setmetatable({}, meta_tblout)
]])
eq('', redir_exec('luafile ' .. fname))
- eq('\nE5105: Error while calling lua chunk: E5114: Error while converting print argument #2: [NULL]',
+ -- TODO(bfredl): these look weird, print() should not use "E5114:" style errors..
+ eq('\nE5108: Error executing lua E5114: Error while converting print argument #2: [NULL]',
redir_exec('lua print("foo", v_nilerr, "bar")'))
- eq('\nE5105: Error while calling lua chunk: E5114: Error while converting print argument #2: Xtest-functional-lua-overrides-luafile:2: abc',
+ eq('\nE5108: Error executing lua E5114: Error while converting print argument #2: Xtest-functional-lua-overrides-luafile:2: abc',
redir_exec('lua print("foo", v_abcerr, "bar")'))
- eq('\nE5105: Error while calling lua chunk: E5114: Error while converting print argument #2: <Unknown error: lua_tolstring returned NULL for tostring result>',
+ eq('\nE5108: Error executing lua E5114: Error while converting print argument #2: <Unknown error: lua_tolstring returned NULL for tostring result>',
redir_exec('lua print("foo", v_tblout, "bar")'))
end)
it('prints strings with NULs and NLs correctly', function()
@@ -156,7 +157,8 @@ describe('debug.debug', function()
lua_debug> ^ |
]])
feed('<C-c>')
- screen:expect([[
+ screen:expect{grid=[[
+ {0:~ }|
{0:~ }|
{0:~ }|
{0:~ }|
@@ -167,11 +169,10 @@ describe('debug.debug', function()
lua_debug> print("TEST") |
TEST |
|
- {E:E5105: Error while calling lua chunk: [string "<VimL }|
- {E:compiled string>"]:5: attempt to perform arithmetic o}|
- {E:n local 'a' (a nil value)} |
+ {E:E5108: Error executing lua [string ":lua"]:5: attempt}|
+ {E: to perform arithmetic on local 'a' (a nil value)} |
Interrupt: {cr:Press ENTER or type command to continue}^ |
- ]])
+ ]]}
feed('<C-l>:lua Test()\n')
screen:expect([[
{0:~ }|
@@ -190,7 +191,8 @@ describe('debug.debug', function()
lua_debug> ^ |
]])
feed('\n')
- screen:expect([[
+ screen:expect{grid=[[
+ {0:~ }|
{0:~ }|
{0:~ }|
{0:~ }|
@@ -201,11 +203,10 @@ describe('debug.debug', function()
{0:~ }|
nil |
lua_debug> |
- {E:E5105: Error while calling lua chunk: [string "<VimL }|
- {E:compiled string>"]:5: attempt to perform arithmetic o}|
- {E:n local 'a' (a nil value)} |
+ {E:E5108: Error executing lua [string ":lua"]:5: attempt}|
+ {E: to perform arithmetic on local 'a' (a nil value)} |
{cr:Press ENTER or type command to continue}^ |
- ]])
+ ]]}
end)
it("can be safely exited with 'cont'", function()
@@ -298,14 +299,11 @@ describe('package.path/package.cpath', function()
end
return new_paths
end
- local function execute_lua(cmd, ...)
- return meths.execute_lua(cmd, {...})
- end
local function eval_lua(expr, ...)
- return meths.execute_lua('return ' .. expr, {...})
+ return meths.exec_lua('return '..expr, {...})
end
local function set_path(which, value)
- return execute_lua('package[select(1, ...)] = select(2, ...)', which, value)
+ return exec_lua('package[select(1, ...)] = select(2, ...)', which, value)
end
it('contains directories from &runtimepath on first invocation', function()
diff --git a/test/functional/lua/treesitter_spec.lua b/test/functional/lua/treesitter_spec.lua
new file mode 100644
index 0000000000..aa3d55b06d
--- /dev/null
+++ b/test/functional/lua/treesitter_spec.lua
@@ -0,0 +1,512 @@
+-- Test suite for testing interactions with API bindings
+local helpers = require('test.functional.helpers')(after_each)
+local Screen = require('test.functional.ui.screen')
+
+local clear = helpers.clear
+local eq = helpers.eq
+local insert = helpers.insert
+local exec_lua = helpers.exec_lua
+local feed = helpers.feed
+local pcall_err = helpers.pcall_err
+local matches = helpers.matches
+
+before_each(clear)
+
+describe('treesitter API', function()
+ -- error tests not requiring a parser library
+ it('handles missing language', function()
+ eq("Error executing lua: .../treesitter.lua: no parser for 'borklang' language",
+ pcall_err(exec_lua, "parser = vim.treesitter.create_parser(0, 'borklang')"))
+
+ -- actual message depends on platform
+ matches("Error executing lua: Failed to load parser: uv_dlopen: .+",
+ pcall_err(exec_lua, "parser = vim.treesitter.require_language('borklang', 'borkbork.so')"))
+
+ eq("Error executing lua: .../treesitter.lua: no parser for 'borklang' language",
+ pcall_err(exec_lua, "parser = vim.treesitter.inspect_language('borklang')"))
+ end)
+
+end)
+
+describe('treesitter API with C parser', function()
+ local function check_parser()
+ local status, msg = unpack(exec_lua([[ return {pcall(vim.treesitter.require_language, 'c')} ]]))
+ if not status then
+ if helpers.isCI() then
+ error("treesitter C parser not found, required on CI: " .. msg)
+ else
+ pending('no C parser, skipping')
+ end
+ end
+ return status
+ end
+
+ it('parses buffer', function()
+ if not check_parser() then return end
+
+ insert([[
+ int main() {
+ int x = 3;
+ }]])
+
+ exec_lua([[
+ parser = vim.treesitter.get_parser(0, "c")
+ tree = parser:parse()
+ root = tree:root()
+ lang = vim.treesitter.inspect_language('c')
+ ]])
+
+ eq("<tree>", exec_lua("return tostring(tree)"))
+ eq("<node translation_unit>", exec_lua("return tostring(root)"))
+ eq({0,0,3,0}, exec_lua("return {root:range()}"))
+
+ eq(1, exec_lua("return root:child_count()"))
+ exec_lua("child = root:child(0)")
+ eq("<node function_definition>", exec_lua("return tostring(child)"))
+ eq({0,0,2,1}, exec_lua("return {child:range()}"))
+
+ eq("function_definition", exec_lua("return child:type()"))
+ eq(true, exec_lua("return child:named()"))
+ eq("number", type(exec_lua("return child:symbol()")))
+ eq({'function_definition', true}, exec_lua("return lang.symbols[child:symbol()]"))
+
+ exec_lua("anon = root:descendant_for_range(0,8,0,9)")
+ eq("(", exec_lua("return anon:type()"))
+ eq(false, exec_lua("return anon:named()"))
+ eq("number", type(exec_lua("return anon:symbol()")))
+ eq({'(', false}, exec_lua("return lang.symbols[anon:symbol()]"))
+
+ exec_lua("descendant = root:descendant_for_range(1,2,1,12)")
+ eq("<node declaration>", exec_lua("return tostring(descendant)"))
+ eq({1,2,1,12}, exec_lua("return {descendant:range()}"))
+ eq("(declaration type: (primitive_type) declarator: (init_declarator declarator: (identifier) value: (number_literal)))", exec_lua("return descendant:sexpr()"))
+
+ eq(true, exec_lua("return child == child"))
+ -- separate lua object, but represents same node
+ eq(true, exec_lua("return child == root:child(0)"))
+ eq(false, exec_lua("return child == descendant2"))
+ eq(false, exec_lua("return child == nil"))
+ eq(false, exec_lua("return child == tree"))
+
+ feed("2G7|ay")
+ exec_lua([[
+ tree2 = parser:parse()
+ root2 = tree2:root()
+ descendant2 = root2:descendant_for_range(1,2,1,13)
+ ]])
+ eq(false, exec_lua("return tree2 == tree1"))
+ eq(false, exec_lua("return root2 == root"))
+ eq("<node declaration>", exec_lua("return tostring(descendant2)"))
+ eq({1,2,1,13}, exec_lua("return {descendant2:range()}"))
+
+ -- orginal tree did not change
+ eq({1,2,1,12}, exec_lua("return {descendant:range()}"))
+
+ -- unchanged buffer: return the same tree
+ eq(true, exec_lua("return parser:parse() == tree2"))
+ end)
+
+ local test_text = [[
+void ui_refresh(void)
+{
+ int width = INT_MAX, height = INT_MAX;
+ bool ext_widgets[kUIExtCount];
+ for (UIExtension i = 0; (int)i < kUIExtCount; i++) {
+ ext_widgets[i] = true;
+ }
+
+ bool inclusive = ui_override();
+ for (size_t i = 0; i < ui_count; i++) {
+ UI *ui = uis[i];
+ width = MIN(ui->width, width);
+ height = MIN(ui->height, height);
+ foo = BAR(ui->bazaar, bazaar);
+ for (UIExtension j = 0; (int)j < kUIExtCount; j++) {
+ ext_widgets[j] &= (ui->ui_ext[j] || inclusive);
+ }
+ }
+}]]
+
+ local query = [[
+ ((call_expression function: (identifier) @minfunc (argument_list (identifier) @min_id)) (eq? @minfunc "MIN"))
+ "for" @keyword
+ (primitive_type) @type
+ (field_expression argument: (identifier) @fieldarg)
+ ]]
+
+ it('support query and iter by capture', function()
+ if not check_parser() then return end
+
+ insert(test_text)
+
+ local res = exec_lua([[
+ cquery = vim.treesitter.parse_query("c", ...)
+ parser = vim.treesitter.get_parser(0, "c")
+ tree = parser:parse()
+ res = {}
+ for cid, node in cquery:iter_captures(tree:root(), 0, 7, 14) do
+ -- can't transmit node over RPC. just check the name and range
+ table.insert(res, {cquery.captures[cid], node:type(), node:range()})
+ end
+ return res
+ ]], query)
+
+ eq({
+ { "type", "primitive_type", 8, 2, 8, 6 },
+ { "keyword", "for", 9, 2, 9, 5 },
+ { "type", "primitive_type", 9, 7, 9, 13 },
+ { "minfunc", "identifier", 11, 12, 11, 15 },
+ { "fieldarg", "identifier", 11, 16, 11, 18 },
+ { "min_id", "identifier", 11, 27, 11, 32 },
+ { "minfunc", "identifier", 12, 13, 12, 16 },
+ { "fieldarg", "identifier", 12, 17, 12, 19 },
+ { "min_id", "identifier", 12, 29, 12, 35 },
+ { "fieldarg", "identifier", 13, 14, 13, 16 }
+ }, res)
+ end)
+
+ it('support query and iter by match', function()
+ if not check_parser() then return end
+
+ insert(test_text)
+
+ local res = exec_lua([[
+ cquery = vim.treesitter.parse_query("c", ...)
+ parser = vim.treesitter.get_parser(0, "c")
+ tree = parser:parse()
+ res = {}
+ for pattern, match in cquery:iter_matches(tree:root(), 0, 7, 14) do
+ -- can't transmit node over RPC. just check the name and range
+ local mrepr = {}
+ for cid,node in pairs(match) do
+ table.insert(mrepr, {cquery.captures[cid], node:type(), node:range()})
+ end
+ table.insert(res, {pattern, mrepr})
+ end
+ return res
+ ]], query)
+
+ eq({
+ { 3, { { "type", "primitive_type", 8, 2, 8, 6 } } },
+ { 2, { { "keyword", "for", 9, 2, 9, 5 } } },
+ { 3, { { "type", "primitive_type", 9, 7, 9, 13 } } },
+ { 4, { { "fieldarg", "identifier", 11, 16, 11, 18 } } },
+ { 1, { { "minfunc", "identifier", 11, 12, 11, 15 }, { "min_id", "identifier", 11, 27, 11, 32 } } },
+ { 4, { { "fieldarg", "identifier", 12, 17, 12, 19 } } },
+ { 1, { { "minfunc", "identifier", 12, 13, 12, 16 }, { "min_id", "identifier", 12, 29, 12, 35 } } },
+ { 4, { { "fieldarg", "identifier", 13, 14, 13, 16 } } }
+ }, res)
+ end)
+
+ it('supports highlighting', function()
+ if not check_parser() then return end
+
+ local hl_text = [[
+/// Schedule Lua callback on main loop's event queue
+static int nlua_schedule(lua_State *const lstate)
+{
+ if (lua_type(lstate, 1) != LUA_TFUNCTION
+ || lstate != lstate) {
+ lua_pushliteral(lstate, "vim.schedule: expected function");
+ return lua_error(lstate);
+ }
+
+ LuaRef cb = nlua_ref(lstate, 1);
+
+ multiqueue_put(main_loop.events, nlua_schedule_event,
+ 1, (void *)(ptrdiff_t)cb);
+ return 0;
+}]]
+
+ local hl_query = [[
+(ERROR) @ErrorMsg
+
+"if" @keyword
+"else" @keyword
+"for" @keyword
+"return" @keyword
+
+"const" @type
+"static" @type
+"struct" @type
+"enum" @type
+"extern" @type
+
+(string_literal) @string
+
+(number_literal) @number
+(char_literal) @string
+
+(type_identifier) @type
+((type_identifier) @Special (#eq? @Special "LuaRef"))
+
+(primitive_type) @type
+(sized_type_specifier) @type
+
+; defaults to very magic syntax, for best compatibility
+((identifier) @Identifier (#match? @Identifier "^l(u)a_"))
+; still support \M etc prefixes
+((identifier) @Constant (#match? @Constant "\M^\[A-Z_]\+$"))
+
+((binary_expression left: (identifier) @WarningMsg.left right: (identifier) @WarningMsg.right) (#eq? @WarningMsg.left @WarningMsg.right))
+
+(comment) @comment
+]]
+
+ local screen = Screen.new(65, 18)
+ screen:attach()
+ screen:set_default_attr_ids({
+ [1] = {bold = true, foreground = Screen.colors.Blue1},
+ [2] = {foreground = Screen.colors.Blue1},
+ [3] = {bold = true, foreground = Screen.colors.SeaGreen4},
+ [4] = {bold = true, foreground = Screen.colors.Brown},
+ [5] = {foreground = Screen.colors.Magenta},
+ [6] = {foreground = Screen.colors.Red},
+ [7] = {bold = true, foreground = Screen.colors.SlateBlue},
+ [8] = {foreground = Screen.colors.Grey100, background = Screen.colors.Red},
+ [9] = {foreground = Screen.colors.Magenta, background = Screen.colors.Red},
+ [10] = {foreground = Screen.colors.Red, background = Screen.colors.Red},
+ [11] = {foreground = Screen.colors.Cyan4},
+ })
+
+ insert(hl_text)
+ screen:expect{grid=[[
+ /// Schedule Lua callback on main loop's event queue |
+ static int nlua_schedule(lua_State *const lstate) |
+ { |
+ if (lua_type(lstate, 1) != LUA_TFUNCTION |
+ || lstate != lstate) { |
+ lua_pushliteral(lstate, "vim.schedule: expected function"); |
+ return lua_error(lstate); |
+ } |
+ |
+ LuaRef cb = nlua_ref(lstate, 1); |
+ |
+ multiqueue_put(main_loop.events, nlua_schedule_event, |
+ 1, (void *)(ptrdiff_t)cb); |
+ return 0; |
+ ^} |
+ {1:~ }|
+ {1:~ }|
+ |
+ ]]}
+
+ exec_lua([[
+ local TSHighlighter = vim.treesitter.TSHighlighter
+ local query = ...
+ test_hl = TSHighlighter.new(query, 0, "c")
+ ]], hl_query)
+ screen:expect{grid=[[
+ {2:/// Schedule Lua callback on main loop's event queue} |
+ {3:static} {3:int} nlua_schedule({3:lua_State} *{3:const} lstate) |
+ { |
+ {4:if} ({11:lua_type}(lstate, {5:1}) != {5:LUA_TFUNCTION} |
+ || {6:lstate} != {6:lstate}) { |
+ {11:lua_pushliteral}(lstate, {5:"vim.schedule: expected function"}); |
+ {4:return} {11:lua_error}(lstate); |
+ } |
+ |
+ {7:LuaRef} cb = nlua_ref(lstate, {5:1}); |
+ |
+ multiqueue_put(main_loop.events, nlua_schedule_event, |
+ {5:1}, ({3:void} *)({3:ptrdiff_t})cb); |
+ {4:return} {5:0}; |
+ ^} |
+ {1:~ }|
+ {1:~ }|
+ |
+ ]]}
+
+ feed('7Go*/<esc>')
+ screen:expect{grid=[[
+ {2:/// Schedule Lua callback on main loop's event queue} |
+ {3:static} {3:int} nlua_schedule({3:lua_State} *{3:const} lstate) |
+ { |
+ {4:if} ({11:lua_type}(lstate, {5:1}) != {5:LUA_TFUNCTION} |
+ || {6:lstate} != {6:lstate}) { |
+ {11:lua_pushliteral}(lstate, {5:"vim.schedule: expected function"}); |
+ {4:return} {11:lua_error}(lstate); |
+ {8:*^/} |
+ } |
+ |
+ {7:LuaRef} cb = nlua_ref(lstate, {5:1}); |
+ |
+ multiqueue_put(main_loop.events, nlua_schedule_event, |
+ {5:1}, ({3:void} *)({3:ptrdiff_t})cb); |
+ {4:return} {5:0}; |
+ } |
+ {1:~ }|
+ |
+ ]]}
+
+ feed('3Go/*<esc>')
+ screen:expect{grid=[[
+ {2:/// Schedule Lua callback on main loop's event queue} |
+ {3:static} {3:int} nlua_schedule({3:lua_State} *{3:const} lstate) |
+ { |
+ {2:/^*} |
+ {2: if (lua_type(lstate, 1) != LUA_TFUNCTION} |
+ {2: || lstate != lstate) {} |
+ {2: lua_pushliteral(lstate, "vim.schedule: expected function");} |
+ {2: return lua_error(lstate);} |
+ {2:*/} |
+ } |
+ |
+ {7:LuaRef} cb = nlua_ref(lstate, {5:1}); |
+ |
+ multiqueue_put(main_loop.events, nlua_schedule_event, |
+ {5:1}, ({3:void} *)({3:ptrdiff_t})cb); |
+ {4:return} {5:0}; |
+ {8:}} |
+ |
+ ]]}
+
+ feed("gg$")
+ feed("~")
+ screen:expect{grid=[[
+ {2:/// Schedule Lua callback on main loop's event queu^E} |
+ {3:static} {3:int} nlua_schedule({3:lua_State} *{3:const} lstate) |
+ { |
+ {2:/*} |
+ {2: if (lua_type(lstate, 1) != LUA_TFUNCTION} |
+ {2: || lstate != lstate) {} |
+ {2: lua_pushliteral(lstate, "vim.schedule: expected function");} |
+ {2: return lua_error(lstate);} |
+ {2:*/} |
+ } |
+ |
+ {7:LuaRef} cb = nlua_ref(lstate, {5:1}); |
+ |
+ multiqueue_put(main_loop.events, nlua_schedule_event, |
+ {5:1}, ({3:void} *)({3:ptrdiff_t})cb); |
+ {4:return} {5:0}; |
+ {8:}} |
+ |
+ ]]}
+
+
+ feed("re")
+ screen:expect{grid=[[
+ {2:/// Schedule Lua callback on main loop's event queu^e} |
+ {3:static} {3:int} nlua_schedule({3:lua_State} *{3:const} lstate) |
+ { |
+ {2:/*} |
+ {2: if (lua_type(lstate, 1) != LUA_TFUNCTION} |
+ {2: || lstate != lstate) {} |
+ {2: lua_pushliteral(lstate, "vim.schedule: expected function");} |
+ {2: return lua_error(lstate);} |
+ {2:*/} |
+ } |
+ |
+ {7:LuaRef} cb = nlua_ref(lstate, {5:1}); |
+ |
+ multiqueue_put(main_loop.events, nlua_schedule_event, |
+ {5:1}, ({3:void} *)({3:ptrdiff_t})cb); |
+ {4:return} {5:0}; |
+ {8:}} |
+ |
+ ]]}
+ end)
+
+ it('inspects language', function()
+ if not check_parser() then return end
+
+ local keys, fields, symbols = unpack(exec_lua([[
+ local lang = vim.treesitter.inspect_language('c')
+ local keys, symbols = {}, {}
+ for k,_ in pairs(lang) do
+ keys[k] = true
+ end
+
+ -- symbols array can have "holes" and is thus not a valid msgpack array
+ -- but we don't care about the numbers here (checked in the parser test)
+ for _, v in pairs(lang.symbols) do
+ table.insert(symbols, v)
+ end
+ return {keys, lang.fields, symbols}
+ ]]))
+
+ eq({fields=true, symbols=true}, keys)
+
+ local fset = {}
+ for _,f in pairs(fields) do
+ eq("string", type(f))
+ fset[f] = true
+ end
+ eq(true, fset["directive"])
+ eq(true, fset["initializer"])
+
+ local has_named, has_anonymous
+ for _,s in pairs(symbols) do
+ eq("string", type(s[1]))
+ eq("boolean", type(s[2]))
+ if s[1] == "for_statement" and s[2] == true then
+ has_named = true
+ elseif s[1] == "|=" and s[2] == false then
+ has_anonymous = true
+ end
+ end
+ eq({true,true}, {has_named,has_anonymous})
+ end)
+ it('allows to set simple ranges', function()
+ if not check_parser() then return end
+
+ insert(test_text)
+
+ local res = exec_lua([[
+ parser = vim.treesitter.get_parser(0, "c")
+ return { parser:parse():root():range() }
+ ]])
+
+ eq({0, 0, 19, 0}, res)
+
+ -- The following sets the included ranges for the current parser
+ -- As stated here, this only includes the function (thus the whole buffer, without the last line)
+ local res2 = exec_lua([[
+ local root = parser:parse():root()
+ parser:set_included_ranges({root:child(0)})
+ parser.valid = false
+ return { parser:parse():root():range() }
+ ]])
+
+ eq({0, 0, 18, 1}, res2)
+ end)
+ it("allows to set complex ranges", function()
+ if not check_parser() then return end
+
+ insert(test_text)
+
+
+ local res = exec_lua([[
+ parser = vim.treesitter.get_parser(0, "c")
+ query = vim.treesitter.parse_query("c", "(declaration) @decl")
+
+ local nodes = {}
+ for _, node in query:iter_captures(parser:parse():root(), 0, 0, 19) do
+ table.insert(nodes, node)
+ end
+
+ parser:set_included_ranges(nodes)
+
+ local root = parser:parse():root()
+
+ local res = {}
+ for i=0,(root:named_child_count() - 1) do
+ table.insert(res, { root:named_child(i):range() })
+ end
+ return res
+ ]])
+
+ eq({
+ { 2, 2, 2, 40 },
+ { 3, 3, 3, 32 },
+ { 4, 7, 4, 8 },
+ { 4, 8, 4, 25 },
+ { 8, 2, 8, 6 },
+ { 8, 7, 8, 33 },
+ { 9, 8, 9, 20 },
+ { 10, 4, 10, 5 },
+ { 10, 5, 10, 20 },
+ { 14, 9, 14, 27 } }, res)
+ end)
+end)
diff --git a/test/functional/lua/uri_spec.lua b/test/functional/lua/uri_spec.lua
new file mode 100644
index 0000000000..f782769935
--- /dev/null
+++ b/test/functional/lua/uri_spec.lua
@@ -0,0 +1,150 @@
+local helpers = require('test.functional.helpers')(after_each)
+local clear = helpers.clear
+local exec_lua = helpers.exec_lua
+local eq = helpers.eq
+
+describe('URI methods', function()
+ before_each(function()
+ clear()
+ end)
+
+ describe('file path to uri', function()
+ describe('encode Unix file path', function()
+ it('file path includes only ascii charactors', function()
+ exec_lua("filepath = '/Foo/Bar/Baz.txt'")
+
+ eq('file:///Foo/Bar/Baz.txt', exec_lua("return vim.uri_from_fname(filepath)"))
+ end)
+
+ it('file path including white space', function()
+ exec_lua("filepath = '/Foo /Bar/Baz.txt'")
+
+ eq('file:///Foo%20/Bar/Baz.txt', exec_lua("return vim.uri_from_fname(filepath)"))
+ end)
+
+ it('file path including Unicode charactors', function()
+ exec_lua("filepath = '/xy/åäö/ɧ/汉语/↥/🤦/🦄/å/بِيَّ.txt'")
+
+ -- The URI encoding should be case-insensitive
+ eq('file:///xy/%c3%a5%c3%a4%c3%b6/%c9%a7/%e6%b1%89%e8%af%ad/%e2%86%a5/%f0%9f%a4%a6/%f0%9f%a6%84/a%cc%8a/%d8%a8%d9%90%d9%8a%d9%8e%d9%91.txt', exec_lua("return vim.uri_from_fname(filepath)"))
+ end)
+ end)
+
+ describe('encode Windows filepath', function()
+ it('file path includes only ascii charactors', function()
+ exec_lua([[filepath = 'C:\\Foo\\Bar\\Baz.txt']])
+
+ eq('file:///C:/Foo/Bar/Baz.txt', exec_lua("return vim.uri_from_fname(filepath)"))
+ end)
+
+ it('file path including white space', function()
+ exec_lua([[filepath = 'C:\\Foo \\Bar\\Baz.txt']])
+
+ eq('file:///C:/Foo%20/Bar/Baz.txt', exec_lua("return vim.uri_from_fname(filepath)"))
+ end)
+
+ it('file path including Unicode charactors', function()
+ exec_lua([[filepath = 'C:\\xy\\åäö\\ɧ\\汉语\\↥\\🤦\\🦄\\å\\بِيَّ.txt']])
+
+ eq('file:///C:/xy/%c3%a5%c3%a4%c3%b6/%c9%a7/%e6%b1%89%e8%af%ad/%e2%86%a5/%f0%9f%a4%a6/%f0%9f%a6%84/a%cc%8a/%d8%a8%d9%90%d9%8a%d9%8e%d9%91.txt', exec_lua("return vim.uri_from_fname(filepath)"))
+ end)
+ end)
+ end)
+
+ describe('uri to filepath', function()
+ describe('decode Unix file path', function()
+ it('file path includes only ascii charactors', function()
+ exec_lua("uri = 'file:///Foo/Bar/Baz.txt'")
+
+ eq('/Foo/Bar/Baz.txt', exec_lua("return vim.uri_to_fname(uri)"))
+ end)
+
+ it('file path including white space', function()
+ exec_lua("uri = 'file:///Foo%20/Bar/Baz.txt'")
+
+ eq('/Foo /Bar/Baz.txt', exec_lua("return vim.uri_to_fname(uri)"))
+ end)
+
+ it('file path including Unicode charactors', function()
+ local test_case = [[
+ local uri = 'file:///xy/%C3%A5%C3%A4%C3%B6/%C9%A7/%E6%B1%89%E8%AF%AD/%E2%86%A5/%F0%9F%A4%A6/%F0%9F%A6%84/a%CC%8A/%D8%A8%D9%90%D9%8A%D9%8E%D9%91.txt'
+ return vim.uri_to_fname(uri)
+ ]]
+
+ eq('/xy/åäö/ɧ/汉语/↥/🤦/🦄/å/بِيَّ.txt', exec_lua(test_case))
+ end)
+ end)
+
+ describe('decode Windows filepath', function()
+ it('file path includes only ascii charactors', function()
+ local test_case = [[
+ local uri = 'file:///C:/Foo/Bar/Baz.txt'
+ return vim.uri_to_fname(uri)
+ ]]
+
+ eq('C:\\Foo\\Bar\\Baz.txt', exec_lua(test_case))
+ end)
+
+ it('file path includes only ascii charactors with encoded colon character', function()
+ local test_case = [[
+ local uri = 'file:///C%3A/Foo/Bar/Baz.txt'
+ return vim.uri_to_fname(uri)
+ ]]
+
+ eq('C:\\Foo\\Bar\\Baz.txt', exec_lua(test_case))
+ end)
+
+ it('file path including white space', function()
+ local test_case = [[
+ local uri = 'file:///C:/Foo%20/Bar/Baz.txt'
+ return vim.uri_to_fname(uri)
+ ]]
+
+ eq('C:\\Foo \\Bar\\Baz.txt', exec_lua(test_case))
+ end)
+
+ it('file path including Unicode charactors', function()
+ local test_case = [[
+ local uri = 'file:///C:/xy/%C3%A5%C3%A4%C3%B6/%C9%A7/%E6%B1%89%E8%AF%AD/%E2%86%A5/%F0%9F%A4%A6/%F0%9F%A6%84/a%CC%8A/%D8%A8%D9%90%D9%8A%D9%8E%D9%91.txt'
+ return vim.uri_to_fname(uri)
+ ]]
+
+ eq('C:\\xy\\åäö\\ɧ\\汉语\\↥\\🤦\\🦄\\å\\بِيَّ.txt', exec_lua(test_case))
+ end)
+ end)
+
+ describe('decode non-file URI', function()
+ it('uri_to_fname returns non-file URI unchanged', function()
+ eq('jdt1.23+x-z://content/%5C/', exec_lua [[
+ return vim.uri_to_fname('jdt1.23+x-z://content/%5C/')
+ ]])
+ end)
+
+ it('uri_to_fname returns non-file upper-case scheme URI unchanged', function()
+ eq('JDT://content/%5C/', exec_lua [[
+ return vim.uri_to_fname('JDT://content/%5C/')
+ ]])
+ end)
+ end)
+
+ describe('decode URI without scheme', function()
+ it('fails because URI must have a scheme', function()
+ eq(false, exec_lua [[
+ return pcall(vim.uri_to_fname, 'not_an_uri.txt')
+ ]])
+ end)
+ end)
+
+ end)
+
+ describe('uri to bufnr', function()
+ it('uri_to_bufnr & uri_from_bufnr returns original uri for non-file uris', function()
+ local uri = 'jdt://contents/java.base/java.util/List.class?=sql/%5C/home%5C/user%5C/.jabba%5C/jdk%5C/openjdk%5C@1.14.0%5C/lib%5C/jrt-fs.jar%60java.base=/javadoc_location=/https:%5C/%5C/docs.oracle.com%5C/en%5C/java%5C/javase%5C/14%5C/docs%5C/api%5C/=/%3Cjava.util(List.class'
+ local test_case = string.format([[
+ local uri = '%s'
+ return vim.uri_from_bufnr(vim.uri_to_bufnr(uri))
+ ]], uri)
+ eq(uri, exec_lua(test_case))
+ end)
+ end)
+end)
diff --git a/test/functional/lua/utility_functions_spec.lua b/test/functional/lua/utility_functions_spec.lua
deleted file mode 100644
index ea2b1fc8a9..0000000000
--- a/test/functional/lua/utility_functions_spec.lua
+++ /dev/null
@@ -1,291 +0,0 @@
--- Test suite for testing interactions with API bindings
-local helpers = require('test.functional.helpers')(after_each)
-local Screen = require('test.functional.ui.screen')
-
-local funcs = helpers.funcs
-local clear = helpers.clear
-local eq = helpers.eq
-local eval = helpers.eval
-local feed = helpers.feed
-local pcall_err = helpers.pcall_err
-local exec_lua = helpers.exec_lua
-
-before_each(clear)
-
-describe('lua stdlib', function()
- -- İ: `tolower("İ")` is `i` which has length 1 while `İ` itself has
- -- length 2 (in bytes).
- -- Ⱥ: `tolower("Ⱥ")` is `ⱥ` which has length 2 while `Ⱥ` itself has
- -- length 3 (in bytes).
- --
- -- Note: 'i' !=? 'İ' and 'ⱥ' !=? 'Ⱥ' on some systems.
- -- Note: Built-in Nvim comparison (on systems lacking `strcasecmp`) works
- -- only on ASCII characters.
- it('vim.stricmp', function()
- eq(0, funcs.luaeval('vim.stricmp("a", "A")'))
- eq(0, funcs.luaeval('vim.stricmp("A", "a")'))
- eq(0, funcs.luaeval('vim.stricmp("a", "a")'))
- eq(0, funcs.luaeval('vim.stricmp("A", "A")'))
-
- eq(0, funcs.luaeval('vim.stricmp("", "")'))
- eq(0, funcs.luaeval('vim.stricmp("\\0", "\\0")'))
- eq(0, funcs.luaeval('vim.stricmp("\\0\\0", "\\0\\0")'))
- eq(0, funcs.luaeval('vim.stricmp("\\0\\0\\0", "\\0\\0\\0")'))
- eq(0, funcs.luaeval('vim.stricmp("\\0\\0\\0A", "\\0\\0\\0a")'))
- eq(0, funcs.luaeval('vim.stricmp("\\0\\0\\0a", "\\0\\0\\0A")'))
- eq(0, funcs.luaeval('vim.stricmp("\\0\\0\\0a", "\\0\\0\\0a")'))
-
- eq(0, funcs.luaeval('vim.stricmp("a\\0", "A\\0")'))
- eq(0, funcs.luaeval('vim.stricmp("A\\0", "a\\0")'))
- eq(0, funcs.luaeval('vim.stricmp("a\\0", "a\\0")'))
- eq(0, funcs.luaeval('vim.stricmp("A\\0", "A\\0")'))
-
- eq(0, funcs.luaeval('vim.stricmp("\\0a", "\\0A")'))
- eq(0, funcs.luaeval('vim.stricmp("\\0A", "\\0a")'))
- eq(0, funcs.luaeval('vim.stricmp("\\0a", "\\0a")'))
- eq(0, funcs.luaeval('vim.stricmp("\\0A", "\\0A")'))
-
- eq(0, funcs.luaeval('vim.stricmp("\\0a\\0", "\\0A\\0")'))
- eq(0, funcs.luaeval('vim.stricmp("\\0A\\0", "\\0a\\0")'))
- eq(0, funcs.luaeval('vim.stricmp("\\0a\\0", "\\0a\\0")'))
- eq(0, funcs.luaeval('vim.stricmp("\\0A\\0", "\\0A\\0")'))
-
- eq(-1, funcs.luaeval('vim.stricmp("a", "B")'))
- eq(-1, funcs.luaeval('vim.stricmp("A", "b")'))
- eq(-1, funcs.luaeval('vim.stricmp("a", "b")'))
- eq(-1, funcs.luaeval('vim.stricmp("A", "B")'))
-
- eq(-1, funcs.luaeval('vim.stricmp("", "\\0")'))
- eq(-1, funcs.luaeval('vim.stricmp("\\0", "\\0\\0")'))
- eq(-1, funcs.luaeval('vim.stricmp("\\0\\0", "\\0\\0\\0")'))
- eq(-1, funcs.luaeval('vim.stricmp("\\0\\0\\0A", "\\0\\0\\0b")'))
- eq(-1, funcs.luaeval('vim.stricmp("\\0\\0\\0a", "\\0\\0\\0B")'))
- eq(-1, funcs.luaeval('vim.stricmp("\\0\\0\\0a", "\\0\\0\\0b")'))
-
- eq(-1, funcs.luaeval('vim.stricmp("a\\0", "B\\0")'))
- eq(-1, funcs.luaeval('vim.stricmp("A\\0", "b\\0")'))
- eq(-1, funcs.luaeval('vim.stricmp("a\\0", "b\\0")'))
- eq(-1, funcs.luaeval('vim.stricmp("A\\0", "B\\0")'))
-
- eq(-1, funcs.luaeval('vim.stricmp("\\0a", "\\0B")'))
- eq(-1, funcs.luaeval('vim.stricmp("\\0A", "\\0b")'))
- eq(-1, funcs.luaeval('vim.stricmp("\\0a", "\\0b")'))
- eq(-1, funcs.luaeval('vim.stricmp("\\0A", "\\0B")'))
-
- eq(-1, funcs.luaeval('vim.stricmp("\\0a\\0", "\\0B\\0")'))
- eq(-1, funcs.luaeval('vim.stricmp("\\0A\\0", "\\0b\\0")'))
- eq(-1, funcs.luaeval('vim.stricmp("\\0a\\0", "\\0b\\0")'))
- eq(-1, funcs.luaeval('vim.stricmp("\\0A\\0", "\\0B\\0")'))
-
- eq(1, funcs.luaeval('vim.stricmp("c", "B")'))
- eq(1, funcs.luaeval('vim.stricmp("C", "b")'))
- eq(1, funcs.luaeval('vim.stricmp("c", "b")'))
- eq(1, funcs.luaeval('vim.stricmp("C", "B")'))
-
- eq(1, funcs.luaeval('vim.stricmp("\\0", "")'))
- eq(1, funcs.luaeval('vim.stricmp("\\0\\0", "\\0")'))
- eq(1, funcs.luaeval('vim.stricmp("\\0\\0\\0", "\\0\\0")'))
- eq(1, funcs.luaeval('vim.stricmp("\\0\\0\\0\\0", "\\0\\0\\0")'))
- eq(1, funcs.luaeval('vim.stricmp("\\0\\0\\0C", "\\0\\0\\0b")'))
- eq(1, funcs.luaeval('vim.stricmp("\\0\\0\\0c", "\\0\\0\\0B")'))
- eq(1, funcs.luaeval('vim.stricmp("\\0\\0\\0c", "\\0\\0\\0b")'))
-
- eq(1, funcs.luaeval('vim.stricmp("c\\0", "B\\0")'))
- eq(1, funcs.luaeval('vim.stricmp("C\\0", "b\\0")'))
- eq(1, funcs.luaeval('vim.stricmp("c\\0", "b\\0")'))
- eq(1, funcs.luaeval('vim.stricmp("C\\0", "B\\0")'))
-
- eq(1, funcs.luaeval('vim.stricmp("c\\0", "B")'))
- eq(1, funcs.luaeval('vim.stricmp("C\\0", "b")'))
- eq(1, funcs.luaeval('vim.stricmp("c\\0", "b")'))
- eq(1, funcs.luaeval('vim.stricmp("C\\0", "B")'))
-
- eq(1, funcs.luaeval('vim.stricmp("\\0c", "\\0B")'))
- eq(1, funcs.luaeval('vim.stricmp("\\0C", "\\0b")'))
- eq(1, funcs.luaeval('vim.stricmp("\\0c", "\\0b")'))
- eq(1, funcs.luaeval('vim.stricmp("\\0C", "\\0B")'))
-
- eq(1, funcs.luaeval('vim.stricmp("\\0c\\0", "\\0B\\0")'))
- eq(1, funcs.luaeval('vim.stricmp("\\0C\\0", "\\0b\\0")'))
- eq(1, funcs.luaeval('vim.stricmp("\\0c\\0", "\\0b\\0")'))
- eq(1, funcs.luaeval('vim.stricmp("\\0C\\0", "\\0B\\0")'))
- end)
-
- it("vim.str_utfindex/str_byteindex", function()
- exec_lua([[_G.test_text = "xy åäö ɧ 汉语 ↥ 🤦x🦄 å بِيَّ"]])
- local indicies32 = {[0]=0,1,2,3,5,7,9,10,12,13,16,19,20,23,24,28,29,33,34,35,37,38,40,42,44,46,48}
- local indicies16 = {[0]=0,1,2,3,5,7,9,10,12,13,16,19,20,23,24,28,28,29,33,33,34,35,37,38,40,42,44,46,48}
- for i,k in pairs(indicies32) do
- eq(k, exec_lua("return vim.str_byteindex(_G.test_text, ...)", i), i)
- end
- for i,k in pairs(indicies16) do
- eq(k, exec_lua("return vim.str_byteindex(_G.test_text, ..., true)", i), i)
- end
- local i32, i16 = 0, 0
- for k = 0,48 do
- if indicies32[i32] < k then
- i32 = i32 + 1
- end
- if indicies16[i16] < k then
- i16 = i16 + 1
- if indicies16[i16+1] == indicies16[i16] then
- i16 = i16 + 1
- end
- end
- eq({i32, i16}, exec_lua("return {vim.str_utfindex(_G.test_text, ...)}", k), k)
- end
- end)
-
- it("vim.schedule", function()
- exec_lua([[
- test_table = {}
- vim.schedule(function()
- table.insert(test_table, "xx")
- end)
- table.insert(test_table, "yy")
- ]])
- eq({"yy","xx"}, exec_lua("return test_table"))
-
- -- type checked args
- eq('Error executing lua: vim.schedule: expected function',
- pcall_err(exec_lua, "vim.schedule('stringly')"))
-
- eq('Error executing lua: vim.schedule: expected function',
- pcall_err(exec_lua, "vim.schedule()"))
-
- exec_lua([[
- vim.schedule(function()
- error("big failure\nvery async")
- end)
- ]])
-
- feed("<cr>")
- eq('Error executing vim.schedule lua callback: [string "<nvim>"]:2: big failure\nvery async', eval("v:errmsg"))
-
- local screen = Screen.new(60,5)
- screen:set_default_attr_ids({
- [1] = {bold = true, foreground = Screen.colors.Blue1},
- [2] = {bold = true, reverse = true},
- [3] = {foreground = Screen.colors.Grey100, background = Screen.colors.Red},
- [4] = {bold = true, foreground = Screen.colors.SeaGreen4},
- })
- screen:attach()
- screen:expect{grid=[[
- ^ |
- {1:~ }|
- {1:~ }|
- {1:~ }|
- |
- ]]}
-
- -- nvim_command causes a vimL exception, check that it is properly caught
- -- and propagated as an error message in async contexts.. #10809
- exec_lua([[
- vim.schedule(function()
- vim.api.nvim_command(":echo 'err")
- end)
- ]])
- screen:expect{grid=[[
- |
- {2: }|
- {3:Error executing vim.schedule lua callback: [string "<nvim>"]}|
- {3::2: Vim(echo):E115: Missing quote: 'err} |
- {4:Press ENTER or type command to continue}^ |
- ]]}
- end)
-
- it("vim.split", function()
- local split = function(str, sep)
- return exec_lua('return vim.split(...)', str, sep)
- end
-
- local tests = {
- { "a,b", ",", false, { 'a', 'b' } },
- { ":aa::bb:", ":", false, { '', 'aa', '', 'bb', '' } },
- { "::ee::ff:", ":", false, { '', '', 'ee', '', 'ff', '' } },
- { "ab", ".", false, { '', '', '' } },
- { "a1b2c", "[0-9]", false, { 'a', 'b', 'c' } },
- { "xy", "", false, { 'x', 'y' } },
- { "here be dragons", " ", false, { "here", "be", "dragons"} },
- { "axaby", "ab?", false, { '', 'x', 'y' } },
- { "f v2v v3v w2w ", "([vw])2%1", false, { 'f ', ' v3v ', ' ' } },
- { "x*yz*oo*l", "*", true, { 'x', 'yz', 'oo', 'l' } },
- }
-
- for _, t in ipairs(tests) do
- eq(t[4], split(t[1], t[2], t[3]))
- end
-
- local loops = {
- { "abc", ".-" },
- }
-
- for _, t in ipairs(loops) do
- local status, err = pcall(split, t[1], t[2])
- eq(false, status)
- assert(string.match(err, "Infinite loop detected"))
- end
- end)
-
- it('vim.trim', function()
- local trim = function(s)
- return exec_lua('return vim.trim(...)', s)
- end
-
- local trims = {
- { " a", "a" },
- { " b ", "b" },
- { "\tc" , "c" },
- { "r\n", "r" },
- }
-
- for _, t in ipairs(trims) do
- assert(t[2], trim(t[1]))
- end
-
- local status, err = pcall(trim, 2)
- eq(false, status)
- assert(string.match(err, "Only strings can be trimmed"))
- end)
-
- it('vim.inspect', function()
- -- just make sure it basically works, it has its own test suite
- local inspect = function(t, opts)
- return exec_lua('return vim.inspect(...)', t, opts)
- end
-
- eq('2', inspect(2))
- eq('{+a = {+b = 1+}+}',
- inspect({ a = { b = 1 } }, { newline = '+', indent = '' }))
-
- -- special value vim.inspect.KEY works
- eq('{ KEY_a = "x", KEY_b = "y"}', exec_lua([[
- return vim.inspect({a="x", b="y"}, {newline = '', process = function(item, path)
- if path[#path] == vim.inspect.KEY then
- return 'KEY_'..item
- end
- return item
- end})
- ]]))
- end)
-
- it("vim.deepcopy", function()
- local is_dc = exec_lua([[
- local a = { x = { 1, 2 }, y = 5}
- local b = vim.deepcopy(a)
-
- local count = 0
- for _ in pairs(b) do count = count + 1 end
-
- return b.x[1] == 1 and b.x[2] == 2 and b.y == 5 and count == 2
- and tostring(a) ~= tostring(b)
- ]])
-
- assert(is_dc)
- end)
-
- it('vim.pesc', function()
- eq('foo%-bar', exec_lua([[return vim.pesc('foo-bar')]]))
- eq('foo%%%-bar', exec_lua([[return vim.pesc(vim.pesc('foo-bar'))]]))
- end)
-end)
diff --git a/test/functional/lua/vim_spec.lua b/test/functional/lua/vim_spec.lua
new file mode 100644
index 0000000000..9b2697b3c2
--- /dev/null
+++ b/test/functional/lua/vim_spec.lua
@@ -0,0 +1,1235 @@
+-- Test suite for testing interactions with API bindings
+local helpers = require('test.functional.helpers')(after_each)
+local Screen = require('test.functional.ui.screen')
+
+local funcs = helpers.funcs
+local meths = helpers.meths
+local command = helpers.command
+local clear = helpers.clear
+local eq = helpers.eq
+local ok = helpers.ok
+local eval = helpers.eval
+local feed = helpers.feed
+local pcall_err = helpers.pcall_err
+local exec_lua = helpers.exec_lua
+local matches = helpers.matches
+local source = helpers.source
+local NIL = helpers.NIL
+local retry = helpers.retry
+
+before_each(clear)
+
+describe('lua stdlib', function()
+ -- İ: `tolower("İ")` is `i` which has length 1 while `İ` itself has
+ -- length 2 (in bytes).
+ -- Ⱥ: `tolower("Ⱥ")` is `ⱥ` which has length 2 while `Ⱥ` itself has
+ -- length 3 (in bytes).
+ --
+ -- Note: 'i' !=? 'İ' and 'ⱥ' !=? 'Ⱥ' on some systems.
+ -- Note: Built-in Nvim comparison (on systems lacking `strcasecmp`) works
+ -- only on ASCII characters.
+ it('vim.stricmp', function()
+ eq(0, funcs.luaeval('vim.stricmp("a", "A")'))
+ eq(0, funcs.luaeval('vim.stricmp("A", "a")'))
+ eq(0, funcs.luaeval('vim.stricmp("a", "a")'))
+ eq(0, funcs.luaeval('vim.stricmp("A", "A")'))
+
+ eq(0, funcs.luaeval('vim.stricmp("", "")'))
+ eq(0, funcs.luaeval('vim.stricmp("\\0", "\\0")'))
+ eq(0, funcs.luaeval('vim.stricmp("\\0\\0", "\\0\\0")'))
+ eq(0, funcs.luaeval('vim.stricmp("\\0\\0\\0", "\\0\\0\\0")'))
+ eq(0, funcs.luaeval('vim.stricmp("\\0\\0\\0A", "\\0\\0\\0a")'))
+ eq(0, funcs.luaeval('vim.stricmp("\\0\\0\\0a", "\\0\\0\\0A")'))
+ eq(0, funcs.luaeval('vim.stricmp("\\0\\0\\0a", "\\0\\0\\0a")'))
+
+ eq(0, funcs.luaeval('vim.stricmp("a\\0", "A\\0")'))
+ eq(0, funcs.luaeval('vim.stricmp("A\\0", "a\\0")'))
+ eq(0, funcs.luaeval('vim.stricmp("a\\0", "a\\0")'))
+ eq(0, funcs.luaeval('vim.stricmp("A\\0", "A\\0")'))
+
+ eq(0, funcs.luaeval('vim.stricmp("\\0a", "\\0A")'))
+ eq(0, funcs.luaeval('vim.stricmp("\\0A", "\\0a")'))
+ eq(0, funcs.luaeval('vim.stricmp("\\0a", "\\0a")'))
+ eq(0, funcs.luaeval('vim.stricmp("\\0A", "\\0A")'))
+
+ eq(0, funcs.luaeval('vim.stricmp("\\0a\\0", "\\0A\\0")'))
+ eq(0, funcs.luaeval('vim.stricmp("\\0A\\0", "\\0a\\0")'))
+ eq(0, funcs.luaeval('vim.stricmp("\\0a\\0", "\\0a\\0")'))
+ eq(0, funcs.luaeval('vim.stricmp("\\0A\\0", "\\0A\\0")'))
+
+ eq(-1, funcs.luaeval('vim.stricmp("a", "B")'))
+ eq(-1, funcs.luaeval('vim.stricmp("A", "b")'))
+ eq(-1, funcs.luaeval('vim.stricmp("a", "b")'))
+ eq(-1, funcs.luaeval('vim.stricmp("A", "B")'))
+
+ eq(-1, funcs.luaeval('vim.stricmp("", "\\0")'))
+ eq(-1, funcs.luaeval('vim.stricmp("\\0", "\\0\\0")'))
+ eq(-1, funcs.luaeval('vim.stricmp("\\0\\0", "\\0\\0\\0")'))
+ eq(-1, funcs.luaeval('vim.stricmp("\\0\\0\\0A", "\\0\\0\\0b")'))
+ eq(-1, funcs.luaeval('vim.stricmp("\\0\\0\\0a", "\\0\\0\\0B")'))
+ eq(-1, funcs.luaeval('vim.stricmp("\\0\\0\\0a", "\\0\\0\\0b")'))
+
+ eq(-1, funcs.luaeval('vim.stricmp("a\\0", "B\\0")'))
+ eq(-1, funcs.luaeval('vim.stricmp("A\\0", "b\\0")'))
+ eq(-1, funcs.luaeval('vim.stricmp("a\\0", "b\\0")'))
+ eq(-1, funcs.luaeval('vim.stricmp("A\\0", "B\\0")'))
+
+ eq(-1, funcs.luaeval('vim.stricmp("\\0a", "\\0B")'))
+ eq(-1, funcs.luaeval('vim.stricmp("\\0A", "\\0b")'))
+ eq(-1, funcs.luaeval('vim.stricmp("\\0a", "\\0b")'))
+ eq(-1, funcs.luaeval('vim.stricmp("\\0A", "\\0B")'))
+
+ eq(-1, funcs.luaeval('vim.stricmp("\\0a\\0", "\\0B\\0")'))
+ eq(-1, funcs.luaeval('vim.stricmp("\\0A\\0", "\\0b\\0")'))
+ eq(-1, funcs.luaeval('vim.stricmp("\\0a\\0", "\\0b\\0")'))
+ eq(-1, funcs.luaeval('vim.stricmp("\\0A\\0", "\\0B\\0")'))
+
+ eq(1, funcs.luaeval('vim.stricmp("c", "B")'))
+ eq(1, funcs.luaeval('vim.stricmp("C", "b")'))
+ eq(1, funcs.luaeval('vim.stricmp("c", "b")'))
+ eq(1, funcs.luaeval('vim.stricmp("C", "B")'))
+
+ eq(1, funcs.luaeval('vim.stricmp("\\0", "")'))
+ eq(1, funcs.luaeval('vim.stricmp("\\0\\0", "\\0")'))
+ eq(1, funcs.luaeval('vim.stricmp("\\0\\0\\0", "\\0\\0")'))
+ eq(1, funcs.luaeval('vim.stricmp("\\0\\0\\0\\0", "\\0\\0\\0")'))
+ eq(1, funcs.luaeval('vim.stricmp("\\0\\0\\0C", "\\0\\0\\0b")'))
+ eq(1, funcs.luaeval('vim.stricmp("\\0\\0\\0c", "\\0\\0\\0B")'))
+ eq(1, funcs.luaeval('vim.stricmp("\\0\\0\\0c", "\\0\\0\\0b")'))
+
+ eq(1, funcs.luaeval('vim.stricmp("c\\0", "B\\0")'))
+ eq(1, funcs.luaeval('vim.stricmp("C\\0", "b\\0")'))
+ eq(1, funcs.luaeval('vim.stricmp("c\\0", "b\\0")'))
+ eq(1, funcs.luaeval('vim.stricmp("C\\0", "B\\0")'))
+
+ eq(1, funcs.luaeval('vim.stricmp("c\\0", "B")'))
+ eq(1, funcs.luaeval('vim.stricmp("C\\0", "b")'))
+ eq(1, funcs.luaeval('vim.stricmp("c\\0", "b")'))
+ eq(1, funcs.luaeval('vim.stricmp("C\\0", "B")'))
+
+ eq(1, funcs.luaeval('vim.stricmp("\\0c", "\\0B")'))
+ eq(1, funcs.luaeval('vim.stricmp("\\0C", "\\0b")'))
+ eq(1, funcs.luaeval('vim.stricmp("\\0c", "\\0b")'))
+ eq(1, funcs.luaeval('vim.stricmp("\\0C", "\\0B")'))
+
+ eq(1, funcs.luaeval('vim.stricmp("\\0c\\0", "\\0B\\0")'))
+ eq(1, funcs.luaeval('vim.stricmp("\\0C\\0", "\\0b\\0")'))
+ eq(1, funcs.luaeval('vim.stricmp("\\0c\\0", "\\0b\\0")'))
+ eq(1, funcs.luaeval('vim.stricmp("\\0C\\0", "\\0B\\0")'))
+ end)
+
+ it('vim.startswith', function()
+ eq(true, funcs.luaeval('vim.startswith("123", "1")'))
+ eq(true, funcs.luaeval('vim.startswith("123", "")'))
+ eq(true, funcs.luaeval('vim.startswith("123", "123")'))
+ eq(true, funcs.luaeval('vim.startswith("", "")'))
+
+ eq(false, funcs.luaeval('vim.startswith("123", " ")'))
+ eq(false, funcs.luaeval('vim.startswith("123", "2")'))
+ eq(false, funcs.luaeval('vim.startswith("123", "1234")'))
+
+ eq("string", type(pcall_err(funcs.luaeval, 'vim.startswith("123", nil)')))
+ eq("string", type(pcall_err(funcs.luaeval, 'vim.startswith(nil, "123")')))
+ end)
+
+ it('vim.endswith', function()
+ eq(true, funcs.luaeval('vim.endswith("123", "3")'))
+ eq(true, funcs.luaeval('vim.endswith("123", "")'))
+ eq(true, funcs.luaeval('vim.endswith("123", "123")'))
+ eq(true, funcs.luaeval('vim.endswith("", "")'))
+
+ eq(false, funcs.luaeval('vim.endswith("123", " ")'))
+ eq(false, funcs.luaeval('vim.endswith("123", "2")'))
+ eq(false, funcs.luaeval('vim.endswith("123", "1234")'))
+
+ eq("string", type(pcall_err(funcs.luaeval, 'vim.endswith("123", nil)')))
+ eq("string", type(pcall_err(funcs.luaeval, 'vim.endswith(nil, "123")')))
+ end)
+
+ it("vim.str_utfindex/str_byteindex", function()
+ exec_lua([[_G.test_text = "xy åäö ɧ 汉语 ↥ 🤦x🦄 å بِيَّ"]])
+ local indicies32 = {[0]=0,1,2,3,5,7,9,10,12,13,16,19,20,23,24,28,29,33,34,35,37,38,40,42,44,46,48}
+ local indicies16 = {[0]=0,1,2,3,5,7,9,10,12,13,16,19,20,23,24,28,28,29,33,33,34,35,37,38,40,42,44,46,48}
+ for i,k in pairs(indicies32) do
+ eq(k, exec_lua("return vim.str_byteindex(_G.test_text, ...)", i), i)
+ end
+ for i,k in pairs(indicies16) do
+ eq(k, exec_lua("return vim.str_byteindex(_G.test_text, ..., true)", i), i)
+ end
+ local i32, i16 = 0, 0
+ for k = 0,48 do
+ if indicies32[i32] < k then
+ i32 = i32 + 1
+ end
+ if indicies16[i16] < k then
+ i16 = i16 + 1
+ if indicies16[i16+1] == indicies16[i16] then
+ i16 = i16 + 1
+ end
+ end
+ eq({i32, i16}, exec_lua("return {vim.str_utfindex(_G.test_text, ...)}", k), k)
+ end
+ end)
+
+ it("vim.schedule", function()
+ exec_lua([[
+ test_table = {}
+ vim.schedule(function()
+ table.insert(test_table, "xx")
+ end)
+ table.insert(test_table, "yy")
+ ]])
+ eq({"yy","xx"}, exec_lua("return test_table"))
+
+ -- Validates args.
+ eq('Error executing lua: vim.schedule: expected function',
+ pcall_err(exec_lua, "vim.schedule('stringly')"))
+ eq('Error executing lua: vim.schedule: expected function',
+ pcall_err(exec_lua, "vim.schedule()"))
+
+ exec_lua([[
+ vim.schedule(function()
+ error("big failure\nvery async")
+ end)
+ ]])
+
+ feed("<cr>")
+ eq('Error executing vim.schedule lua callback: [string "<nvim>"]:2: big failure\nvery async', eval("v:errmsg"))
+
+ local screen = Screen.new(60,5)
+ screen:set_default_attr_ids({
+ [1] = {bold = true, foreground = Screen.colors.Blue1},
+ [2] = {bold = true, reverse = true},
+ [3] = {foreground = Screen.colors.Grey100, background = Screen.colors.Red},
+ [4] = {bold = true, foreground = Screen.colors.SeaGreen4},
+ })
+ screen:attach()
+ screen:expect{grid=[[
+ ^ |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ |
+ ]]}
+
+ -- nvim_command causes a vimL exception, check that it is properly caught
+ -- and propagated as an error message in async contexts.. #10809
+ exec_lua([[
+ vim.schedule(function()
+ vim.api.nvim_command(":echo 'err")
+ end)
+ ]])
+ screen:expect{grid=[[
+ |
+ {2: }|
+ {3:Error executing vim.schedule lua callback: [string "<nvim>"]}|
+ {3::2: Vim(echo):E115: Missing quote: 'err} |
+ {4:Press ENTER or type command to continue}^ |
+ ]]}
+ end)
+
+ it("vim.split", function()
+ local split = function(str, sep, plain)
+ return exec_lua('return vim.split(...)', str, sep, plain)
+ end
+
+ local tests = {
+ { "a,b", ",", false, { 'a', 'b' } },
+ { ":aa::bb:", ":", false, { '', 'aa', '', 'bb', '' } },
+ { "::ee::ff:", ":", false, { '', '', 'ee', '', 'ff', '' } },
+ { "ab", ".", false, { '', '', '' } },
+ { "a1b2c", "[0-9]", false, { 'a', 'b', 'c' } },
+ { "xy", "", false, { 'x', 'y' } },
+ { "here be dragons", " ", false, { "here", "be", "dragons"} },
+ { "axaby", "ab?", false, { '', 'x', 'y' } },
+ { "f v2v v3v w2w ", "([vw])2%1", false, { 'f ', ' v3v ', ' ' } },
+ { "", "", false, {} },
+ { "", "a", false, { '' } },
+ { "x*yz*oo*l", "*", true, { 'x', 'yz', 'oo', 'l' } },
+ }
+
+ for _, t in ipairs(tests) do
+ eq(t[4], split(t[1], t[2], t[3]))
+ end
+
+ local loops = {
+ { "abc", ".-" },
+ }
+
+ for _, t in ipairs(loops) do
+ matches(".*Infinite loop detected", pcall_err(split, t[1], t[2]))
+ end
+
+ -- Validates args.
+ eq(true, pcall(split, 'string', 'string'))
+ eq('Error executing lua: .../shared.lua: s: expected string, got number',
+ pcall_err(split, 1, 'string'))
+ eq('Error executing lua: .../shared.lua: sep: expected string, got number',
+ pcall_err(split, 'string', 1))
+ eq('Error executing lua: .../shared.lua: plain: expected boolean, got number',
+ pcall_err(split, 'string', 'string', 1))
+ end)
+
+ it('vim.trim', function()
+ local trim = function(s)
+ return exec_lua('return vim.trim(...)', s)
+ end
+
+ local trims = {
+ { " a", "a" },
+ { " b ", "b" },
+ { "\tc" , "c" },
+ { "r\n", "r" },
+ }
+
+ for _, t in ipairs(trims) do
+ assert(t[2], trim(t[1]))
+ end
+
+ -- Validates args.
+ eq('Error executing lua: .../shared.lua: s: expected string, got number',
+ pcall_err(trim, 2))
+ end)
+
+ it('vim.inspect', function()
+ -- just make sure it basically works, it has its own test suite
+ local inspect = function(t, opts)
+ return exec_lua('return vim.inspect(...)', t, opts)
+ end
+
+ eq('2', inspect(2))
+ eq('{+a = {+b = 1+}+}',
+ inspect({ a = { b = 1 } }, { newline = '+', indent = '' }))
+
+ -- special value vim.inspect.KEY works
+ eq('{ KEY_a = "x", KEY_b = "y"}', exec_lua([[
+ return vim.inspect({a="x", b="y"}, {newline = '', process = function(item, path)
+ if path[#path] == vim.inspect.KEY then
+ return 'KEY_'..item
+ end
+ return item
+ end})
+ ]]))
+ end)
+
+ it("vim.deepcopy", function()
+ ok(exec_lua([[
+ local a = { x = { 1, 2 }, y = 5}
+ local b = vim.deepcopy(a)
+
+ return b.x[1] == 1 and b.x[2] == 2 and b.y == 5 and vim.tbl_count(b) == 2
+ and tostring(a) ~= tostring(b)
+ ]]))
+
+ ok(exec_lua([[
+ local a = {}
+ local b = vim.deepcopy(a)
+
+ return vim.tbl_islist(b) and vim.tbl_count(b) == 0 and tostring(a) ~= tostring(b)
+ ]]))
+
+ ok(exec_lua([[
+ local a = vim.empty_dict()
+ local b = vim.deepcopy(a)
+
+ return not vim.tbl_islist(b) and vim.tbl_count(b) == 0
+ ]]))
+
+ ok(exec_lua([[
+ local a = {x = vim.empty_dict(), y = {}}
+ local b = vim.deepcopy(a)
+
+ return not vim.tbl_islist(b.x) and vim.tbl_islist(b.y)
+ and vim.tbl_count(b) == 2
+ and tostring(a) ~= tostring(b)
+ ]]))
+
+ ok(exec_lua([[
+ local f1 = function() return 1 end
+ local f2 = function() return 2 end
+ local t1 = {f = f1}
+ local t2 = vim.deepcopy(t1)
+ t1.f = f2
+ return t1.f() ~= t2.f()
+ ]]))
+
+ eq('Error executing lua: .../shared.lua: Cannot deepcopy object of type thread',
+ pcall_err(exec_lua, [[
+ local thread = coroutine.create(function () return 0 end)
+ local t = {thr = thread}
+ vim.deepcopy(t)
+ ]]))
+ end)
+
+ it('vim.pesc', function()
+ eq('foo%-bar', exec_lua([[return vim.pesc('foo-bar')]]))
+ eq('foo%%%-bar', exec_lua([[return vim.pesc(vim.pesc('foo-bar'))]]))
+
+ -- Validates args.
+ eq('Error executing lua: .../shared.lua: s: expected string, got number',
+ pcall_err(exec_lua, [[return vim.pesc(2)]]))
+ end)
+
+ it('vim.tbl_keys', function()
+ eq({}, exec_lua("return vim.tbl_keys({})"))
+ for _, v in pairs(exec_lua("return vim.tbl_keys({'a', 'b', 'c'})")) do
+ eq(true, exec_lua("return vim.tbl_contains({ 1, 2, 3 }, ...)", v))
+ end
+ for _, v in pairs(exec_lua("return vim.tbl_keys({a=1, b=2, c=3})")) do
+ eq(true, exec_lua("return vim.tbl_contains({ 'a', 'b', 'c' }, ...)", v))
+ end
+ end)
+
+ it('vim.tbl_values', function()
+ eq({}, exec_lua("return vim.tbl_values({})"))
+ for _, v in pairs(exec_lua("return vim.tbl_values({'a', 'b', 'c'})")) do
+ eq(true, exec_lua("return vim.tbl_contains({ 'a', 'b', 'c' }, ...)", v))
+ end
+ for _, v in pairs(exec_lua("return vim.tbl_values({a=1, b=2, c=3})")) do
+ eq(true, exec_lua("return vim.tbl_contains({ 1, 2, 3 }, ...)", v))
+ end
+ end)
+
+ it('vim.tbl_map', function()
+ eq({}, exec_lua([[
+ return vim.tbl_map(function(v) return v * 2 end, {})
+ ]]))
+ eq({2, 4, 6}, exec_lua([[
+ return vim.tbl_map(function(v) return v * 2 end, {1, 2, 3})
+ ]]))
+ eq({{i=2}, {i=4}, {i=6}}, exec_lua([[
+ return vim.tbl_map(function(v) return { i = v.i * 2 } end, {{i=1}, {i=2}, {i=3}})
+ ]]))
+ end)
+
+ it('vim.tbl_filter', function()
+ eq({}, exec_lua([[
+ return vim.tbl_filter(function(v) return (v % 2) == 0 end, {})
+ ]]))
+ eq({2}, exec_lua([[
+ return vim.tbl_filter(function(v) return (v % 2) == 0 end, {1, 2, 3})
+ ]]))
+ eq({{i=2}}, exec_lua([[
+ return vim.tbl_filter(function(v) return (v.i % 2) == 0 end, {{i=1}, {i=2}, {i=3}})
+ ]]))
+ end)
+
+ it('vim.tbl_islist', function()
+ eq(true, exec_lua("return vim.tbl_islist({})"))
+ eq(false, exec_lua("return vim.tbl_islist(vim.empty_dict())"))
+ eq(true, exec_lua("return vim.tbl_islist({'a', 'b', 'c'})"))
+ eq(false, exec_lua("return vim.tbl_islist({'a', '32', a='hello', b='baz'})"))
+ eq(false, exec_lua("return vim.tbl_islist({1, a='hello', b='baz'})"))
+ eq(false, exec_lua("return vim.tbl_islist({a='hello', b='baz', 1})"))
+ eq(false, exec_lua("return vim.tbl_islist({1, 2, nil, a='hello'})"))
+ end)
+
+ it('vim.tbl_isempty', function()
+ eq(true, exec_lua("return vim.tbl_isempty({})"))
+ eq(false, exec_lua("return vim.tbl_isempty({ 1, 2, 3 })"))
+ eq(false, exec_lua("return vim.tbl_isempty({a=1, b=2, c=3})"))
+ end)
+
+ it('vim.tbl_extend', function()
+ ok(exec_lua([[
+ local a = {x = 1}
+ local b = {y = 2}
+ local c = vim.tbl_extend("keep", a, b)
+
+ return c.x == 1 and b.y == 2 and vim.tbl_count(c) == 2
+ ]]))
+
+ ok(exec_lua([[
+ local a = {x = 1}
+ local b = {y = 2}
+ local c = {z = 3}
+ local d = vim.tbl_extend("keep", a, b, c)
+
+ return d.x == 1 and d.y == 2 and d.z == 3 and vim.tbl_count(d) == 3
+ ]]))
+
+ ok(exec_lua([[
+ local a = {x = 1}
+ local b = {x = 3}
+ local c = vim.tbl_extend("keep", a, b)
+
+ return c.x == 1 and vim.tbl_count(c) == 1
+ ]]))
+
+ ok(exec_lua([[
+ local a = {x = 1}
+ local b = {x = 3}
+ local c = vim.tbl_extend("force", a, b)
+
+ return c.x == 3 and vim.tbl_count(c) == 1
+ ]]))
+
+ ok(exec_lua([[
+ local a = vim.empty_dict()
+ local b = {}
+ local c = vim.tbl_extend("keep", a, b)
+
+ return not vim.tbl_islist(c) and vim.tbl_count(c) == 0
+ ]]))
+
+ ok(exec_lua([[
+ local a = {}
+ local b = vim.empty_dict()
+ local c = vim.tbl_extend("keep", a, b)
+
+ return vim.tbl_islist(c) and vim.tbl_count(c) == 0
+ ]]))
+
+ ok(exec_lua([[
+ local a = {x = {a = 1, b = 2}}
+ local b = {x = {a = 2, c = {y = 3}}}
+ local c = vim.tbl_extend("keep", a, b)
+
+ local count = 0
+ for _ in pairs(c) do count = count + 1 end
+
+ return c.x.a == 1 and c.x.b == 2 and c.x.c == nil and count == 1
+ ]]))
+
+ eq('Error executing lua: .../shared.lua: invalid "behavior": nil',
+ pcall_err(exec_lua, [[
+ return vim.tbl_extend()
+ ]])
+ )
+
+ eq('Error executing lua: .../shared.lua: wrong number of arguments (given 1, expected at least 3)',
+ pcall_err(exec_lua, [[
+ return vim.tbl_extend("keep")
+ ]])
+ )
+
+ eq('Error executing lua: .../shared.lua: wrong number of arguments (given 2, expected at least 3)',
+ pcall_err(exec_lua, [[
+ return vim.tbl_extend("keep", {})
+ ]])
+ )
+ end)
+
+ it('vim.tbl_deep_extend', function()
+ ok(exec_lua([[
+ local a = {x = {a = 1, b = 2}}
+ local b = {x = {a = 2, c = {y = 3}}}
+ local c = vim.tbl_deep_extend("keep", a, b)
+
+ local count = 0
+ for _ in pairs(c) do count = count + 1 end
+
+ return c.x.a == 1 and c.x.b == 2 and c.x.c.y == 3 and count == 1
+ ]]))
+
+ ok(exec_lua([[
+ local a = {x = {a = 1, b = 2}}
+ local b = {x = {a = 2, c = {y = 3}}}
+ local c = vim.tbl_deep_extend("force", a, b)
+
+ local count = 0
+ for _ in pairs(c) do count = count + 1 end
+
+ return c.x.a == 2 and c.x.b == 2 and c.x.c.y == 3 and count == 1
+ ]]))
+
+ ok(exec_lua([[
+ local a = {x = {a = 1, b = 2}}
+ local b = {x = {a = 2, c = {y = 3}}}
+ local c = {x = {c = 4, d = {y = 4}}}
+ local d = vim.tbl_deep_extend("keep", a, b, c)
+
+ local count = 0
+ for _ in pairs(c) do count = count + 1 end
+
+ return d.x.a == 1 and d.x.b == 2 and d.x.c.y == 3 and d.x.d.y == 4 and count == 1
+ ]]))
+
+ ok(exec_lua([[
+ local a = {x = {a = 1, b = 2}}
+ local b = {x = {a = 2, c = {y = 3}}}
+ local c = {x = {c = 4, d = {y = 4}}}
+ local d = vim.tbl_deep_extend("force", a, b, c)
+
+ local count = 0
+ for _ in pairs(c) do count = count + 1 end
+
+ return d.x.a == 2 and d.x.b == 2 and d.x.c == 4 and d.x.d.y == 4 and count == 1
+ ]]))
+
+ ok(exec_lua([[
+ local a = vim.empty_dict()
+ local b = {}
+ local c = vim.tbl_deep_extend("keep", a, b)
+
+ local count = 0
+ for _ in pairs(c) do count = count + 1 end
+
+ return not vim.tbl_islist(c) and count == 0
+ ]]))
+
+ ok(exec_lua([[
+ local a = {}
+ local b = vim.empty_dict()
+ local c = vim.tbl_deep_extend("keep", a, b)
+
+ local count = 0
+ for _ in pairs(c) do count = count + 1 end
+
+ return vim.tbl_islist(c) and count == 0
+ ]]))
+
+ eq('Error executing lua: .../shared.lua: invalid "behavior": nil',
+ pcall_err(exec_lua, [[
+ return vim.tbl_deep_extend()
+ ]])
+ )
+
+ eq('Error executing lua: .../shared.lua: wrong number of arguments (given 1, expected at least 3)',
+ pcall_err(exec_lua, [[
+ return vim.tbl_deep_extend("keep")
+ ]])
+ )
+
+ eq('Error executing lua: .../shared.lua: wrong number of arguments (given 2, expected at least 3)',
+ pcall_err(exec_lua, [[
+ return vim.tbl_deep_extend("keep", {})
+ ]])
+ )
+ end)
+
+ it('vim.tbl_count', function()
+ eq(0, exec_lua [[ return vim.tbl_count({}) ]])
+ eq(0, exec_lua [[ return vim.tbl_count(vim.empty_dict()) ]])
+ eq(0, exec_lua [[ return vim.tbl_count({nil}) ]])
+ eq(0, exec_lua [[ return vim.tbl_count({a=nil}) ]])
+ eq(1, exec_lua [[ return vim.tbl_count({1}) ]])
+ eq(2, exec_lua [[ return vim.tbl_count({1, 2}) ]])
+ eq(2, exec_lua [[ return vim.tbl_count({1, nil, 3}) ]])
+ eq(1, exec_lua [[ return vim.tbl_count({a=1}) ]])
+ eq(2, exec_lua [[ return vim.tbl_count({a=1, b=2}) ]])
+ eq(2, exec_lua [[ return vim.tbl_count({a=1, b=nil, c=3}) ]])
+ end)
+
+ it('vim.deep_equal', function()
+ eq(true, exec_lua [[ return vim.deep_equal({a=1}, {a=1}) ]])
+ eq(true, exec_lua [[ return vim.deep_equal({a={b=1}}, {a={b=1}}) ]])
+ eq(true, exec_lua [[ return vim.deep_equal({a={b={nil}}}, {a={b={}}}) ]])
+ eq(true, exec_lua [[ return vim.deep_equal({a=1, [5]=5}, {nil,nil,nil,nil,5,a=1}) ]])
+ eq(false, exec_lua [[ return vim.deep_equal(1, {nil,nil,nil,nil,5,a=1}) ]])
+ eq(false, exec_lua [[ return vim.deep_equal(1, 3) ]])
+ eq(false, exec_lua [[ return vim.deep_equal(nil, 3) ]])
+ eq(false, exec_lua [[ return vim.deep_equal({a=1}, {a=2}) ]])
+ end)
+
+ it('vim.list_extend', function()
+ eq({1,2,3}, exec_lua [[ return vim.list_extend({1}, {2,3}) ]])
+ eq('Error executing lua: .../shared.lua: src: expected table, got nil',
+ pcall_err(exec_lua, [[ return vim.list_extend({1}, nil) ]]))
+ eq({1,2}, exec_lua [[ return vim.list_extend({1}, {2;a=1}) ]])
+ eq(true, exec_lua [[ local a = {1} return vim.list_extend(a, {2;a=1}) == a ]])
+ eq({2}, exec_lua [[ return vim.list_extend({}, {2;a=1}, 1) ]])
+ eq({}, exec_lua [[ return vim.list_extend({}, {2;a=1}, 2) ]])
+ eq({}, exec_lua [[ return vim.list_extend({}, {2;a=1}, 1, -1) ]])
+ eq({2}, exec_lua [[ return vim.list_extend({}, {2;a=1}, -1, 2) ]])
+ end)
+
+ it('vim.tbl_add_reverse_lookup', function()
+ eq(true, exec_lua [[
+ local a = { A = 1 }
+ vim.tbl_add_reverse_lookup(a)
+ return vim.deep_equal(a, { A = 1; [1] = 'A'; })
+ ]])
+ -- Throw an error for trying to do it twice (run into an existing key)
+ local code = [[
+ local res = {}
+ local a = { A = 1 }
+ vim.tbl_add_reverse_lookup(a)
+ assert(vim.deep_equal(a, { A = 1; [1] = 'A'; }))
+ vim.tbl_add_reverse_lookup(a)
+ ]]
+ matches('Error executing lua: .../shared.lua: The reverse lookup found an existing value for "[1A]" while processing key "[1A]"',
+ pcall_err(exec_lua, code))
+ end)
+
+ it('vim.call, vim.fn', function()
+ eq(true, exec_lua([[return vim.call('sin', 0.0) == 0.0 ]]))
+ eq(true, exec_lua([[return vim.fn.sin(0.0) == 0.0 ]]))
+ -- compat: nvim_call_function uses "special" value for vimL float
+ eq(false, exec_lua([[return vim.api.nvim_call_function('sin', {0.0}) == 0.0 ]]))
+
+ source([[
+ func! FooFunc(test)
+ let g:test = a:test
+ return {}
+ endfunc
+ func! VarArg(...)
+ return a:000
+ endfunc
+ func! Nilly()
+ return [v:null, v:null]
+ endfunc
+ ]])
+ eq(true, exec_lua([[return next(vim.fn.FooFunc(3)) == nil ]]))
+ eq(3, eval("g:test"))
+ -- compat: nvim_call_function uses "special" value for empty dict
+ eq(true, exec_lua([[return next(vim.api.nvim_call_function("FooFunc", {5})) == true ]]))
+ eq(5, eval("g:test"))
+
+ eq({2, "foo", true}, exec_lua([[return vim.fn.VarArg(2, "foo", true)]]))
+
+ eq(true, exec_lua([[
+ local x = vim.fn.Nilly()
+ return #x == 2 and x[1] == vim.NIL and x[2] == vim.NIL
+ ]]))
+ eq({NIL, NIL}, exec_lua([[return vim.fn.Nilly()]]))
+
+ -- error handling
+ eq({false, 'Vim:E714: List required'}, exec_lua([[return {pcall(vim.fn.add, "aa", "bb")}]]))
+ end)
+
+ it('vim.rpcrequest and vim.rpcnotify', function()
+ exec_lua([[
+ chan = vim.fn.jobstart({'cat'}, {rpc=true})
+ vim.rpcrequest(chan, 'nvim_set_current_line', 'meow')
+ ]])
+ eq('meow', meths.get_current_line())
+ command("let x = [3, 'aa', v:true, v:null]")
+ eq(true, exec_lua([[
+ ret = vim.rpcrequest(chan, 'nvim_get_var', 'x')
+ return #ret == 4 and ret[1] == 3 and ret[2] == 'aa' and ret[3] == true and ret[4] == vim.NIL
+ ]]))
+ eq({3, 'aa', true, NIL}, exec_lua([[return ret]]))
+
+ eq({{}, {}, false, true}, exec_lua([[
+ vim.rpcrequest(chan, 'nvim_exec', 'let xx = {}\nlet yy = []', false)
+ local dict = vim.rpcrequest(chan, 'nvim_eval', 'xx')
+ local list = vim.rpcrequest(chan, 'nvim_eval', 'yy')
+ return {dict, list, vim.tbl_islist(dict), vim.tbl_islist(list)}
+ ]]))
+
+ exec_lua([[
+ vim.rpcrequest(chan, 'nvim_set_var', 'aa', {})
+ vim.rpcrequest(chan, 'nvim_set_var', 'bb', vim.empty_dict())
+ ]])
+ eq({1, 1}, eval('[type(g:aa) == type([]), type(g:bb) == type({})]'))
+
+ -- error handling
+ eq({false, 'Invalid channel: 23'},
+ exec_lua([[return {pcall(vim.rpcrequest, 23, 'foo')}]]))
+ eq({false, 'Invalid channel: 23'},
+ exec_lua([[return {pcall(vim.rpcnotify, 23, 'foo')}]]))
+
+ eq({false, 'Vim:E121: Undefined variable: foobar'},
+ exec_lua([[return {pcall(vim.rpcrequest, chan, 'nvim_eval', "foobar")}]]))
+
+
+ -- rpcnotify doesn't wait on request
+ eq('meow', exec_lua([[
+ vim.rpcnotify(chan, 'nvim_set_current_line', 'foo')
+ return vim.api.nvim_get_current_line()
+ ]]))
+ retry(10, nil, function()
+ eq('foo', meths.get_current_line())
+ end)
+
+ local screen = Screen.new(50,7)
+ screen:set_default_attr_ids({
+ [1] = {bold = true, foreground = Screen.colors.Blue1},
+ [2] = {bold = true, reverse = true},
+ [3] = {foreground = Screen.colors.Grey100, background = Screen.colors.Red},
+ [4] = {bold = true, foreground = Screen.colors.SeaGreen4},
+ })
+ screen:attach()
+ exec_lua([[
+ timer = vim.loop.new_timer()
+ timer:start(20, 0, function ()
+ -- notify ok (executed later when safe)
+ vim.rpcnotify(chan, 'nvim_set_var', 'yy', {3, vim.NIL})
+ -- rpcrequest an error
+ vim.rpcrequest(chan, 'nvim_set_current_line', 'bork')
+ end)
+ ]])
+ screen:expect{grid=[[
+ foo |
+ {1:~ }|
+ {2: }|
+ {3:Error executing luv callback:} |
+ {3:[string "<nvim>"]:6: E5560: rpcrequest must not be}|
+ {3: called in a lua loop callback} |
+ {4:Press ENTER or type command to continue}^ |
+ ]]}
+ feed('<cr>')
+ eq({3, NIL}, meths.get_var('yy'))
+
+ exec_lua([[timer:close()]])
+ end)
+
+ it('vim.empty_dict()', function()
+ eq({true, false, true, true}, exec_lua([[
+ vim.api.nvim_set_var('listy', {})
+ vim.api.nvim_set_var('dicty', vim.empty_dict())
+ local listy = vim.fn.eval("listy")
+ local dicty = vim.fn.eval("dicty")
+ return {vim.tbl_islist(listy), vim.tbl_islist(dicty), next(listy) == nil, next(dicty) == nil}
+ ]]))
+
+ -- vim.empty_dict() gives new value each time
+ -- equality is not overriden (still by ref)
+ -- non-empty table uses the usual heuristics (ignores the tag)
+ eq({false, {"foo"}, {namey="bar"}}, exec_lua([[
+ local aa = vim.empty_dict()
+ local bb = vim.empty_dict()
+ local equally = (aa == bb)
+ aa[1] = "foo"
+ bb["namey"] = "bar"
+ return {equally, aa, bb}
+ ]]))
+
+ eq("{ {}, vim.empty_dict() }", exec_lua("return vim.inspect({{}, vim.empty_dict()})"))
+ eq('{}', exec_lua([[ return vim.fn.json_encode(vim.empty_dict()) ]]))
+ eq('{"a": {}, "b": []}', exec_lua([[ return vim.fn.json_encode({a=vim.empty_dict(), b={}}) ]]))
+ end)
+
+ it('vim.validate', function()
+ exec_lua("vim.validate{arg1={{}, 'table' }}")
+ exec_lua("vim.validate{arg1={{}, 't' }}")
+ exec_lua("vim.validate{arg1={nil, 't', true }}")
+ exec_lua("vim.validate{arg1={{ foo='foo' }, 't' }}")
+ exec_lua("vim.validate{arg1={{ 'foo' }, 't' }}")
+ exec_lua("vim.validate{arg1={'foo', 'string' }}")
+ exec_lua("vim.validate{arg1={'foo', 's' }}")
+ exec_lua("vim.validate{arg1={'', 's' }}")
+ exec_lua("vim.validate{arg1={nil, 's', true }}")
+ exec_lua("vim.validate{arg1={1, 'number' }}")
+ exec_lua("vim.validate{arg1={1, 'n' }}")
+ exec_lua("vim.validate{arg1={0, 'n' }}")
+ exec_lua("vim.validate{arg1={0.1, 'n' }}")
+ exec_lua("vim.validate{arg1={nil, 'n', true }}")
+ exec_lua("vim.validate{arg1={true, 'boolean' }}")
+ exec_lua("vim.validate{arg1={true, 'b' }}")
+ exec_lua("vim.validate{arg1={false, 'b' }}")
+ exec_lua("vim.validate{arg1={nil, 'b', true }}")
+ exec_lua("vim.validate{arg1={function()end, 'function' }}")
+ exec_lua("vim.validate{arg1={function()end, 'f' }}")
+ exec_lua("vim.validate{arg1={nil, 'f', true }}")
+ exec_lua("vim.validate{arg1={nil, 'nil' }}")
+ exec_lua("vim.validate{arg1={nil, 'nil', true }}")
+ exec_lua("vim.validate{arg1={coroutine.create(function()end), 'thread' }}")
+ exec_lua("vim.validate{arg1={nil, 'thread', true }}")
+ exec_lua("vim.validate{arg1={{}, 't' }, arg2={ 'foo', 's' }}")
+ exec_lua("vim.validate{arg1={2, function(a) return (a % 2) == 0 end, 'even number' }}")
+
+ eq("Error executing lua: .../shared.lua: 1: expected table, got number",
+ pcall_err(exec_lua, "vim.validate{ 1, 'x' }"))
+ eq("Error executing lua: .../shared.lua: invalid type name: x",
+ pcall_err(exec_lua, "vim.validate{ arg1={ 1, 'x' }}"))
+ eq("Error executing lua: .../shared.lua: invalid type name: 1",
+ pcall_err(exec_lua, "vim.validate{ arg1={ 1, 1 }}"))
+ eq("Error executing lua: .../shared.lua: invalid type name: nil",
+ pcall_err(exec_lua, "vim.validate{ arg1={ 1 }}"))
+
+ -- Validated parameters are required by default.
+ eq("Error executing lua: .../shared.lua: arg1: expected string, got nil",
+ pcall_err(exec_lua, "vim.validate{ arg1={ nil, 's' }}"))
+ -- Explicitly required.
+ eq("Error executing lua: .../shared.lua: arg1: expected string, got nil",
+ pcall_err(exec_lua, "vim.validate{ arg1={ nil, 's', false }}"))
+
+ eq("Error executing lua: .../shared.lua: arg1: expected table, got number",
+ pcall_err(exec_lua, "vim.validate{arg1={1, 't'}}"))
+ eq("Error executing lua: .../shared.lua: arg2: expected string, got number",
+ pcall_err(exec_lua, "vim.validate{arg1={{}, 't'}, arg2={1, 's'}}"))
+ eq("Error executing lua: .../shared.lua: arg2: expected string, got nil",
+ pcall_err(exec_lua, "vim.validate{arg1={{}, 't'}, arg2={nil, 's'}}"))
+ eq("Error executing lua: .../shared.lua: arg2: expected string, got nil",
+ pcall_err(exec_lua, "vim.validate{arg1={{}, 't'}, arg2={nil, 's'}}"))
+ eq("Error executing lua: .../shared.lua: arg1: expected even number, got 3",
+ pcall_err(exec_lua, "vim.validate{arg1={3, function(a) return a == 1 end, 'even number'}}"))
+ eq("Error executing lua: .../shared.lua: arg1: expected ?, got 3",
+ pcall_err(exec_lua, "vim.validate{arg1={3, function(a) return a == 1 end}}"))
+ end)
+
+ it('vim.is_callable', function()
+ eq(true, exec_lua("return vim.is_callable(function()end)"))
+ eq(true, exec_lua([[
+ local meta = { __call = function()end }
+ local function new_callable()
+ return setmetatable({}, meta)
+ end
+ local callable = new_callable()
+ return vim.is_callable(callable)
+ ]]))
+
+ eq(false, exec_lua("return vim.is_callable(1)"))
+ eq(false, exec_lua("return vim.is_callable('foo')"))
+ eq(false, exec_lua("return vim.is_callable({})"))
+ end)
+
+ it('vim.g', function()
+ exec_lua [[
+ vim.api.nvim_set_var("testing", "hi")
+ vim.api.nvim_set_var("other", 123)
+ vim.api.nvim_set_var("to_delete", {hello="world"})
+ ]]
+
+ eq('hi', funcs.luaeval "vim.g.testing")
+ eq(123, funcs.luaeval "vim.g.other")
+ eq(NIL, funcs.luaeval "vim.g.nonexistant")
+
+ eq({hello="world"}, funcs.luaeval "vim.g.to_delete")
+ exec_lua [[
+ vim.g.to_delete = nil
+ ]]
+ eq(NIL, funcs.luaeval "vim.g.to_delete")
+ end)
+
+ it('vim.b', function()
+ exec_lua [[
+ vim.api.nvim_buf_set_var(0, "testing", "hi")
+ vim.api.nvim_buf_set_var(0, "other", 123)
+ vim.api.nvim_buf_set_var(0, "to_delete", {hello="world"})
+ ]]
+
+ eq('hi', funcs.luaeval "vim.b.testing")
+ eq(123, funcs.luaeval "vim.b.other")
+ eq(NIL, funcs.luaeval "vim.b.nonexistant")
+
+ eq({hello="world"}, funcs.luaeval "vim.b.to_delete")
+ exec_lua [[
+ vim.b.to_delete = nil
+ ]]
+ eq(NIL, funcs.luaeval "vim.b.to_delete")
+
+ exec_lua [[
+ vim.cmd "vnew"
+ ]]
+
+ eq(NIL, funcs.luaeval "vim.b.testing")
+ eq(NIL, funcs.luaeval "vim.b.other")
+ eq(NIL, funcs.luaeval "vim.b.nonexistant")
+ end)
+
+ it('vim.w', function()
+ exec_lua [[
+ vim.api.nvim_win_set_var(0, "testing", "hi")
+ vim.api.nvim_win_set_var(0, "other", 123)
+ vim.api.nvim_win_set_var(0, "to_delete", {hello="world"})
+ ]]
+
+ eq('hi', funcs.luaeval "vim.w.testing")
+ eq(123, funcs.luaeval "vim.w.other")
+ eq(NIL, funcs.luaeval "vim.w.nonexistant")
+
+ eq({hello="world"}, funcs.luaeval "vim.w.to_delete")
+ exec_lua [[
+ vim.w.to_delete = nil
+ ]]
+ eq(NIL, funcs.luaeval "vim.w.to_delete")
+
+ exec_lua [[
+ vim.cmd "vnew"
+ ]]
+
+ eq(NIL, funcs.luaeval "vim.w.testing")
+ eq(NIL, funcs.luaeval "vim.w.other")
+ eq(NIL, funcs.luaeval "vim.w.nonexistant")
+ end)
+
+ it('vim.t', function()
+ exec_lua [[
+ vim.api.nvim_tabpage_set_var(0, "testing", "hi")
+ vim.api.nvim_tabpage_set_var(0, "other", 123)
+ vim.api.nvim_tabpage_set_var(0, "to_delete", {hello="world"})
+ ]]
+
+ eq('hi', funcs.luaeval "vim.t.testing")
+ eq(123, funcs.luaeval "vim.t.other")
+ eq(NIL, funcs.luaeval "vim.t.nonexistant")
+
+ eq({hello="world"}, funcs.luaeval "vim.t.to_delete")
+ exec_lua [[
+ vim.t.to_delete = nil
+ ]]
+ eq(NIL, funcs.luaeval "vim.t.to_delete")
+
+ exec_lua [[
+ vim.cmd "tabnew"
+ ]]
+
+ eq(NIL, funcs.luaeval "vim.t.testing")
+ eq(NIL, funcs.luaeval "vim.t.other")
+ eq(NIL, funcs.luaeval "vim.t.nonexistant")
+ end)
+
+ it('vim.env', function()
+ exec_lua [[
+ vim.fn.setenv("A", 123)
+ ]]
+ eq('123', funcs.luaeval "vim.env.A")
+ eq(true, funcs.luaeval "vim.env.B == nil")
+ end)
+
+ it('vim.v', function()
+ eq(funcs.luaeval "vim.api.nvim_get_vvar('progpath')", funcs.luaeval "vim.v.progpath")
+ eq(false, funcs.luaeval "vim.v['false']")
+ eq(NIL, funcs.luaeval "vim.v.null")
+ end)
+
+ it('vim.bo', function()
+ eq('', funcs.luaeval "vim.bo.filetype")
+ exec_lua [[
+ vim.api.nvim_buf_set_option(0, "filetype", "markdown")
+ BUF = vim.api.nvim_create_buf(false, true)
+ vim.api.nvim_buf_set_option(BUF, "modifiable", false)
+ ]]
+ eq(false, funcs.luaeval "vim.bo.modified")
+ eq('markdown', funcs.luaeval "vim.bo.filetype")
+ eq(false, funcs.luaeval "vim.bo[BUF].modifiable")
+ exec_lua [[
+ vim.bo.filetype = ''
+ vim.bo[BUF].modifiable = true
+ ]]
+ eq('', funcs.luaeval "vim.bo.filetype")
+ eq(true, funcs.luaeval "vim.bo[BUF].modifiable")
+ matches("^Error executing lua: .*: Invalid option name: 'nosuchopt'$",
+ pcall_err(exec_lua, 'return vim.bo.nosuchopt'))
+ matches("^Error executing lua: .*: Expected lua string$",
+ pcall_err(exec_lua, 'return vim.bo[0][0].autoread'))
+ end)
+
+ it('vim.wo', function()
+ exec_lua [[
+ vim.api.nvim_win_set_option(0, "cole", 2)
+ vim.cmd "split"
+ vim.api.nvim_win_set_option(0, "cole", 2)
+ ]]
+ eq(2, funcs.luaeval "vim.wo.cole")
+ exec_lua [[
+ vim.wo.conceallevel = 0
+ ]]
+ eq(0, funcs.luaeval "vim.wo.cole")
+ eq(0, funcs.luaeval "vim.wo[0].cole")
+ eq(0, funcs.luaeval "vim.wo[1001].cole")
+ matches("^Error executing lua: .*: Invalid option name: 'notanopt'$",
+ pcall_err(exec_lua, 'return vim.wo.notanopt'))
+ matches("^Error executing lua: .*: Expected lua string$",
+ pcall_err(exec_lua, 'return vim.wo[0][0].list'))
+ eq(2, funcs.luaeval "vim.wo[1000].cole")
+ exec_lua [[
+ vim.wo[1000].cole = 0
+ ]]
+ eq(0, funcs.luaeval "vim.wo[1000].cole")
+ end)
+
+ it('vim.cmd', function()
+ exec_lua [[
+ vim.cmd "autocmd BufNew * ++once lua BUF = vim.fn.expand('<abuf>')"
+ vim.cmd "new"
+ ]]
+ eq('2', funcs.luaeval "BUF")
+ eq(2, funcs.luaeval "#vim.api.nvim_list_bufs()")
+ end)
+
+ it('vim.regex', function()
+ exec_lua [[
+ re1 = vim.regex"ab\\+c"
+ vim.cmd "set nomagic ignorecase"
+ re2 = vim.regex"xYz"
+ ]]
+ eq({}, exec_lua[[return {re1:match_str("x ac")}]])
+ eq({3,7}, exec_lua[[return {re1:match_str("ac abbc")}]])
+
+ meths.buf_set_lines(0, 0, -1, true, {"yy", "abc abbc"})
+ eq({}, exec_lua[[return {re1:match_line(0, 0)}]])
+ eq({0,3}, exec_lua[[return {re1:match_line(0, 1)}]])
+ eq({3,7}, exec_lua[[return {re1:match_line(0, 1, 1)}]])
+ eq({3,7}, exec_lua[[return {re1:match_line(0, 1, 1, 8)}]])
+ eq({}, exec_lua[[return {re1:match_line(0, 1, 1, 7)}]])
+ eq({0,3}, exec_lua[[return {re1:match_line(0, 1, 0, 7)}]])
+ end)
+
+ it('vim.defer_fn', function()
+ eq(false, exec_lua [[
+ vim.g.test = false
+ vim.defer_fn(function() vim.g.test = true end, 150)
+ return vim.g.test
+ ]])
+ exec_lua [[vim.wait(1000, function() return vim.g.test end)]]
+ eq(true, exec_lua[[return vim.g.test]])
+ end)
+
+ it('vim.region', function()
+ helpers.insert(helpers.dedent( [[
+ text tααt tααt text
+ text tαxt txtα tex
+ text tαxt tαxt
+ ]]))
+ eq({5,15}, exec_lua[[ return vim.region(0,{1,5},{1,14},'v',true)[1] ]])
+ end)
+
+ describe('vim.wait', function()
+ before_each(function()
+ exec_lua[[
+ -- high precision timer
+ get_time = function()
+ return vim.fn.reltimefloat(vim.fn.reltime())
+ end
+ ]]
+ end)
+
+ it('should run from lua', function()
+ exec_lua[[vim.wait(100, function() return true end)]]
+ end)
+
+ it('should wait the expected time if false', function()
+ eq({time = true, wait_result = {false, -1}}, exec_lua[[
+ start_time = get_time()
+ wait_succeed, wait_fail_val = vim.wait(200, function() return false end)
+
+ return {
+ -- 150ms waiting or more results in true. Flaky tests are bad.
+ time = (start_time + 0.15) < get_time(),
+ wait_result = {wait_succeed, wait_fail_val}
+ }
+ ]])
+ end)
+
+
+ it('should not block other events', function()
+ eq({time = true, wait_result = true}, exec_lua[[
+ start_time = get_time()
+
+ vim.g.timer_result = false
+ timer = vim.loop.new_timer()
+ timer:start(100, 0, vim.schedule_wrap(function()
+ vim.g.timer_result = true
+ end))
+
+ -- Would wait ten seconds if results blocked.
+ wait_result = vim.wait(10000, function() return vim.g.timer_result end)
+
+ return {
+ time = (start_time + 5) > get_time(),
+ wait_result = wait_result,
+ }
+ ]])
+ end)
+
+ it('should work with vim.defer_fn', function()
+ eq({time = true, wait_result = true}, exec_lua[[
+ start_time = get_time()
+
+ vim.defer_fn(function() vim.g.timer_result = true end, 100)
+ wait_result = vim.wait(10000, function() return vim.g.timer_result end)
+
+ return {
+ time = (start_time + 5) > get_time(),
+ wait_result = wait_result,
+ }
+ ]])
+ end)
+
+ it('should require functions to be passed', function()
+ local pcall_result = exec_lua [[
+ return {pcall(function() vim.wait(1000, 13) end)}
+ ]]
+
+ eq(pcall_result[1], false)
+ matches('condition must be a function', pcall_result[2])
+ end)
+
+ it('should not crash when callback errors', function()
+ local pcall_result = exec_lua [[
+ return {pcall(function() vim.wait(1000, function() error("As Expected") end) end)}
+ ]]
+
+ eq(pcall_result[1], false)
+ matches('As Expected', pcall_result[2])
+ end)
+
+ it('should call callbacks exactly once if they return true immediately', function()
+ eq(true, exec_lua [[
+ vim.g.wait_count = 0
+ vim.wait(1000, function()
+ vim.g.wait_count = vim.g.wait_count + 1
+ return true
+ end, 20)
+ return vim.g.wait_count == 1
+ ]])
+ end)
+
+ it('should call callbacks few times with large `interval`', function()
+ eq(true, exec_lua [[
+ vim.g.wait_count = 0
+ vim.wait(50, function() vim.g.wait_count = vim.g.wait_count + 1 end, 200)
+ return vim.g.wait_count < 5
+ ]])
+ end)
+
+ it('should play nice with `not` when fails', function()
+ eq(true, exec_lua [[
+ if not vim.wait(50, function() end) then
+ return true
+ end
+
+ return false
+ ]])
+ end)
+
+ it('should play nice with `if` when success', function()
+ eq(true, exec_lua [[
+ if vim.wait(50, function() return true end) then
+ return true
+ end
+
+ return false
+ ]])
+ end)
+
+ it('should return immediately with false if timeout is 0', function()
+ eq({false, -1}, exec_lua [[
+ return {
+ vim.wait(0, function() return false end)
+ }
+ ]])
+ end)
+
+ it('should work with tables with __call', function()
+ eq(true, exec_lua [[
+ local t = setmetatable({}, {__call = function(...) return true end})
+ return vim.wait(100, t, 10)
+ ]])
+ end)
+
+ it('should work with tables with __call that change', function()
+ eq(true, exec_lua [[
+ local t = {count = 0}
+ setmetatable(t, {
+ __call = function()
+ t.count = t.count + 1
+ return t.count > 3
+ end
+ })
+
+ return vim.wait(1000, t, 10)
+ ]])
+ end)
+
+ it('should not work with negative intervals', function()
+ local pcall_result = exec_lua [[
+ return pcall(function() vim.wait(1000, function() return false end, -1) end)
+ ]]
+
+ eq(false, pcall_result)
+ end)
+
+ it('should not work with weird intervals', function()
+ local pcall_result = exec_lua [[
+ return pcall(function() vim.wait(1000, function() return false end, 'a string value') end)
+ ]]
+
+ eq(false, pcall_result)
+ end)
+ end)
+end)
diff --git a/test/functional/normal/jump_spec.lua b/test/functional/normal/jump_spec.lua
index 5bed541752..9e7158e2f7 100644
--- a/test/functional/normal/jump_spec.lua
+++ b/test/functional/normal/jump_spec.lua
@@ -5,6 +5,7 @@ local command = helpers.command
local eq = helpers.eq
local funcs = helpers.funcs
local feed = helpers.feed
+local redir_exec = helpers.redir_exec
local write_file = helpers.write_file
describe('jumplist', function()
@@ -46,3 +47,93 @@ describe('jumplist', function()
eq(buf1, funcs.bufnr('%'))
end)
end)
+
+describe("jumpoptions=stack behaves like 'tagstack'", function()
+ before_each(function()
+ clear()
+ feed(':clearjumps<cr>')
+
+ -- Add lines so that we have locations to jump to.
+ for i = 1,101,1
+ do
+ feed('iLine ' .. i .. '<cr><esc>')
+ end
+
+ -- Jump around to add some locations to the jump list.
+ feed('0gg')
+ feed('10gg')
+ feed('20gg')
+ feed('30gg')
+ feed('40gg')
+ feed('50gg')
+
+ feed(':set jumpoptions=stack<cr>')
+ end)
+
+ after_each(function()
+ feed('set jumpoptions=')
+ end)
+
+ it('discards the tail when navigating from the middle', function()
+ feed('<C-O>')
+ feed('<C-O>')
+
+ eq( '\n'
+ .. ' jump line col file/text\n'
+ .. ' 4 102 0 \n'
+ .. ' 3 1 0 Line 1\n'
+ .. ' 2 10 0 Line 10\n'
+ .. ' 1 20 0 Line 20\n'
+ .. '> 0 30 0 Line 30\n'
+ .. ' 1 40 0 Line 40\n'
+ .. ' 2 50 0 Line 50',
+ redir_exec('jumps'))
+
+ feed('90gg')
+
+ eq( '\n'
+ .. ' jump line col file/text\n'
+ .. ' 5 102 0 \n'
+ .. ' 4 1 0 Line 1\n'
+ .. ' 3 10 0 Line 10\n'
+ .. ' 2 20 0 Line 20\n'
+ .. ' 1 30 0 Line 30\n'
+ .. '>',
+ redir_exec('jumps'))
+ end)
+
+ it('does not add the same location twice adjacently', function()
+ feed('60gg')
+ feed('60gg')
+
+ eq( '\n'
+ .. ' jump line col file/text\n'
+ .. ' 7 102 0 \n'
+ .. ' 6 1 0 Line 1\n'
+ .. ' 5 10 0 Line 10\n'
+ .. ' 4 20 0 Line 20\n'
+ .. ' 3 30 0 Line 30\n'
+ .. ' 2 40 0 Line 40\n'
+ .. ' 1 50 0 Line 50\n'
+ .. '>',
+ redir_exec('jumps'))
+ end)
+
+ it('does add the same location twice nonadjacently', function()
+ feed('10gg')
+ feed('20gg')
+
+ eq( '\n'
+ .. ' jump line col file/text\n'
+ .. ' 8 102 0 \n'
+ .. ' 7 1 0 Line 1\n'
+ .. ' 6 10 0 Line 10\n'
+ .. ' 5 20 0 Line 20\n'
+ .. ' 4 30 0 Line 30\n'
+ .. ' 3 40 0 Line 40\n'
+ .. ' 2 50 0 Line 50\n'
+ .. ' 1 10 0 Line 10\n'
+ .. '>',
+ redir_exec('jumps'))
+ end)
+end)
diff --git a/test/functional/normal/put_spec.lua b/test/functional/normal/put_spec.lua
index 40a4f051e3..26967ecbba 100644
--- a/test/functional/normal/put_spec.lua
+++ b/test/functional/normal/put_spec.lua
@@ -6,8 +6,8 @@ local insert = helpers.insert
local feed = helpers.feed
local expect = helpers.expect
local eq = helpers.eq
-local map = helpers.map
-local filter = helpers.filter
+local map = helpers.tbl_map
+local filter = helpers.tbl_filter
local feed_command = helpers.feed_command
local curbuf_contents = helpers.curbuf_contents
local funcs = helpers.funcs
@@ -307,7 +307,7 @@ describe('put command', function()
-- }}}
-- Conversion functions {{{
- local function convert_characterwise(expect_base, conversion_table,
+ local function convert_charwise(expect_base, conversion_table,
virtualedit_end, visual_put)
expect_base = dedent(expect_base)
-- There is no difference between 'P' and 'p' when VIsual_active
@@ -335,7 +335,7 @@ describe('put command', function()
expect_base = expect_base:gsub('(test_stringx?)"', '%1.')
end
return expect_base
- end -- convert_characterwise()
+ end -- convert_charwise()
local function make_back(string)
local prev_line
@@ -500,7 +500,7 @@ describe('put command', function()
local function run_normal_mode_tests(test_string, base_map, extra_setup,
virtualedit_end, selection_string)
local function convert_closure(e, c)
- return convert_characterwise(e, c, virtualedit_end, selection_string)
+ return convert_charwise(e, c, virtualedit_end, selection_string)
end
local function expect_normal_creator(expect_base, conversion_table)
local test_expect = expect_creator(convert_closure, expect_base, conversion_table)
diff --git a/test/functional/normal/tabpage_spec.lua b/test/functional/normal/tabpage_spec.lua
new file mode 100644
index 0000000000..d1d6854b07
--- /dev/null
+++ b/test/functional/normal/tabpage_spec.lua
@@ -0,0 +1,38 @@
+local helpers = require('test.functional.helpers')(after_each)
+
+local clear = helpers.clear
+local command = helpers.command
+local eq = helpers.eq
+local feed = helpers.feed
+local eval = helpers.eval
+
+describe('tabpage', function()
+ before_each(clear)
+
+ it('advances to the next page via <C-W>gt', function()
+ -- add some tabpages
+ command('tabnew')
+ command('tabnew')
+ command('tabnew')
+
+ eq(4, eval('tabpagenr()'))
+
+ feed('<C-W>gt')
+
+ eq(1, eval('tabpagenr()'))
+ end)
+
+ it('retreats to the previous page via <C-W>gT', function()
+ -- add some tabpages
+ command('tabnew')
+ command('tabnew')
+ command('tabnew')
+
+ eq(4, eval('tabpagenr()'))
+
+ feed('<C-W>gT')
+
+ eq(3, eval('tabpagenr()'))
+ end)
+end)
+
diff --git a/test/functional/options/chars_spec.lua b/test/functional/options/chars_spec.lua
index 1330c29e61..5439ca3dba 100644
--- a/test/functional/options/chars_spec.lua
+++ b/test/functional/options/chars_spec.lua
@@ -16,10 +16,6 @@ describe("'fillchars'", function()
screen:attach()
end)
- after_each(function()
- screen:detach()
- end)
-
local function shouldfail(val,errval)
errval = errval or val
eq('Vim(set):E474: Invalid argument: fillchars='..errval,
@@ -71,16 +67,29 @@ describe("'fillchars'", function()
shouldfail('eob:xy') -- two ascii chars
shouldfail('eob:\255', 'eob:<ff>') -- invalid UTF-8
end)
- it('is local to window', function()
- clear()
- screen = Screen.new(50, 5)
- screen:attach()
+ it('has global value', function()
+ screen:try_resize(50, 5)
insert("foo\nbar")
command('set laststatus=0')
command('1,2fold')
command('vsplit')
command('set fillchars=fold:x')
screen:expect([[
+ ^+-- 2 lines: fooxxxxxxxx│+-- 2 lines: fooxxxxxxx|
+ ~ │~ |
+ ~ │~ |
+ ~ │~ |
+ |
+ ]])
+ end)
+ it('has local window value', function()
+ screen:try_resize(50, 5)
+ insert("foo\nbar")
+ command('set laststatus=0')
+ command('1,2fold')
+ command('vsplit')
+ command('setl fillchars=fold:x')
+ screen:expect([[
^+-- 2 lines: fooxxxxxxxx│+-- 2 lines: foo·······|
~ │~ |
~ │~ |
@@ -100,16 +109,25 @@ describe("'listchars'", function()
screen:attach()
end)
- after_each(function()
- screen:detach()
+ it('has global value', function()
+ feed('i<tab><tab><tab><esc>')
+ command('set list laststatus=0')
+ command('vsplit')
+ command('set listchars=tab:<->')
+ screen:expect([[
+ <------><------>^<------> │<------><------><------>|
+ ~ │~ |
+ ~ │~ |
+ ~ │~ |
+ |
+ ]])
end)
-
- it('is local to window', function()
+ it('has value local to window', function()
feed('i<tab><tab><tab><esc>')
- command('set laststatus=0')
- command('set list listchars=tab:<->')
+ command('set list laststatus=0')
+ command('setl listchars=tab:<->')
command('vsplit')
- command('set listchars&')
+ command('setl listchars<')
screen:expect([[
> > ^> │<------><------><------>|
~ │~ |
diff --git a/test/functional/options/defaults_spec.lua b/test/functional/options/defaults_spec.lua
index 490a04186d..11ce26410d 100644
--- a/test/functional/options/defaults_spec.lua
+++ b/test/functional/options/defaults_spec.lua
@@ -224,9 +224,6 @@ describe('startup defaults', function()
XDG_DATA_HOME=xdgdir,
NVIM_LOG_FILE='', -- Empty is invalid.
}})
- -- server_start() calls ELOG, which tickles log_path_init().
- pcall(command, 'call serverstart(serverlist()[0])')
-
eq(xdgdir..'/'..datasubdir..'/log', string.gsub(eval('$NVIM_LOG_FILE'), '\\', '/'))
end)
it('defaults to stdpath("data")/log if invalid', function()
@@ -235,9 +232,6 @@ describe('startup defaults', function()
XDG_DATA_HOME=xdgdir,
NVIM_LOG_FILE='.', -- Any directory is invalid.
}})
- -- server_start() calls ELOG, which tickles log_path_init().
- pcall(command, 'call serverstart(serverlist()[0])')
-
eq(xdgdir..'/'..datasubdir..'/log', string.gsub(eval('$NVIM_LOG_FILE'), '\\', '/'))
end)
it('defaults to .nvimlog if stdpath("data") is invalid', function()
@@ -245,9 +239,6 @@ describe('startup defaults', function()
XDG_DATA_HOME='Xtest-missing-xdg-dir',
NVIM_LOG_FILE='.', -- Any directory is invalid.
}})
- -- server_start() calls ELOG, which tickles log_path_init().
- pcall(command, 'call serverstart(serverlist()[0])')
-
eq('.nvimlog', eval('$NVIM_LOG_FILE'))
end)
end)
@@ -302,6 +293,14 @@ describe('XDG-based defaults', function()
-- TODO(jkeyes): tests below fail on win32 because of path separator.
if helpers.pending_win32(pending) then return end
+ local function vimruntime_and_libdir()
+ local vimruntime = eval('$VIMRUNTIME')
+ -- libdir is hard to calculate reliably across various ci platforms
+ -- local libdir = string.gsub(vimruntime, "share/nvim/runtime$", "lib/nvim")
+ local libdir = meths._get_lib_dir()
+ return vimruntime, libdir
+ end
+
describe('with too long XDG variables', function()
before_each(function()
clear({env={
@@ -317,6 +316,8 @@ describe('XDG-based defaults', function()
end)
it('are correctly set', function()
+ local vimruntime, libdir = vimruntime_and_libdir()
+
eq((('/x'):rep(4096) .. '/nvim'
.. ',' .. ('/a'):rep(2048) .. '/nvim'
.. ',' .. ('/b'):rep(2048) .. '/nvim'
@@ -325,7 +326,8 @@ describe('XDG-based defaults', function()
.. ',' .. ('/A'):rep(2048) .. '/nvim/site'
.. ',' .. ('/B'):rep(2048) .. '/nvim/site'
.. (',' .. '/C/nvim/site'):rep(512)
- .. ',' .. eval('$VIMRUNTIME')
+ .. ',' .. vimruntime
+ .. ',' .. libdir
.. (',' .. '/C/nvim/site/after'):rep(512)
.. ',' .. ('/B'):rep(2048) .. '/nvim/site/after'
.. ',' .. ('/A'):rep(2048) .. '/nvim/site/after'
@@ -348,7 +350,8 @@ describe('XDG-based defaults', function()
.. ',' .. ('/A'):rep(2048) .. '/nvim/site'
.. ',' .. ('/B'):rep(2048) .. '/nvim/site'
.. (',' .. '/C/nvim/site'):rep(512)
- .. ',' .. eval('$VIMRUNTIME')
+ .. ',' .. vimruntime
+ .. ',' .. libdir
.. (',' .. '/C/nvim/site/after'):rep(512)
.. ',' .. ('/B'):rep(2048) .. '/nvim/site/after'
.. ',' .. ('/A'):rep(2048) .. '/nvim/site/after'
@@ -377,11 +380,13 @@ describe('XDG-based defaults', function()
end)
it('are not expanded', function()
+ local vimruntime, libdir = vimruntime_and_libdir()
eq(('$XDG_DATA_HOME/nvim'
.. ',$XDG_DATA_DIRS/nvim'
.. ',$XDG_CONFIG_HOME/nvim/site'
.. ',$XDG_CONFIG_DIRS/nvim/site'
- .. ',' .. eval('$VIMRUNTIME')
+ .. ',' .. vimruntime
+ .. ',' .. libdir
.. ',$XDG_CONFIG_DIRS/nvim/site/after'
.. ',$XDG_CONFIG_HOME/nvim/site/after'
.. ',$XDG_DATA_DIRS/nvim/after'
@@ -396,7 +401,8 @@ describe('XDG-based defaults', function()
.. ',$XDG_DATA_DIRS/nvim'
.. ',$XDG_CONFIG_HOME/nvim/site'
.. ',$XDG_CONFIG_DIRS/nvim/site'
- .. ',' .. eval('$VIMRUNTIME')
+ .. ',' .. vimruntime
+ .. ',' .. libdir
.. ',$XDG_CONFIG_DIRS/nvim/site/after'
.. ',$XDG_CONFIG_HOME/nvim/site/after'
.. ',$XDG_DATA_DIRS/nvim/after'
@@ -411,7 +417,8 @@ describe('XDG-based defaults', function()
.. ',$XDG_DATA_DIRS/nvim'
.. ',$XDG_CONFIG_HOME/nvim/site'
.. ',$XDG_CONFIG_DIRS/nvim/site'
- .. ',' .. eval('$VIMRUNTIME')
+ .. ',' .. vimruntime
+ .. ',' .. libdir
.. ',$XDG_CONFIG_DIRS/nvim/site/after'
.. ',$XDG_CONFIG_HOME/nvim/site/after'
.. ',$XDG_DATA_DIRS/nvim/after'
@@ -435,13 +442,15 @@ describe('XDG-based defaults', function()
end)
it('are escaped properly', function()
+ local vimruntime, libdir = vimruntime_and_libdir()
eq(('\\, \\, \\,/nvim'
.. ',\\,-\\,-\\,/nvim'
.. ',-\\,-\\,-/nvim'
.. ',\\,=\\,=\\,/nvim/site'
.. ',\\,≡\\,≡\\,/nvim/site'
.. ',≡\\,≡\\,≡/nvim/site'
- .. ',' .. eval('$VIMRUNTIME')
+ .. ',' .. vimruntime
+ .. ',' .. libdir
.. ',≡\\,≡\\,≡/nvim/site/after'
.. ',\\,≡\\,≡\\,/nvim/site/after'
.. ',\\,=\\,=\\,/nvim/site/after'
@@ -460,7 +469,8 @@ describe('XDG-based defaults', function()
.. ',\\,=\\,=\\,/nvim/site'
.. ',\\,≡\\,≡\\,/nvim/site'
.. ',≡\\,≡\\,≡/nvim/site'
- .. ',' .. eval('$VIMRUNTIME')
+ .. ',' .. vimruntime
+ .. ',' .. libdir
.. ',≡\\,≡\\,≡/nvim/site/after'
.. ',\\,≡\\,≡\\,/nvim/site/after'
.. ',\\,=\\,=\\,/nvim/site/after'
diff --git a/test/functional/options/keymap_spec.lua b/test/functional/options/keymap_spec.lua
index 7f6d623dc7..52a714f7a8 100644
--- a/test/functional/options/keymap_spec.lua
+++ b/test/functional/options/keymap_spec.lua
@@ -30,7 +30,7 @@ describe("'keymap' / :lmap", function()
command('lmapclear <buffer>')
command('set keymap=dvorak')
command('set nomore')
- local bindings = funcs.nvim_command_output('lmap')
+ local bindings = funcs.nvim_exec('lmap', true)
eq(dedent([[
l " @_
diff --git a/test/functional/options/num_options_spec.lua b/test/functional/options/num_options_spec.lua
index deda5c9118..4754c14f5b 100644
--- a/test/functional/options/num_options_spec.lua
+++ b/test/functional/options/num_options_spec.lua
@@ -65,14 +65,12 @@ describe(':set validation', function()
should_succeed('regexpengine', 2)
should_fail('report', -1, 'E487')
should_succeed('report', 0)
- should_fail('scrolloff', -1, 'E49')
- should_fail('sidescrolloff', -1, 'E487')
should_fail('sidescroll', -1, 'E487')
should_fail('cmdwinheight', 0, 'E487')
should_fail('updatetime', -1, 'E487')
should_fail('foldlevel', -5, 'E487')
- should_fail('foldcolumn', 13, 'E474')
+ should_fail('foldcolumn', '13', 'E474')
should_fail('conceallevel', 4, 'E474')
should_fail('numberwidth', 21, 'E474')
should_fail('numberwidth', 0, 'E487')
@@ -82,6 +80,22 @@ describe(':set validation', function()
meths.set_option('window', -10)
eq(23, meths.get_option('window'))
eq('', eval("v:errmsg"))
+
+ -- 'scrolloff' and 'sidescrolloff' can have a -1 value when
+ -- set for the current window, but not globally
+ feed_command('setglobal scrolloff=-1')
+ eq('E487', eval("v:errmsg"):match("E%d*"))
+
+ feed_command('setglobal sidescrolloff=-1')
+ eq('E487', eval("v:errmsg"):match("E%d*"))
+
+ feed_command('let v:errmsg=""')
+
+ feed_command('setlocal scrolloff=-1')
+ eq('', eval("v:errmsg"))
+
+ feed_command('setlocal sidescrolloff=-1')
+ eq('', eval("v:errmsg"))
end)
it('set wmh/wh wmw/wiw checks', function()
diff --git a/test/functional/options/shortmess_spec.lua b/test/functional/options/shortmess_spec.lua
index 8ea9a19464..a56e9c09b4 100644
--- a/test/functional/options/shortmess_spec.lua
+++ b/test/functional/options/shortmess_spec.lua
@@ -25,7 +25,7 @@ describe("'shortmess'", function()
~ |
~ |
~ |
- "foo" [New File] |
+ "foo" [New] |
]])
eq(1, eval('bufnr("%")'))
@@ -50,7 +50,7 @@ describe("'shortmess'", function()
~ |
~ |
~ |
- "foo" [New File] |
+ "foo" [New] |
]])
eq(1, eval('bufnr("%")'))
feed(':edit bar<CR>')
@@ -59,7 +59,7 @@ describe("'shortmess'", function()
~ |
~ |
~ |
- "bar" [New File] |
+ "bar" [New] |
]])
eq(2, eval('bufnr("%")'))
feed(':bprevious<CR>')
@@ -68,7 +68,7 @@ describe("'shortmess'", function()
~ |
~ |
~ |
- "foo" [New file] --No lines in buffer-- |
+ "foo" [New] --No lines in buffer-- |
]])
eq(1, eval('bufnr("%")'))
diff --git a/test/functional/plugin/health_spec.lua b/test/functional/plugin/health_spec.lua
index 3525e235de..a78ed07876 100644
--- a/test/functional/plugin/health_spec.lua
+++ b/test/functional/plugin/health_spec.lua
@@ -116,8 +116,6 @@ describe('health.vim', function()
screen:set_default_attr_ids({
Ok = { foreground = Screen.colors.Grey3, background = 6291200 },
Error = { foreground = Screen.colors.Grey100, background = Screen.colors.Red },
- })
- screen:set_default_attr_ignore({
Heading = { bold=true, foreground=Screen.colors.Magenta },
Heading2 = { foreground = Screen.colors.SlateBlue },
Bar = { foreground=Screen.colors.Purple },
@@ -126,18 +124,18 @@ describe('health.vim', function()
command("checkhealth foo success1")
command("1tabclose")
command("set laststatus=0")
- screen:expect([[
+ screen:expect{grid=[[
^ |
- health#foo#check |
- ========================================================================|
- - {Error:ERROR:} No healthcheck found for "foo" plugin. |
+ {Heading:health#foo#check} |
+ {Bar:========================================================================}|
+ {Bullet: -} {Error:ERROR:} No healthcheck found for "foo" plugin. |
|
- health#success1#check |
- ========================================================================|
- ## report 1 |
- - {Ok:OK:} everything is fine |
+ {Heading:health#success1#check} |
+ {Bar:========================================================================}|
+ {Heading2:##}{Heading: report 1} |
+ {Bullet: -} {Ok:OK:} everything is fine |
|
- ]])
+ ]]}
end)
it("gracefully handles invalid healthcheck", function()
diff --git a/test/functional/plugin/lsp_spec.lua b/test/functional/plugin/lsp_spec.lua
new file mode 100644
index 0000000000..1002011999
--- /dev/null
+++ b/test/functional/plugin/lsp_spec.lua
@@ -0,0 +1,1620 @@
+local helpers = require('test.functional.helpers')(after_each)
+
+local assert_log = helpers.assert_log
+local clear = helpers.clear
+local buf_lines = helpers.buf_lines
+local dedent = helpers.dedent
+local exec_lua = helpers.exec_lua
+local eq = helpers.eq
+local pcall_err = helpers.pcall_err
+local pesc = helpers.pesc
+local insert = helpers.insert
+local retry = helpers.retry
+local NIL = helpers.NIL
+
+-- Use these to get access to a coroutine so that I can run async tests and use
+-- yield.
+local run, stop = helpers.run, helpers.stop
+
+-- TODO(justinmk): hangs on Windows https://github.com/neovim/neovim/pull/11837
+if helpers.pending_win32(pending) then return end
+
+-- Fake LSP server.
+local fake_lsp_code = 'test/functional/fixtures/fake-lsp-server.lua'
+local fake_lsp_logfile = 'Xtest-fake-lsp.log'
+
+teardown(function()
+ os.remove(fake_lsp_logfile)
+end)
+
+local function fake_lsp_server_setup(test_name, timeout_ms)
+ exec_lua([=[
+ lsp = require('vim.lsp')
+ local test_name, fixture_filename, logfile, timeout = ...
+ TEST_RPC_CLIENT_ID = lsp.start_client {
+ cmd_env = {
+ NVIM_LOG_FILE = logfile;
+ };
+ cmd = {
+ vim.v.progpath, '-Es', '-u', 'NONE', '--headless',
+ "-c", string.format("lua TEST_NAME = %q", test_name),
+ "-c", string.format("lua TIMEOUT = %d", timeout),
+ "-c", "luafile "..fixture_filename,
+ };
+ callbacks = setmetatable({}, {
+ __index = function(t, method)
+ return function(...)
+ return vim.rpcrequest(1, 'callback', ...)
+ end
+ end;
+ });
+ root_dir = vim.loop.cwd();
+ on_init = function(client, result)
+ TEST_RPC_CLIENT = client
+ vim.rpcrequest(1, "init", result)
+ end;
+ on_exit = function(...)
+ vim.rpcnotify(1, "exit", ...)
+ end;
+ }
+ ]=], test_name, fake_lsp_code, fake_lsp_logfile, timeout_ms or 1e3)
+end
+
+local function test_rpc_server(config)
+ if config.test_name then
+ clear()
+ fake_lsp_server_setup(config.test_name, config.timeout_ms or 1e3)
+ end
+ local client = setmetatable({}, {
+ __index = function(_, name)
+ -- Workaround for not being able to yield() inside __index for Lua 5.1 :(
+ -- Otherwise I would just return the value here.
+ return function(...)
+ return exec_lua([=[
+ local name = ...
+ if type(TEST_RPC_CLIENT[name]) == 'function' then
+ return TEST_RPC_CLIENT[name](select(2, ...))
+ else
+ return TEST_RPC_CLIENT[name]
+ end
+ ]=], name, ...)
+ end
+ end;
+ })
+ local code, signal
+ local function on_request(method, args)
+ if method == "init" then
+ if config.on_init then
+ config.on_init(client, unpack(args))
+ end
+ return NIL
+ end
+ if method == 'callback' then
+ if config.on_callback then
+ config.on_callback(unpack(args))
+ end
+ end
+ return NIL
+ end
+ local function on_notify(method, args)
+ if method == 'exit' then
+ code, signal = unpack(args)
+ return stop()
+ end
+ end
+ -- TODO specify timeout?
+ -- run(on_request, on_notify, config.on_setup, 1000)
+ run(on_request, on_notify, config.on_setup)
+ if config.on_exit then
+ config.on_exit(code, signal)
+ end
+ stop()
+ if config.test_name then
+ exec_lua("lsp._vim_exit_handler()")
+ end
+end
+
+describe('LSP', function()
+ describe('server_name specified', function()
+ before_each(function()
+ clear()
+ -- Run an instance of nvim on the file which contains our "scripts".
+ -- Pass TEST_NAME to pick the script.
+ local test_name = "basic_init"
+ exec_lua([=[
+ lsp = require('vim.lsp')
+ local test_name, fixture_filename, logfile = ...
+ function test__start_client()
+ return lsp.start_client {
+ cmd_env = {
+ NVIM_LOG_FILE = logfile;
+ };
+ cmd = {
+ vim.v.progpath, '-Es', '-u', 'NONE', '--headless',
+ "-c", string.format("lua TEST_NAME = %q", test_name),
+ "-c", "luafile "..fixture_filename;
+ };
+ root_dir = vim.loop.cwd();
+ }
+ end
+ TEST_CLIENT1 = test__start_client()
+ ]=], test_name, fake_lsp_code, fake_lsp_logfile)
+ end)
+
+ after_each(function()
+ exec_lua("lsp._vim_exit_handler()")
+ -- exec_lua("lsp.stop_all_clients(true)")
+ end)
+
+ it('start_client(), stop_client()', function()
+ retry(nil, 4000, function()
+ eq(1, exec_lua('return #lsp.get_active_clients()'))
+ end)
+ eq(2, exec_lua([[
+ TEST_CLIENT2 = test__start_client()
+ return TEST_CLIENT2
+ ]]))
+ eq(3, exec_lua([[
+ TEST_CLIENT3 = test__start_client()
+ return TEST_CLIENT3
+ ]]))
+ retry(nil, 4000, function()
+ eq(3, exec_lua('return #lsp.get_active_clients()'))
+ end)
+
+ eq(false, exec_lua('return lsp.get_client_by_id(TEST_CLIENT1) == nil'))
+ eq(false, exec_lua('return lsp.get_client_by_id(TEST_CLIENT1).is_stopped()'))
+ exec_lua('return lsp.get_client_by_id(TEST_CLIENT1).stop()')
+ retry(nil, 4000, function()
+ eq(2, exec_lua('return #lsp.get_active_clients()'))
+ end)
+ eq(true, exec_lua('return lsp.get_client_by_id(TEST_CLIENT1) == nil'))
+
+ exec_lua('lsp.stop_client({TEST_CLIENT2, TEST_CLIENT3})')
+ retry(nil, 4000, function()
+ eq(0, exec_lua('return #lsp.get_active_clients()'))
+ end)
+ end)
+
+ it('stop_client() also works on client objects', function()
+ exec_lua([[
+ TEST_CLIENT2 = test__start_client()
+ TEST_CLIENT3 = test__start_client()
+ ]])
+ retry(nil, 4000, function()
+ eq(3, exec_lua('return #lsp.get_active_clients()'))
+ end)
+ -- Stop all clients.
+ exec_lua('lsp.stop_client(lsp.get_active_clients())')
+ retry(nil, 4000, function()
+ eq(0, exec_lua('return #lsp.get_active_clients()'))
+ end)
+ end)
+ end)
+
+ describe('basic_init test', function()
+ it('should run correctly', function()
+ local expected_callbacks = {
+ {NIL, "test", {}, 1};
+ }
+ test_rpc_server {
+ test_name = "basic_init";
+ on_init = function(client, _init_result)
+ -- client is a dummy object which will queue up commands to be run
+ -- once the server initializes. It can't accept lua callbacks or
+ -- other types that may be unserializable for now.
+ client.stop()
+ end;
+ -- If the program timed out, then code will be nil.
+ on_exit = function(code, signal)
+ eq(0, code, "exit code", fake_lsp_logfile)
+ eq(0, signal, "exit signal", fake_lsp_logfile)
+ end;
+ -- Note that NIL must be used here.
+ -- on_callback(err, method, result, client_id)
+ on_callback = function(...)
+ eq(table.remove(expected_callbacks), {...})
+ end;
+ }
+ end)
+
+ it('should fail', function()
+ local expected_callbacks = {
+ {NIL, "test", {}, 1};
+ }
+ test_rpc_server {
+ test_name = "basic_init";
+ on_init = function(client)
+ client.notify('test')
+ client.stop()
+ end;
+ on_exit = function(code, signal)
+ eq(101, code, "exit code", fake_lsp_logfile) -- See fake-lsp-server.lua
+ eq(0, signal, "exit signal", fake_lsp_logfile)
+ assert_log(pesc([[assert_eq failed: left == "\"shutdown\"", right == "\"test\""]]),
+ fake_lsp_logfile)
+ end;
+ on_callback = function(...)
+ eq(table.remove(expected_callbacks), {...}, "expected callback")
+ end;
+ }
+ end)
+
+ it('should succeed with manual shutdown', function()
+ local expected_callbacks = {
+ {NIL, "shutdown", {}, 1, NIL};
+ {NIL, "test", {}, 1};
+ }
+ test_rpc_server {
+ test_name = "basic_init";
+ on_init = function(client)
+ eq(0, client.resolved_capabilities().text_document_did_change)
+ client.request('shutdown')
+ client.notify('exit')
+ end;
+ on_exit = function(code, signal)
+ eq(0, code, "exit code", fake_lsp_logfile)
+ eq(0, signal, "exit signal", fake_lsp_logfile)
+ end;
+ on_callback = function(...)
+ eq(table.remove(expected_callbacks), {...}, "expected callback")
+ end;
+ }
+ end)
+
+ it('should verify capabilities sent', function()
+ local expected_callbacks = {
+ {NIL, "shutdown", {}, 1};
+ }
+ test_rpc_server {
+ test_name = "basic_check_capabilities";
+ on_init = function(client)
+ client.stop()
+ end;
+ on_exit = function(code, signal)
+ eq(0, code, "exit code", fake_lsp_logfile)
+ eq(0, signal, "exit signal", fake_lsp_logfile)
+ end;
+ on_callback = function(...)
+ eq(table.remove(expected_callbacks), {...}, "expected callback")
+ end;
+ }
+ end)
+
+ it('should not send didOpen if the buffer closes before init', function()
+ local expected_callbacks = {
+ {NIL, "shutdown", {}, 1};
+ {NIL, "finish", {}, 1};
+ }
+ local client
+ test_rpc_server {
+ test_name = "basic_finish";
+ on_setup = function()
+ exec_lua [[
+ BUFFER = vim.api.nvim_create_buf(false, true)
+ vim.api.nvim_buf_set_lines(BUFFER, 0, -1, false, {
+ "testing";
+ "123";
+ })
+ ]]
+ eq(1, exec_lua("return TEST_RPC_CLIENT_ID"))
+ eq(true, exec_lua("return lsp.buf_attach_client(BUFFER, TEST_RPC_CLIENT_ID)"))
+ eq(true, exec_lua("return lsp.buf_is_attached(BUFFER, TEST_RPC_CLIENT_ID)"))
+ exec_lua [[
+ vim.api.nvim_command(BUFFER.."bwipeout")
+ ]]
+ end;
+ on_init = function(_client)
+ client = _client
+ local full_kind = exec_lua("return require'vim.lsp.protocol'.TextDocumentSyncKind.Full")
+ eq(full_kind, client.resolved_capabilities().text_document_did_change)
+ eq(true, client.resolved_capabilities().text_document_open_close)
+ client.notify('finish')
+ end;
+ on_exit = function(code, signal)
+ eq(0, code, "exit code", fake_lsp_logfile)
+ eq(0, signal, "exit signal", fake_lsp_logfile)
+ end;
+ on_callback = function(err, method, params, client_id)
+ eq(table.remove(expected_callbacks), {err, method, params, client_id}, "expected callback")
+ if method == 'finish' then
+ client.stop()
+ end
+ end;
+ }
+ end)
+
+ it('should check the body sent attaching before init', function()
+ local expected_callbacks = {
+ {NIL, "shutdown", {}, 1};
+ {NIL, "finish", {}, 1};
+ {NIL, "start", {}, 1};
+ }
+ local client
+ test_rpc_server {
+ test_name = "basic_check_buffer_open";
+ on_setup = function()
+ exec_lua [[
+ BUFFER = vim.api.nvim_create_buf(false, true)
+ vim.api.nvim_buf_set_lines(BUFFER, 0, -1, false, {
+ "testing";
+ "123";
+ })
+ ]]
+ exec_lua [[
+ assert(lsp.buf_attach_client(BUFFER, TEST_RPC_CLIENT_ID))
+ ]]
+ end;
+ on_init = function(_client)
+ client = _client
+ local full_kind = exec_lua("return require'vim.lsp.protocol'.TextDocumentSyncKind.Full")
+ eq(full_kind, client.resolved_capabilities().text_document_did_change)
+ eq(true, client.resolved_capabilities().text_document_open_close)
+ exec_lua [[
+ assert(not lsp.buf_attach_client(BUFFER, TEST_RPC_CLIENT_ID), "Shouldn't attach twice")
+ ]]
+ end;
+ on_exit = function(code, signal)
+ eq(0, code, "exit code", fake_lsp_logfile)
+ eq(0, signal, "exit signal", fake_lsp_logfile)
+ end;
+ on_callback = function(err, method, params, client_id)
+ if method == 'start' then
+ client.notify('finish')
+ end
+ eq(table.remove(expected_callbacks), {err, method, params, client_id}, "expected callback")
+ if method == 'finish' then
+ client.stop()
+ end
+ end;
+ }
+ end)
+
+ it('should check the body sent attaching after init', function()
+ local expected_callbacks = {
+ {NIL, "shutdown", {}, 1};
+ {NIL, "finish", {}, 1};
+ {NIL, "start", {}, 1};
+ }
+ local client
+ test_rpc_server {
+ test_name = "basic_check_buffer_open";
+ on_setup = function()
+ exec_lua [[
+ BUFFER = vim.api.nvim_create_buf(false, true)
+ vim.api.nvim_buf_set_lines(BUFFER, 0, -1, false, {
+ "testing";
+ "123";
+ })
+ ]]
+ end;
+ on_init = function(_client)
+ client = _client
+ local full_kind = exec_lua("return require'vim.lsp.protocol'.TextDocumentSyncKind.Full")
+ eq(full_kind, client.resolved_capabilities().text_document_did_change)
+ eq(true, client.resolved_capabilities().text_document_open_close)
+ exec_lua [[
+ assert(lsp.buf_attach_client(BUFFER, TEST_RPC_CLIENT_ID))
+ ]]
+ end;
+ on_exit = function(code, signal)
+ eq(0, code, "exit code", fake_lsp_logfile)
+ eq(0, signal, "exit signal", fake_lsp_logfile)
+ end;
+ on_callback = function(err, method, params, client_id)
+ if method == 'start' then
+ client.notify('finish')
+ end
+ eq(table.remove(expected_callbacks), {err, method, params, client_id}, "expected callback")
+ if method == 'finish' then
+ client.stop()
+ end
+ end;
+ }
+ end)
+
+ it('should check the body and didChange full', function()
+ local expected_callbacks = {
+ {NIL, "shutdown", {}, 1};
+ {NIL, "finish", {}, 1};
+ {NIL, "start", {}, 1};
+ }
+ local client
+ test_rpc_server {
+ test_name = "basic_check_buffer_open_and_change";
+ on_setup = function()
+ exec_lua [[
+ BUFFER = vim.api.nvim_create_buf(false, true)
+ vim.api.nvim_buf_set_lines(BUFFER, 0, -1, false, {
+ "testing";
+ "123";
+ })
+ ]]
+ end;
+ on_init = function(_client)
+ client = _client
+ local full_kind = exec_lua("return require'vim.lsp.protocol'.TextDocumentSyncKind.Full")
+ eq(full_kind, client.resolved_capabilities().text_document_did_change)
+ eq(true, client.resolved_capabilities().text_document_open_close)
+ exec_lua [[
+ assert(lsp.buf_attach_client(BUFFER, TEST_RPC_CLIENT_ID))
+ ]]
+ end;
+ on_exit = function(code, signal)
+ eq(0, code, "exit code", fake_lsp_logfile)
+ eq(0, signal, "exit signal", fake_lsp_logfile)
+ end;
+ on_callback = function(err, method, params, client_id)
+ if method == 'start' then
+ exec_lua [[
+ vim.api.nvim_buf_set_lines(BUFFER, 1, 2, false, {
+ "boop";
+ })
+ ]]
+ client.notify('finish')
+ end
+ eq(table.remove(expected_callbacks), {err, method, params, client_id}, "expected callback")
+ if method == 'finish' then
+ client.stop()
+ end
+ end;
+ }
+ end)
+
+ it('should check the body and didChange full with noeol', function()
+ local expected_callbacks = {
+ {NIL, "shutdown", {}, 1};
+ {NIL, "finish", {}, 1};
+ {NIL, "start", {}, 1};
+ }
+ local client
+ test_rpc_server {
+ test_name = "basic_check_buffer_open_and_change_noeol";
+ on_setup = function()
+ exec_lua [[
+ BUFFER = vim.api.nvim_create_buf(false, true)
+ vim.api.nvim_buf_set_lines(BUFFER, 0, -1, false, {
+ "testing";
+ "123";
+ })
+ vim.api.nvim_buf_set_option(BUFFER, 'eol', false)
+ ]]
+ end;
+ on_init = function(_client)
+ client = _client
+ local full_kind = exec_lua("return require'vim.lsp.protocol'.TextDocumentSyncKind.Full")
+ eq(full_kind, client.resolved_capabilities().text_document_did_change)
+ eq(true, client.resolved_capabilities().text_document_open_close)
+ exec_lua [[
+ assert(lsp.buf_attach_client(BUFFER, TEST_RPC_CLIENT_ID))
+ ]]
+ end;
+ on_exit = function(code, signal)
+ eq(0, code, "exit code", fake_lsp_logfile)
+ eq(0, signal, "exit signal", fake_lsp_logfile)
+ end;
+ on_callback = function(err, method, params, client_id)
+ if method == 'start' then
+ exec_lua [[
+ vim.api.nvim_buf_set_lines(BUFFER, 1, 2, false, {
+ "boop";
+ })
+ ]]
+ client.notify('finish')
+ end
+ eq(table.remove(expected_callbacks), {err, method, params, client_id}, "expected callback")
+ if method == 'finish' then
+ client.stop()
+ end
+ end;
+ }
+ end)
+
+ -- TODO(askhan) we don't support full for now, so we can disable these tests.
+ pending('should check the body and didChange incremental', function()
+ local expected_callbacks = {
+ {NIL, "shutdown", {}, 1};
+ {NIL, "finish", {}, 1};
+ {NIL, "start", {}, 1};
+ }
+ local client
+ test_rpc_server {
+ test_name = "basic_check_buffer_open_and_change_incremental";
+ on_setup = function()
+ exec_lua [[
+ BUFFER = vim.api.nvim_create_buf(false, true)
+ vim.api.nvim_buf_set_lines(BUFFER, 0, -1, false, {
+ "testing";
+ "123";
+ })
+ ]]
+ end;
+ on_init = function(_client)
+ client = _client
+ local sync_kind = exec_lua("return require'vim.lsp.protocol'.TextDocumentSyncKind.Incremental")
+ eq(sync_kind, client.resolved_capabilities().text_document_did_change)
+ eq(true, client.resolved_capabilities().text_document_open_close)
+ exec_lua [[
+ assert(lsp.buf_attach_client(BUFFER, TEST_RPC_CLIENT_ID))
+ ]]
+ end;
+ on_exit = function(code, signal)
+ eq(0, code, "exit code", fake_lsp_logfile)
+ eq(0, signal, "exit signal", fake_lsp_logfile)
+ end;
+ on_callback = function(err, method, params, client_id)
+ if method == 'start' then
+ exec_lua [[
+ vim.api.nvim_buf_set_lines(BUFFER, 1, 2, false, {
+ "boop";
+ })
+ ]]
+ client.notify('finish')
+ end
+ eq(table.remove(expected_callbacks), {err, method, params, client_id}, "expected callback")
+ if method == 'finish' then
+ client.stop()
+ end
+ end;
+ }
+ end)
+
+ -- TODO(askhan) we don't support full for now, so we can disable these tests.
+ pending('should check the body and didChange incremental normal mode editing', function()
+ local expected_callbacks = {
+ {NIL, "shutdown", {}, 1};
+ {NIL, "finish", {}, 1};
+ {NIL, "start", {}, 1};
+ }
+ local client
+ test_rpc_server {
+ test_name = "basic_check_buffer_open_and_change_incremental_editing";
+ on_setup = function()
+ exec_lua [[
+ BUFFER = vim.api.nvim_create_buf(false, true)
+ vim.api.nvim_buf_set_lines(BUFFER, 0, -1, false, {
+ "testing";
+ "123";
+ })
+ ]]
+ end;
+ on_init = function(_client)
+ client = _client
+ local sync_kind = exec_lua("return require'vim.lsp.protocol'.TextDocumentSyncKind.Incremental")
+ eq(sync_kind, client.resolved_capabilities().text_document_did_change)
+ eq(true, client.resolved_capabilities().text_document_open_close)
+ exec_lua [[
+ assert(lsp.buf_attach_client(BUFFER, TEST_RPC_CLIENT_ID))
+ ]]
+ end;
+ on_exit = function(code, signal)
+ eq(0, code, "exit code", fake_lsp_logfile)
+ eq(0, signal, "exit signal", fake_lsp_logfile)
+ end;
+ on_callback = function(err, method, params, client_id)
+ if method == 'start' then
+ helpers.command("normal! 1Go")
+ client.notify('finish')
+ end
+ eq(table.remove(expected_callbacks), {err, method, params, client_id}, "expected callback")
+ if method == 'finish' then
+ client.stop()
+ end
+ end;
+ }
+ end)
+
+ it('should check the body and didChange full with 2 changes', function()
+ local expected_callbacks = {
+ {NIL, "shutdown", {}, 1};
+ {NIL, "finish", {}, 1};
+ {NIL, "start", {}, 1};
+ }
+ local client
+ test_rpc_server {
+ test_name = "basic_check_buffer_open_and_change_multi";
+ on_setup = function()
+ exec_lua [[
+ BUFFER = vim.api.nvim_create_buf(false, true)
+ vim.api.nvim_buf_set_lines(BUFFER, 0, -1, false, {
+ "testing";
+ "123";
+ })
+ ]]
+ end;
+ on_init = function(_client)
+ client = _client
+ local sync_kind = exec_lua("return require'vim.lsp.protocol'.TextDocumentSyncKind.Full")
+ eq(sync_kind, client.resolved_capabilities().text_document_did_change)
+ eq(true, client.resolved_capabilities().text_document_open_close)
+ exec_lua [[
+ assert(lsp.buf_attach_client(BUFFER, TEST_RPC_CLIENT_ID))
+ ]]
+ end;
+ on_exit = function(code, signal)
+ eq(0, code, "exit code", fake_lsp_logfile)
+ eq(0, signal, "exit signal", fake_lsp_logfile)
+ end;
+ on_callback = function(err, method, params, client_id)
+ if method == 'start' then
+ exec_lua [[
+ vim.api.nvim_buf_set_lines(BUFFER, 1, 2, false, {
+ "321";
+ })
+ vim.api.nvim_buf_set_lines(BUFFER, 1, 2, false, {
+ "boop";
+ })
+ ]]
+ client.notify('finish')
+ end
+ eq(table.remove(expected_callbacks), {err, method, params, client_id}, "expected callback")
+ if method == 'finish' then
+ client.stop()
+ end
+ end;
+ }
+ end)
+
+ it('should check the body and didChange full lifecycle', function()
+ local expected_callbacks = {
+ {NIL, "shutdown", {}, 1};
+ {NIL, "finish", {}, 1};
+ {NIL, "start", {}, 1};
+ }
+ local client
+ test_rpc_server {
+ test_name = "basic_check_buffer_open_and_change_multi_and_close";
+ on_setup = function()
+ exec_lua [[
+ BUFFER = vim.api.nvim_create_buf(false, true)
+ vim.api.nvim_buf_set_lines(BUFFER, 0, -1, false, {
+ "testing";
+ "123";
+ })
+ ]]
+ end;
+ on_init = function(_client)
+ client = _client
+ local sync_kind = exec_lua("return require'vim.lsp.protocol'.TextDocumentSyncKind.Full")
+ eq(sync_kind, client.resolved_capabilities().text_document_did_change)
+ eq(true, client.resolved_capabilities().text_document_open_close)
+ exec_lua [[
+ assert(lsp.buf_attach_client(BUFFER, TEST_RPC_CLIENT_ID))
+ ]]
+ end;
+ on_exit = function(code, signal)
+ eq(0, code, "exit code", fake_lsp_logfile)
+ eq(0, signal, "exit signal", fake_lsp_logfile)
+ end;
+ on_callback = function(err, method, params, client_id)
+ if method == 'start' then
+ exec_lua [[
+ vim.api.nvim_buf_set_lines(BUFFER, 1, 2, false, {
+ "321";
+ })
+ vim.api.nvim_buf_set_lines(BUFFER, 1, 2, false, {
+ "boop";
+ })
+ vim.api.nvim_command(BUFFER.."bwipeout")
+ ]]
+ client.notify('finish')
+ end
+ eq(table.remove(expected_callbacks), {err, method, params, client_id}, "expected callback")
+ if method == 'finish' then
+ client.stop()
+ end
+ end;
+ }
+ end)
+ end)
+
+ describe("parsing tests", function()
+ it('should handle invalid content-length correctly', function()
+ local expected_callbacks = {
+ {NIL, "shutdown", {}, 1};
+ {NIL, "finish", {}, 1};
+ {NIL, "start", {}, 1};
+ }
+ local client
+ test_rpc_server {
+ test_name = "invalid_header";
+ on_setup = function()
+ end;
+ on_init = function(_client)
+ client = _client
+ client.stop(true)
+ end;
+ on_exit = function(code, signal)
+ eq(0, code, "exit code", fake_lsp_logfile)
+ eq(0, signal, "exit signal", fake_lsp_logfile)
+ end;
+ on_callback = function(err, method, params, client_id)
+ eq(table.remove(expected_callbacks), {err, method, params, client_id}, "expected callback")
+ end;
+ }
+ end)
+ end)
+ describe('lsp._cmd_parts test', function()
+ local function _cmd_parts(input)
+ return exec_lua([[
+ lsp = require('vim.lsp')
+ return lsp._cmd_parts(...)
+ ]], input)
+ end
+ it('should valid cmd argument', function()
+ eq(true, pcall(_cmd_parts, {"nvim"}))
+ eq(true, pcall(_cmd_parts, {"nvim", "--head"}))
+ end)
+
+ it('should invalid cmd argument', function()
+ eq('Error executing lua: .../shared.lua: cmd: expected list, got nvim', pcall_err(_cmd_parts, "nvim"))
+ eq('Error executing lua: .../shared.lua: cmd argument: expected string, got number', pcall_err(_cmd_parts, {"nvim", 1}))
+ end)
+ end)
+end)
+
+describe('LSP', function()
+ before_each(function()
+ clear()
+ end)
+
+ local function make_edit(y_0, x_0, y_1, x_1, text)
+ return {
+ range = {
+ start = { line = y_0, character = x_0 };
+ ["end"] = { line = y_1, character = x_1 };
+ };
+ newText = type(text) == 'table' and table.concat(text, '\n') or (text or "");
+ }
+ end
+
+ it('highlight groups', function()
+ eq({'LspDiagnosticsError',
+ 'LspDiagnosticsErrorFloating',
+ 'LspDiagnosticsErrorSign',
+ 'LspDiagnosticsHint',
+ 'LspDiagnosticsHintFloating',
+ 'LspDiagnosticsHintSign',
+ 'LspDiagnosticsInformation',
+ 'LspDiagnosticsInformationFloating',
+ 'LspDiagnosticsInformationSign',
+ 'LspDiagnosticsUnderline',
+ 'LspDiagnosticsUnderlineError',
+ 'LspDiagnosticsUnderlineHint',
+ 'LspDiagnosticsUnderlineInformation',
+ 'LspDiagnosticsUnderlineWarning',
+ 'LspDiagnosticsWarning',
+ 'LspDiagnosticsWarningFloating',
+ 'LspDiagnosticsWarningSign',
+ },
+ exec_lua([[require'vim.lsp'; return vim.fn.getcompletion('Lsp', 'highlight')]]))
+ end)
+
+ describe('apply_text_edits', function()
+ before_each(function()
+ insert(dedent([[
+ First line of text
+ Second line of text
+ Third line of text
+ Fourth line of text
+ å å ɧ 汉语 ↥ 🤦 🦄]]))
+ end)
+ it('applies simple edits', function()
+ local edits = {
+ make_edit(0, 0, 0, 0, {"123"});
+ make_edit(1, 0, 1, 1, {"2"});
+ make_edit(2, 0, 2, 2, {"3"});
+ make_edit(3, 2, 3, 4, {""});
+ }
+ exec_lua('vim.lsp.util.apply_text_edits(...)', edits, 1)
+ eq({
+ '123First line of text';
+ '2econd line of text';
+ '3ird line of text';
+ 'Foth line of text';
+ 'å å ɧ 汉语 ↥ 🤦 🦄';
+ }, buf_lines(1))
+ end)
+ it('applies complex edits', function()
+ local edits = {
+ make_edit(0, 0, 0, 0, {"", "12"});
+ make_edit(0, 0, 0, 0, {"3", "foo"});
+ make_edit(0, 1, 0, 1, {"bar", "123"});
+ make_edit(0, #"First ", 0, #"First line of text", {"guy"});
+ make_edit(1, 0, 1, #'Second', {"baz"});
+ make_edit(2, #'Th', 2, #"Third", {"e next"});
+ make_edit(3, #'', 3, #"Fourth", {"another line of text", "before this"});
+ make_edit(3, #'Fourth', 3, #"Fourth line of text", {"!"});
+ }
+ exec_lua('vim.lsp.util.apply_text_edits(...)', edits, 1)
+ eq({
+ '';
+ '123';
+ 'fooFbar';
+ '123irst guy';
+ 'baz line of text';
+ 'The next line of text';
+ 'another line of text';
+ 'before this!';
+ 'å å ɧ 汉语 ↥ 🤦 🦄';
+ }, buf_lines(1))
+ end)
+ it('applies non-ASCII characters edits', function()
+ local edits = {
+ make_edit(4, 3, 4, 4, {"ä"});
+ }
+ exec_lua('vim.lsp.util.apply_text_edits(...)', edits, 1)
+ eq({
+ 'First line of text';
+ 'Second line of text';
+ 'Third line of text';
+ 'Fourth line of text';
+ 'å ä ɧ 汉语 ↥ 🤦 🦄';
+ }, buf_lines(1))
+ end)
+
+ describe('with LSP end line after what Vim considers to be the end line', function()
+ it('applies edits when the last linebreak is considered a new line', function()
+ local edits = {
+ make_edit(0, 0, 5, 0, {"All replaced"});
+ }
+ exec_lua('vim.lsp.util.apply_text_edits(...)', edits, 1)
+ eq({'All replaced'}, buf_lines(1))
+ end)
+ it('applies edits when the end line is 2 larger than vim\'s', function()
+ local edits = {
+ make_edit(0, 0, 6, 0, {"All replaced"});
+ }
+ exec_lua('vim.lsp.util.apply_text_edits(...)', edits, 1)
+ eq({'All replaced'}, buf_lines(1))
+ end)
+ it('applies edits with a column offset', function()
+ local edits = {
+ make_edit(0, 0, 5, 2, {"All replaced"});
+ }
+ exec_lua('vim.lsp.util.apply_text_edits(...)', edits, 1)
+ eq({'All replaced'}, buf_lines(1))
+ end)
+ end)
+ end)
+
+ describe('apply_text_document_edit', function()
+ local target_bufnr
+ local text_document_edit = function(editVersion)
+ return {
+ edits = {
+ make_edit(0, 0, 0, 3, "First ↥ 🤦 🦄")
+ },
+ textDocument = {
+ uri = "file://fake/uri";
+ version = editVersion
+ }
+ }
+ end
+ before_each(function()
+ target_bufnr = exec_lua [[
+ local bufnr = vim.uri_to_bufnr("file://fake/uri")
+ local lines = {"1st line of text", "2nd line of 语text"}
+ vim.api.nvim_buf_set_lines(bufnr, 0, 1, false, lines)
+ return bufnr
+ ]]
+ end)
+ it('correctly goes ahead with the edit if all is normal', function()
+ exec_lua('vim.lsp.util.apply_text_document_edit(...)', text_document_edit(5))
+ eq({
+ 'First ↥ 🤦 🦄 line of text';
+ '2nd line of 语text';
+ }, buf_lines(target_bufnr))
+ end)
+ it('correctly goes ahead with the edit if the version is vim.NIL', function()
+ -- we get vim.NIL when we decode json null value.
+ local json = exec_lua[[
+ return vim.fn.json_decode("{ \"a\": 1, \"b\": null }")
+ ]]
+ eq(json.b, exec_lua("return vim.NIL"))
+
+ exec_lua('vim.lsp.util.apply_text_document_edit(...)', text_document_edit(exec_lua("return vim.NIL")))
+ eq({
+ 'First ↥ 🤦 🦄 line of text';
+ '2nd line of 语text';
+ }, buf_lines(target_bufnr))
+ end)
+ it('skips the edit if the version of the edit is behind the local buffer ', function()
+ local apply_edit_mocking_current_version = function(edit, versionedBuf)
+ exec_lua([[
+ local args = {...}
+ local versionedBuf = args[2]
+ vim.lsp.util.buf_versions[versionedBuf.bufnr] = versionedBuf.currentVersion
+ vim.lsp.util.apply_text_document_edit(...)
+ ]], edit, versionedBuf)
+ end
+
+ local baseText = {
+ '1st line of text';
+ '2nd line of 语text';
+ }
+
+ eq(baseText, buf_lines(target_bufnr))
+
+ -- Apply an edit for an old version, should skip
+ apply_edit_mocking_current_version(text_document_edit(2), {currentVersion=7; bufnr=target_bufnr})
+ eq(baseText, buf_lines(target_bufnr)) -- no change
+
+ -- Sanity check that next version to current does apply change
+ apply_edit_mocking_current_version(text_document_edit(8), {currentVersion=7; bufnr=target_bufnr})
+ eq({
+ 'First ↥ 🤦 🦄 line of text';
+ '2nd line of 语text';
+ }, buf_lines(target_bufnr))
+ end)
+ end)
+ describe('workspace_apply_edit', function()
+ it('workspace/applyEdit returns ApplyWorkspaceEditResponse', function()
+ local expected = {
+ applied = true;
+ failureReason = nil;
+ }
+ eq(expected, exec_lua [[
+ local apply_edit = {
+ label = nil;
+ edit = {};
+ }
+ return vim.lsp.callbacks['workspace/applyEdit'](nil, nil, apply_edit)
+ ]])
+ end)
+ end)
+ describe('completion_list_to_complete_items', function()
+ -- Completion option precedence:
+ -- textEdit.newText > insertText > label
+ -- https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_completion
+ it('should choose right completion option', function ()
+ local prefix = 'foo'
+ local completion_list = {
+ -- resolves into label
+ { label='foobar' },
+ { label='foobar', textEdit={} },
+ -- resolves into insertText
+ { label='foocar', insertText='foobar' },
+ { label='foocar', insertText='foobar', textEdit={} },
+ -- resolves into textEdit.newText
+ { label='foocar', insertText='foodar', textEdit={newText='foobar'} },
+ { label='foocar', textEdit={newText='foobar'} },
+ -- real-world snippet text
+ { label='foocar', insertText='foodar', textEdit={newText='foobar(${1:place holder}, ${2:more ...holder{\\}})'} },
+ { label='foocar', insertText='foodar(${1:var1} typ1, ${2:var2} *typ2) {$0\\}', textEdit={} },
+ -- nested snippet tokens
+ { label='foocar', insertText='foodar(${1:var1 ${2|typ2,typ3|} ${3:tail}}) {$0\\}', textEdit={} },
+ -- plain text
+ { label='foocar', insertText='foodar(${1:var1})', insertTextFormat=1, textEdit={} },
+ }
+ local completion_list_items = {items=completion_list}
+ local expected = {
+ { abbr = 'foobar', dup = 1, empty = 1, icase = 1, info = ' ', kind = 'Unknown', menu = '', word = 'foobar', user_data = { nvim = { lsp = { completion_item = { label = 'foobar' } } } } },
+ { abbr = 'foobar', dup = 1, empty = 1, icase = 1, info = ' ', kind = 'Unknown', menu = '', word = 'foobar', user_data = { nvim = { lsp = { completion_item = { label='foobar', textEdit={} } } } } },
+ { abbr = 'foocar', dup = 1, empty = 1, icase = 1, info = ' ', kind = 'Unknown', menu = '', word = 'foobar', user_data = { nvim = { lsp = { completion_item = { label='foocar', insertText='foobar' } } } } },
+ { abbr = 'foocar', dup = 1, empty = 1, icase = 1, info = ' ', kind = 'Unknown', menu = '', word = 'foobar', user_data = { nvim = { lsp = { completion_item = { label='foocar', insertText='foobar', textEdit={} } } } } },
+ { abbr = 'foocar', dup = 1, empty = 1, icase = 1, info = ' ', kind = 'Unknown', menu = '', word = 'foobar', user_data = { nvim = { lsp = { completion_item = { label='foocar', insertText='foodar', textEdit={newText='foobar'} } } } } },
+ { abbr = 'foocar', dup = 1, empty = 1, icase = 1, info = ' ', kind = 'Unknown', menu = '', word = 'foobar', user_data = { nvim = { lsp = { completion_item = { label='foocar', textEdit={newText='foobar'} } } } } },
+ { abbr = 'foocar', dup = 1, empty = 1, icase = 1, info = ' ', kind = 'Unknown', menu = '', word = 'foobar(place holder, more ...holder{})', user_data = { nvim = { lsp = { completion_item = { label='foocar', insertText='foodar', textEdit={newText='foobar(${1:place holder}, ${2:more ...holder{\\}})'} } } } } },
+ { abbr = 'foocar', dup = 1, empty = 1, icase = 1, info = ' ', kind = 'Unknown', menu = '', word = 'foodar(var1 typ1, var2 *typ2) {}', user_data = { nvim = { lsp = { completion_item = { label='foocar', insertText='foodar(${1:var1} typ1, ${2:var2} *typ2) {$0\\}', textEdit={} } } } } },
+ { abbr = 'foocar', dup = 1, empty = 1, icase = 1, info = ' ', kind = 'Unknown', menu = '', word = 'foodar(var1 typ2,typ3 tail) {}', user_data = { nvim = { lsp = { completion_item = { label='foocar', insertText='foodar(${1:var1 ${2|typ2,typ3|} ${3:tail}}) {$0\\}', textEdit={} } } } } },
+ { abbr = 'foocar', dup = 1, empty = 1, icase = 1, info = ' ', kind = 'Unknown', menu = '', word = 'foodar(${1:var1})', user_data = { nvim = { lsp = { completion_item = { label='foocar', insertText='foodar(${1:var1})', insertTextFormat=1, textEdit={} } } } } },
+ }
+
+ eq(expected, exec_lua([[return vim.lsp.util.text_document_completion_list_to_complete_items(...)]], completion_list, prefix))
+ eq(expected, exec_lua([[return vim.lsp.util.text_document_completion_list_to_complete_items(...)]], completion_list_items, prefix))
+ eq({}, exec_lua([[return vim.lsp.util.text_document_completion_list_to_complete_items(...)]], {}, prefix))
+ end)
+ end)
+ describe('buf_diagnostics_save_positions', function()
+ it('stores the diagnostics in diagnostics_by_buf', function ()
+ local diagnostics = {
+ { range = {}; message = "diag1" },
+ { range = {}; message = "diag2" },
+ }
+ exec_lua([[
+ vim.lsp.util.buf_diagnostics_save_positions(...)]], 0, diagnostics)
+ eq(1, exec_lua [[ return #vim.lsp.util.diagnostics_by_buf ]])
+ eq(diagnostics, exec_lua [[
+ for _, diagnostics in pairs(vim.lsp.util.diagnostics_by_buf) do
+ return diagnostics
+ end
+ ]])
+ end)
+ end)
+ describe('lsp.util.show_line_diagnostics', function()
+ it('creates floating window and returns popup bufnr and winnr if current line contains diagnostics', function()
+ eq(3, exec_lua [[
+ local buffer = vim.api.nvim_create_buf(false, true)
+ vim.api.nvim_buf_set_lines(buffer, 0, -1, false, {
+ "testing";
+ "123";
+ })
+ local diagnostics = {
+ {
+ range = {
+ start = { line = 0; character = 1; };
+ ["end"] = { line = 0; character = 3; };
+ };
+ severity = vim.lsp.protocol.DiagnosticSeverity.Error;
+ message = "Syntax error";
+ },
+ }
+ vim.api.nvim_win_set_buf(0, buffer)
+ vim.lsp.util.buf_diagnostics_save_positions(vim.fn.bufnr(buffer), diagnostics)
+ local popup_bufnr, winnr = vim.lsp.util.show_line_diagnostics()
+ return popup_bufnr
+ ]])
+ end)
+ end)
+ describe('lsp.util.locations_to_items', function()
+ it('Convert Location[] to items', function()
+ local expected = {
+ {
+ filename = 'fake/uri',
+ lnum = 1,
+ col = 3,
+ text = 'testing'
+ },
+ }
+ local actual = exec_lua [[
+ local bufnr = vim.uri_to_bufnr("file://fake/uri")
+ local lines = {"testing", "123"}
+ vim.api.nvim_buf_set_lines(bufnr, 0, 1, false, lines)
+ local locations = {
+ {
+ uri = 'file://fake/uri',
+ range = {
+ start = { line = 0, character = 2 },
+ ['end'] = { line = 0, character = 3 },
+ }
+ },
+ }
+ return vim.lsp.util.locations_to_items(locations)
+ ]]
+ eq(expected, actual)
+ end)
+ it('Convert LocationLink[] to items', function()
+ local expected = {
+ {
+ filename = 'fake/uri',
+ lnum = 1,
+ col = 3,
+ text = 'testing'
+ },
+ }
+ local actual = exec_lua [[
+ local bufnr = vim.uri_to_bufnr("file://fake/uri")
+ local lines = {"testing", "123"}
+ vim.api.nvim_buf_set_lines(bufnr, 0, 1, false, lines)
+ local locations = {
+ {
+ targetUri = vim.uri_from_bufnr(bufnr),
+ targetRange = {
+ start = { line = 0, character = 2 },
+ ['end'] = { line = 0, character = 3 },
+ },
+ targetSelectionRange = {
+ start = { line = 0, character = 2 },
+ ['end'] = { line = 0, character = 3 },
+ }
+ },
+ }
+ return vim.lsp.util.locations_to_items(locations)
+ ]]
+ eq(expected, actual)
+ end)
+ end)
+ describe('lsp.util.symbols_to_items', function()
+ describe('convert DocumentSymbol[] to items', function()
+ it('DocumentSymbol has children', function()
+ local expected = {
+ {
+ col = 1,
+ filename = '',
+ kind = 'File',
+ lnum = 2,
+ text = '[File] TestA'
+ },
+ {
+ col = 1,
+ filename = '',
+ kind = 'Module',
+ lnum = 4,
+ text = '[Module] TestB'
+ },
+ {
+ col = 1,
+ filename = '',
+ kind = 'Namespace',
+ lnum = 6,
+ text = '[Namespace] TestC'
+ }
+ }
+ eq(expected, exec_lua [[
+ local doc_syms = {
+ {
+ deprecated = false,
+ detail = "A",
+ kind = 1,
+ name = "TestA",
+ range = {
+ start = {
+ character = 0,
+ line = 1
+ },
+ ["end"] = {
+ character = 0,
+ line = 2
+ }
+ },
+ selectionRange = {
+ start = {
+ character = 0,
+ line = 1
+ },
+ ["end"] = {
+ character = 4,
+ line = 1
+ }
+ },
+ children = {
+ {
+ children = {},
+ deprecated = false,
+ detail = "B",
+ kind = 2,
+ name = "TestB",
+ range = {
+ start = {
+ character = 0,
+ line = 3
+ },
+ ["end"] = {
+ character = 0,
+ line = 4
+ }
+ },
+ selectionRange = {
+ start = {
+ character = 0,
+ line = 3
+ },
+ ["end"] = {
+ character = 4,
+ line = 3
+ }
+ }
+ }
+ }
+ },
+ {
+ deprecated = false,
+ detail = "C",
+ kind = 3,
+ name = "TestC",
+ range = {
+ start = {
+ character = 0,
+ line = 5
+ },
+ ["end"] = {
+ character = 0,
+ line = 6
+ }
+ },
+ selectionRange = {
+ start = {
+ character = 0,
+ line = 5
+ },
+ ["end"] = {
+ character = 4,
+ line = 5
+ }
+ }
+ }
+ }
+ return vim.lsp.util.symbols_to_items(doc_syms, nil)
+ ]])
+ end)
+ it('DocumentSymbol has no children', function()
+ local expected = {
+ {
+ col = 1,
+ filename = '',
+ kind = 'File',
+ lnum = 2,
+ text = '[File] TestA'
+ },
+ {
+ col = 1,
+ filename = '',
+ kind = 'Namespace',
+ lnum = 6,
+ text = '[Namespace] TestC'
+ }
+ }
+ eq(expected, exec_lua [[
+ local doc_syms = {
+ {
+ deprecated = false,
+ detail = "A",
+ kind = 1,
+ name = "TestA",
+ range = {
+ start = {
+ character = 0,
+ line = 1
+ },
+ ["end"] = {
+ character = 0,
+ line = 2
+ }
+ },
+ selectionRange = {
+ start = {
+ character = 0,
+ line = 1
+ },
+ ["end"] = {
+ character = 4,
+ line = 1
+ }
+ },
+ },
+ {
+ deprecated = false,
+ detail = "C",
+ kind = 3,
+ name = "TestC",
+ range = {
+ start = {
+ character = 0,
+ line = 5
+ },
+ ["end"] = {
+ character = 0,
+ line = 6
+ }
+ },
+ selectionRange = {
+ start = {
+ character = 0,
+ line = 5
+ },
+ ["end"] = {
+ character = 4,
+ line = 5
+ }
+ }
+ }
+ }
+ return vim.lsp.util.symbols_to_items(doc_syms, nil)
+ ]])
+ end)
+ end)
+ it('convert SymbolInformation[] to items', function()
+ local expected = {
+ {
+ col = 1,
+ filename = 'test_a',
+ kind = 'File',
+ lnum = 2,
+ text = '[File] TestA'
+ },
+ {
+ col = 1,
+ filename = 'test_b',
+ kind = 'Module',
+ lnum = 4,
+ text = '[Module] TestB'
+ }
+ }
+ eq(expected, exec_lua [[
+ local sym_info = {
+ {
+ deprecated = false,
+ kind = 1,
+ name = "TestA",
+ location = {
+ range = {
+ start = {
+ character = 0,
+ line = 1
+ },
+ ["end"] = {
+ character = 0,
+ line = 2
+ }
+ },
+ uri = "file://test_a"
+ },
+ contanerName = "TestAContainer"
+ },
+ {
+ deprecated = false,
+ kind = 2,
+ name = "TestB",
+ location = {
+ range = {
+ start = {
+ character = 0,
+ line = 3
+ },
+ ["end"] = {
+ character = 0,
+ line = 4
+ }
+ },
+ uri = "file://test_b"
+ },
+ contanerName = "TestBContainer"
+ }
+ }
+ return vim.lsp.util.symbols_to_items(sym_info, nil)
+ ]])
+ end)
+ end)
+
+ describe('lsp.util._get_completion_item_kind_name', function()
+ it('returns the name specified by protocol', function()
+ eq("Text", exec_lua("return vim.lsp.util._get_completion_item_kind_name(1)"))
+ eq("TypeParameter", exec_lua("return vim.lsp.util._get_completion_item_kind_name(25)"))
+ end)
+ it('returns the name not specified by protocol', function()
+ eq("Unknown", exec_lua("return vim.lsp.util._get_completion_item_kind_name(nil)"))
+ eq("Unknown", exec_lua("return vim.lsp.util._get_completion_item_kind_name(vim.NIL)"))
+ eq("Unknown", exec_lua("return vim.lsp.util._get_completion_item_kind_name(1000)"))
+ end)
+ end)
+
+ describe('lsp.util._get_symbol_kind_name', function()
+ it('returns the name specified by protocol', function()
+ eq("File", exec_lua("return vim.lsp.util._get_symbol_kind_name(1)"))
+ eq("TypeParameter", exec_lua("return vim.lsp.util._get_symbol_kind_name(26)"))
+ end)
+ it('returns the name not specified by protocol', function()
+ eq("Unknown", exec_lua("return vim.lsp.util._get_symbol_kind_name(nil)"))
+ eq("Unknown", exec_lua("return vim.lsp.util._get_symbol_kind_name(vim.NIL)"))
+ eq("Unknown", exec_lua("return vim.lsp.util._get_symbol_kind_name(1000)"))
+ end)
+ end)
+
+ describe('lsp.util.jump_to_location', function()
+ local target_bufnr
+
+ before_each(function()
+ target_bufnr = exec_lua [[
+ local bufnr = vim.uri_to_bufnr("file://fake/uri")
+ local lines = {"1st line of text", "å å ɧ 汉语 ↥ 🤦 🦄"}
+ vim.api.nvim_buf_set_lines(bufnr, 0, 1, false, lines)
+ return bufnr
+ ]]
+ end)
+
+ local location = function(start_line, start_char, end_line, end_char)
+ return {
+ uri = "file://fake/uri",
+ range = {
+ start = { line = start_line, character = start_char },
+ ["end"] = { line = end_line, character = end_char },
+ },
+ }
+ end
+
+ local jump = function(msg)
+ eq(true, exec_lua('return vim.lsp.util.jump_to_location(...)', msg))
+ eq(target_bufnr, exec_lua[[return vim.fn.bufnr('%')]])
+ return {
+ line = exec_lua[[return vim.fn.line('.')]],
+ col = exec_lua[[return vim.fn.col('.')]],
+ }
+ end
+
+ it('jumps to a Location', function()
+ local pos = jump(location(0, 9, 0, 9))
+ eq(1, pos.line)
+ eq(10, pos.col)
+ end)
+
+ it('jumps to a LocationLink', function()
+ local pos = jump({
+ targetUri = "file://fake/uri",
+ targetSelectionRange = {
+ start = { line = 0, character = 4 },
+ ["end"] = { line = 0, character = 4 },
+ },
+ targetRange = {
+ start = { line = 1, character = 5 },
+ ["end"] = { line = 1, character = 5 },
+ },
+ })
+ eq(1, pos.line)
+ eq(5, pos.col)
+ end)
+
+ it('jumps to the correct multibyte column', function()
+ local pos = jump(location(1, 2, 1, 2))
+ eq(2, pos.line)
+ eq(4, pos.col)
+ eq('å', exec_lua[[return vim.fn.expand('<cword>')]])
+ end)
+ end)
+
+ describe('lsp.util._make_floating_popup_size', function()
+ before_each(function()
+ exec_lua [[ contents =
+ {"text tαxt txtα tex",
+ "text tααt tααt text",
+ "text tαxt tαxt"}
+ ]]
+ end)
+
+ it('calculates size correctly', function()
+ eq({19,3}, exec_lua[[ return {vim.lsp.util._make_floating_popup_size(contents)} ]])
+ end)
+
+ it('calculates size correctly with wrapping', function()
+ eq({15,5}, exec_lua[[ return {vim.lsp.util._make_floating_popup_size(contents,{width = 15, wrap_at = 14})} ]])
+ end)
+ end)
+
+ describe('lsp.util.get_effective_tabstop', function()
+ local function test_tabstop(tabsize, softtabstop)
+ exec_lua(string.format([[
+ vim.api.nvim_buf_set_option(0, 'softtabstop', %d)
+ vim.api.nvim_buf_set_option(0, 'tabstop', 2)
+ vim.api.nvim_buf_set_option(0, 'shiftwidth', 3)
+ ]], softtabstop))
+ eq(tabsize, exec_lua('return vim.lsp.util.get_effective_tabstop()'))
+ end
+
+ it('with softtabstop = 1', function() test_tabstop(1, 1) end)
+ it('with softtabstop = 0', function() test_tabstop(2, 0) end)
+ it('with softtabstop = -1', function() test_tabstop(3, -1) end)
+ end)
+
+ describe('vim.lsp.buf.outgoing_calls', function()
+ it('does nothing for an empty response', function()
+ local qflist_count = exec_lua([=[
+ require'vim.lsp.callbacks'['callHierarchy/outgoingCalls']()
+ return #vim.fn.getqflist()
+ ]=])
+ eq(0, qflist_count)
+ end)
+
+ it('opens the quickfix list with the right caller', function()
+ local qflist = exec_lua([=[
+ local rust_analyzer_response = { {
+ fromRanges = { {
+ ['end'] = {
+ character = 7,
+ line = 3
+ },
+ start = {
+ character = 4,
+ line = 3
+ }
+ } },
+ to = {
+ detail = "fn foo()",
+ kind = 12,
+ name = "foo",
+ range = {
+ ['end'] = {
+ character = 11,
+ line = 0
+ },
+ start = {
+ character = 0,
+ line = 0
+ }
+ },
+ selectionRange = {
+ ['end'] = {
+ character = 6,
+ line = 0
+ },
+ start = {
+ character = 3,
+ line = 0
+ }
+ },
+ uri = "file:///src/main.rs"
+ }
+ } }
+ local callback = require'vim.lsp.callbacks'['callHierarchy/outgoingCalls']
+ callback(nil, nil, rust_analyzer_response)
+ return vim.fn.getqflist()
+ ]=])
+
+ local expected = { {
+ bufnr = 2,
+ col = 5,
+ lnum = 4,
+ module = "",
+ nr = 0,
+ pattern = "",
+ text = "foo",
+ type = "",
+ valid = 1,
+ vcol = 0
+ } }
+
+ eq(expected, qflist)
+ end)
+ end)
+
+ describe('vim.lsp.buf.incoming_calls', function()
+ it('does nothing for an empty response', function()
+ local qflist_count = exec_lua([=[
+ require'vim.lsp.callbacks'['callHierarchy/incomingCalls']()
+ return #vim.fn.getqflist()
+ ]=])
+ eq(0, qflist_count)
+ end)
+
+ it('opens the quickfix list with the right callee', function()
+ local qflist = exec_lua([=[
+ local rust_analyzer_response = { {
+ from = {
+ detail = "fn main()",
+ kind = 12,
+ name = "main",
+ range = {
+ ['end'] = {
+ character = 1,
+ line = 4
+ },
+ start = {
+ character = 0,
+ line = 2
+ }
+ },
+ selectionRange = {
+ ['end'] = {
+ character = 7,
+ line = 2
+ },
+ start = {
+ character = 3,
+ line = 2
+ }
+ },
+ uri = "file:///src/main.rs"
+ },
+ fromRanges = { {
+ ['end'] = {
+ character = 7,
+ line = 3
+ },
+ start = {
+ character = 4,
+ line = 3
+ }
+ } }
+ } }
+
+ local callback = require'vim.lsp.callbacks'['callHierarchy/incomingCalls']
+ callback(nil, nil, rust_analyzer_response)
+ return vim.fn.getqflist()
+ ]=])
+
+ local expected = { {
+ bufnr = 2,
+ col = 5,
+ lnum = 4,
+ module = "",
+ nr = 0,
+ pattern = "",
+ text = "main",
+ type = "",
+ valid = 1,
+ vcol = 0
+ } }
+
+ eq(expected, qflist)
+ end)
+ end)
+end)
diff --git a/test/functional/plugin/man_spec.lua b/test/functional/plugin/man_spec.lua
index d95995797e..e5b2e7dc1f 100644
--- a/test/functional/plugin/man_spec.lua
+++ b/test/functional/plugin/man_spec.lua
@@ -19,38 +19,32 @@ describe(':Man', function()
u = { underline = true },
bi = { bold = true, italic = true },
biu = { bold = true, italic = true, underline = true },
- })
- screen:set_default_attr_ignore({
- { foreground = Screen.colors.Blue }, -- control chars
- { bold = true, foreground = Screen.colors.Blue } -- empty line '~'s
+ c = { foreground = Screen.colors.Blue }, -- control chars
+ eob = { bold = true, foreground = Screen.colors.Blue } -- empty line '~'s
})
screen:attach()
end)
- after_each(function()
- screen:detach()
- end)
-
it('clears backspaces from text and adds highlights', function()
rawfeed([[
ithis i<C-v><C-h>is<C-v><C-h>s a<C-v><C-h>a test
with _<C-v><C-h>o_<C-v><C-h>v_<C-v><C-h>e_<C-v><C-h>r_<C-v><C-h>s_<C-v><C-h>t_<C-v><C-h>r_<C-v><C-h>u_<C-v><C-h>c_<C-v><C-h>k text<ESC>]])
- screen:expect([[
- this i^His^Hs a^Ha test |
- with _^Ho_^Hv_^He_^Hr_^Hs_^Ht_^Hr_^Hu_^Hc_^Hk tex^t |
- ~ |
- ~ |
- |
- ]])
+ screen:expect{grid=[[
+ this i{c:^H}is{c:^H}s a{c:^H}a test |
+ with _{c:^H}o_{c:^H}v_{c:^H}e_{c:^H}r_{c:^H}s_{c:^H}t_{c:^H}r_{c:^H}u_{c:^H}c_{c:^H}k tex^t |
+ {eob:~ }|
+ {eob:~ }|
+ |
+ ]]}
eval('man#init_pager()')
screen:expect([[
^this {b:is} {b:a} test |
with {u:overstruck} text |
- ~ |
- ~ |
+ {eob:~ }|
+ {eob:~ }|
|
]])
end)
@@ -60,21 +54,21 @@ describe(':Man', function()
ithis <C-v><ESC>[1mis <C-v><ESC>[3ma <C-v><ESC>[4mtest<C-v><ESC>[0m
<C-v><ESC>[4mwith<C-v><ESC>[24m <C-v><ESC>[4mescaped<C-v><ESC>[24m <C-v><ESC>[4mtext<C-v><ESC>[24m<ESC>]])
- screen:expect([=[
- this ^[[1mis ^[[3ma ^[[4mtest^[[0m |
- ^[[4mwith^[[24m ^[[4mescaped^[[24m ^[[4mtext^[[24^m |
- ~ |
- ~ |
- |
- ]=])
+ screen:expect{grid=[=[
+ this {c:^[}[1mis {c:^[}[3ma {c:^[}[4mtest{c:^[}[0m |
+ {c:^[}[4mwith{c:^[}[24m {c:^[}[4mescaped{c:^[}[24m {c:^[}[4mtext{c:^[}[24^m |
+ {eob:~ }|
+ {eob:~ }|
+ |
+ ]=]}
eval('man#init_pager()')
screen:expect([[
^this {b:is }{bi:a }{biu:test} |
{u:with} {u:escaped} {u:text} |
- ~ |
- ~ |
+ {eob:~ }|
+ {eob:~ }|
|
]])
end)
@@ -88,8 +82,8 @@ describe(':Man', function()
screen:expect([[
^this {b:is} {b:あ} test |
with {u:överstrũck} te{i:xt¶} |
- ~ |
- ~ |
+ {eob:~ }|
+ {eob:~ }|
|
]])
end)
@@ -105,7 +99,7 @@ describe(':Man', function()
{b:^_begins} |
{b:mid_dle} |
{u:mid_dle} |
- ~ |
+ {eob:~ }|
|
]])
end)
@@ -121,7 +115,7 @@ describe(':Man', function()
^· {b:·} |
{b:·} |
{b:·} double |
- ~ |
+ {eob:~ }|
|
]])
end)
diff --git a/test/functional/preload.lua b/test/functional/preload.lua
index 1107b45d54..24a3977e6b 100644
--- a/test/functional/preload.lua
+++ b/test/functional/preload.lua
@@ -2,3 +2,13 @@
-- Busted started doing this to help provide more isolation. See issue #62
-- for more information about this.
local helpers = require('test.functional.helpers')(nil)
+local iswin = helpers.iswin
+
+if iswin() then
+ local ffi = require('ffi')
+ ffi.cdef[[
+ typedef int errno_t;
+ errno_t _set_fmode(int mode);
+ ]]
+ ffi.C._set_fmode(0x8000)
+end
diff --git a/test/functional/provider/clipboard_spec.lua b/test/functional/provider/clipboard_spec.lua
index b2d75db745..da9dd09129 100644
--- a/test/functional/provider/clipboard_spec.lua
+++ b/test/functional/provider/clipboard_spec.lua
@@ -88,6 +88,11 @@ describe('clipboard', function()
before_each(function()
clear()
screen = Screen.new(72, 4)
+ screen:set_default_attr_ids({
+ [0] = {bold = true, foreground = Screen.colors.Blue},
+ [1] = {foreground = Screen.colors.Grey100, background = Screen.colors.Red},
+ [2] = {bold = true, foreground = Screen.colors.SeaGreen4},
+ })
screen:attach()
command("set display-=msgsep")
end)
@@ -103,22 +108,22 @@ describe('clipboard', function()
feed_command('redir @+> | :silent echo system("cat CONTRIBUTING.md") | redir END')
screen:expect([[
^ |
- ~ |
- ~ |
+ {0:~ }|
+ {0:~ }|
clipboard: No provider. Try ":checkhealth" or ":h clipboard". |
- ]], nil, {{bold = true, foreground = Screen.colors.Blue}})
+ ]])
end)
it('`:redir @+>|bogus_cmd|redir END` + invalid g:clipboard must not recurse #7184',
function()
command("let g:clipboard = 'bogus'")
feed_command('redir @+> | bogus_cmd | redir END')
- screen:expect([[
- ~ |
+ screen:expect{grid=[[
+ {0:~ }|
clipboard: No provider. Try ":checkhealth" or ":h clipboard". |
- E492: Not an editor command: bogus_cmd | redir END |
- Press ENTER or type command to continue^ |
- ]], nil, {{bold = true, foreground = Screen.colors.Blue}})
+ {1:E492: Not an editor command: bogus_cmd | redir END} |
+ {2:Press ENTER or type command to continue}^ |
+ ]]}
end)
it('invalid g:clipboard shows hint if :redir is not active', function()
@@ -131,10 +136,10 @@ describe('clipboard', function()
feed_command('let @+="foo"')
screen:expect([[
^ |
- ~ |
- ~ |
+ {0:~ }|
+ {0:~ }|
clipboard: No provider. Try ":checkhealth" or ":h clipboard". |
- ]], nil, {{bold = true, foreground = Screen.colors.Blue}})
+ ]])
end)
it('valid g:clipboard', function()
@@ -266,13 +271,17 @@ describe('clipboard (with fake clipboard.vim)', function()
function()
local screen = Screen.new(72, 4)
screen:attach()
+ screen:set_default_attr_ids({
+ [0] = {bold = true, foreground = Screen.colors.Blue},
+ [1] = {foreground = Screen.colors.Grey100, background = Screen.colors.Red},
+ })
feed_command('redir @+> | bogus_cmd | redir END')
screen:expect([[
^ |
- ~ |
- ~ |
- E492: Not an editor command: bogus_cmd | redir END |
- ]], nil, {{bold = true, foreground = Screen.colors.Blue}})
+ {0:~ }|
+ {0:~ }|
+ {1:E492: Not an editor command: bogus_cmd | redir END} |
+ ]])
end)
it('has independent "* and unnamed registers by default', function()
@@ -637,6 +646,9 @@ describe('clipboard (with fake clipboard.vim)', function()
feed_command('set mouse=a')
local screen = Screen.new(30, 5)
+ screen:set_default_attr_ids({
+ [0] = {bold = true, foreground = Screen.colors.Blue},
+ })
screen:attach()
insert([[
the source
@@ -646,10 +658,10 @@ describe('clipboard (with fake clipboard.vim)', function()
screen:expect([[
the ^source |
a target |
- ~ |
- ~ |
+ {0:~ }|
+ {0:~ }|
|
- ]], nil, {{bold = true, foreground = Screen.colors.Blue}})
+ ]])
feed('<MiddleMouse><0,1>')
expect([[
diff --git a/test/functional/provider/define_spec.lua b/test/functional/provider/define_spec.lua
index 51a8831274..1d50ce0a56 100644
--- a/test/functional/provider/define_spec.lua
+++ b/test/functional/provider/define_spec.lua
@@ -89,6 +89,21 @@ local function command_specs_for(fn, sync, first_arg_factory, init)
runx(sync, handler, on_setup)
end)
+ it('with nargs/double-quote', function()
+ call(fn, args..', {"nargs": "*"}')
+ local function on_setup()
+ command('RpcCommand "arg1" "arg2" "arg3"')
+ end
+
+ local function handler(method, arguments)
+ eq('test-handler', method)
+ eq({'"arg1"', '"arg2"', '"arg3"'}, arguments[1])
+ return ''
+ end
+
+ runx(sync, handler, on_setup)
+ end)
+
it('with range', function()
call(fn,args..', {"range": ""}')
local function on_setup()
diff --git a/test/functional/provider/nodejs_spec.lua b/test/functional/provider/nodejs_spec.lua
index 07a00f8a8c..661a6f4f94 100644
--- a/test/functional/provider/nodejs_spec.lua
+++ b/test/functional/provider/nodejs_spec.lua
@@ -8,8 +8,9 @@ local retry = helpers.retry
do
clear()
- if missing_provider('node') then
- pending("Missing nodejs host, or nodejs version is too old.", function()end)
+ local reason = missing_provider('node')
+ if reason then
+ pending(string.format("Missing nodejs host, or nodejs version is too old (%s)", reason), function() end)
return
end
end
diff --git a/test/functional/provider/perl_spec.lua b/test/functional/provider/perl_spec.lua
new file mode 100644
index 0000000000..7b446e4ab3
--- /dev/null
+++ b/test/functional/provider/perl_spec.lua
@@ -0,0 +1,80 @@
+local helpers = require('test.functional.helpers')(after_each)
+local eq, clear = helpers.eq, helpers.clear
+local missing_provider = helpers.missing_provider
+local command = helpers.command
+local write_file = helpers.write_file
+local eval = helpers.eval
+local retry = helpers.retry
+
+do
+ clear()
+ local reason = missing_provider('perl')
+ if reason then
+ pending(string.format("Missing perl host, or perl version is too old (%s)", reason), function() end)
+ return
+ end
+end
+
+before_each(function()
+ clear()
+end)
+
+describe('perl host', function()
+ if helpers.pending_win32(pending) then return end
+ teardown(function ()
+ os.remove('Xtest-perl-hello.pl')
+ os.remove('Xtest-perl-hello-plugin.pl')
+ end)
+
+ it('works', function()
+ local fname = 'Xtest-perl-hello.pl'
+ write_file(fname, [[
+ package main;
+ use strict;
+ use warnings;
+ use Neovim::Ext;
+ use Neovim::Ext::MsgPack::RPC;
+
+ my $session = Neovim::Ext::MsgPack::RPC::socket_session($ENV{NVIM_LISTEN_ADDRESS});
+ my $nvim = Neovim::Ext::from_session($session);
+ $nvim->command('let g:job_out = "hello"');
+ 1;
+ ]])
+ command('let g:job_id = jobstart(["perl", "'..fname..'"])')
+ retry(nil, 3000, function() eq('hello', eval('g:job_out')) end)
+ end)
+
+ it('plugin works', function()
+ local fname = 'Xtest-perl-hello-plugin.pl'
+ write_file(fname, [[
+ package TestPlugin;
+ use strict;
+ use warnings;
+ use parent qw(Neovim::Ext::Plugin);
+
+ __PACKAGE__->register;
+
+ @{TestPlugin::commands} = ();
+ @{TestPlugin::specs} = ();
+ sub test_command :nvim_command('TestCommand')
+ {
+ my ($this) = @_;
+ $this->nvim->command('let g:job_out = "hello-plugin"');
+ }
+
+ package main;
+ use strict;
+ use warnings;
+ use Neovim::Ext;
+ use Neovim::Ext::MsgPack::RPC;
+
+ my $session = Neovim::Ext::MsgPack::RPC::socket_session($ENV{NVIM_LISTEN_ADDRESS});
+ my $nvim = Neovim::Ext::from_session($session);
+ my $plugin = TestPlugin->new($nvim);
+ $plugin->test_command();
+ 1;
+ ]])
+ command('let g:job_id = jobstart(["perl", "'..fname..'"])')
+ retry(nil, 3000, function() eq('hello-plugin', eval('g:job_out')) end)
+ end)
+end)
diff --git a/test/functional/provider/python3_spec.lua b/test/functional/provider/python3_spec.lua
index b319d5e948..d254edc7d5 100644
--- a/test/functional/provider/python3_spec.lua
+++ b/test/functional/provider/python3_spec.lua
@@ -10,13 +10,14 @@ local pcall_err = helpers.pcall_err
do
clear()
- if missing_provider('python3') then
+ local reason = missing_provider('python3')
+ if reason then
it(':python3 reports E319 if provider is missing', function()
local expected = [[Vim%(py3.*%):E319: No "python3" provider found.*]]
matches(expected, pcall_err(command, 'py3 print("foo")'))
matches(expected, pcall_err(command, 'py3file foo'))
end)
- pending('Python 3 (or the pynvim module) is broken/missing', function() end)
+ pending(string.format('Python 3 (or the pynvim module) is broken/missing (%s)', reason), function() end)
return
end
end
diff --git a/test/functional/provider/python_spec.lua b/test/functional/provider/python_spec.lua
index 986f10b2e9..d60d8d1001 100644
--- a/test/functional/provider/python_spec.lua
+++ b/test/functional/provider/python_spec.lua
@@ -18,13 +18,14 @@ local pcall_err = helpers.pcall_err
do
clear()
- if missing_provider('python') then
+ local reason = missing_provider('python')
+ if reason then
it(':python reports E319 if provider is missing', function()
local expected = [[Vim%(py.*%):E319: No "python" provider found.*]]
matches(expected, pcall_err(command, 'py print("foo")'))
matches(expected, pcall_err(command, 'pyfile foo'))
end)
- pending('Python 2 (or the pynvim module) is broken/missing', function() end)
+ pending(string.format('Python 2 (or the pynvim module) is broken/missing (%s)', reason), function() end)
return
end
end
diff --git a/test/functional/provider/ruby_spec.lua b/test/functional/provider/ruby_spec.lua
index d20adde2ef..bb7d23ede6 100644
--- a/test/functional/provider/ruby_spec.lua
+++ b/test/functional/provider/ruby_spec.lua
@@ -18,13 +18,14 @@ local pcall_err = helpers.pcall_err
do
clear()
- if missing_provider('ruby') then
+ local reason = missing_provider('ruby')
+ if reason then
it(':ruby reports E319 if provider is missing', function()
local expected = [[Vim%(ruby.*%):E319: No "ruby" provider found.*]]
matches(expected, pcall_err(command, 'ruby puts "foo"'))
matches(expected, pcall_err(command, 'rubyfile foo'))
end)
- pending("Missing neovim RubyGem.", function() end)
+ pending(string.format('Missing neovim RubyGem (%s)', reason), function() end)
return
end
end
diff --git a/test/functional/shada/errors_spec.lua b/test/functional/shada/errors_spec.lua
index 66c8c4ad2f..77a41caec7 100644
--- a/test/functional/shada/errors_spec.lua
+++ b/test/functional/shada/errors_spec.lua
@@ -1,7 +1,7 @@
-- ShaDa errors handling support
local helpers = require('test.functional.helpers')(after_each)
-local nvim_command, eq, exc_exec, redir_exec =
- helpers.command, helpers.eq, helpers.exc_exec, helpers.redir_exec
+local nvim_command, eq, exc_exec =
+ helpers.command, helpers.eq, helpers.exc_exec
local shada_helpers = require('test.functional.shada.helpers')
local reset, clear, get_shada_rw =
@@ -494,23 +494,6 @@ $
eq(0, exc_exec('wshada! ' .. shada_fname))
end)
- it('errors when a funcref is stored in a variable', function()
- nvim_command('let F = function("tr")')
- nvim_command('set shada+=!')
- eq('\nE5004: Error while dumping variable g:F, itself: attempt to dump function reference'
- .. '\nE574: Failed to write variable F',
- redir_exec('wshada'))
- end)
-
- it('errors when a self-referencing list is stored in a variable', function()
- nvim_command('let L = []')
- nvim_command('call add(L, L)')
- nvim_command('set shada+=!')
- eq('\nE5005: Unable to dump variable g:L: container references itself in index 0'
- .. '\nE574: Failed to write variable L',
- redir_exec('wshada'))
- end)
-
it('errors with too large items', function()
wshada({
1, 206, 70, 90, 31, 179, 86, 133, 169, 103, 101, 110, 101, 114, 97,
diff --git a/test/functional/shada/history_spec.lua b/test/functional/shada/history_spec.lua
index 78b5c77857..9291f5e100 100644
--- a/test/functional/shada/history_spec.lua
+++ b/test/functional/shada/history_spec.lua
@@ -2,6 +2,7 @@
local helpers = require('test.functional.helpers')(after_each)
local nvim_command, funcs, meths, nvim_feed, eq =
helpers.command, helpers.funcs, helpers.meths, helpers.feed, helpers.eq
+local eval = helpers.eval
local shada_helpers = require('test.functional.shada.helpers')
local reset, clear = shada_helpers.reset, shada_helpers.clear
@@ -237,4 +238,13 @@ describe('ShaDa support code', function()
nvim_command('wshada')
end)
+ it('does not crash when number of history save to zero (#11497)', function()
+ nvim_command('set shada=\'10')
+ nvim_feed(':" Test\n')
+ nvim_command('wshada')
+ nvim_command('set shada=\'10,:0')
+ nvim_command('wshada')
+ eq(2, eval('1+1')) -- check nvim still running
+ end)
+
end)
diff --git a/test/functional/shada/variables_spec.lua b/test/functional/shada/variables_spec.lua
index 74bbceddcc..cc0e7fa537 100644
--- a/test/functional/shada/variables_spec.lua
+++ b/test/functional/shada/variables_spec.lua
@@ -1,7 +1,7 @@
-- ShaDa variables saving/reading support
local helpers = require('test.functional.helpers')(after_each)
-local meths, funcs, nvim_command, eq, exc_exec =
- helpers.meths, helpers.funcs, helpers.command, helpers.eq, helpers.exc_exec
+local meths, funcs, nvim_command, eq =
+ helpers.meths, helpers.funcs, helpers.command, helpers.eq
local shada_helpers = require('test.functional.shada.helpers')
local reset, clear = shada_helpers.reset, shada_helpers.clear
@@ -121,28 +121,39 @@ describe('ShaDa support code', function()
meths.get_var('NESTEDVAR'))
end)
- it('errors and writes when a funcref is stored in a variable',
+ it('ignore when a funcref is stored in a variable',
function()
nvim_command('let F = function("tr")')
meths.set_var('U', '10')
nvim_command('set shada+=!')
- eq('Vim(wshada):E5004: Error while dumping variable g:F, itself: attempt to dump function reference',
- exc_exec('wshada'))
- meths.set_option('shada', '')
- reset('set shada+=!')
+ nvim_command('wshada')
+ reset()
+ nvim_command('set shada+=!')
+ nvim_command('rshada')
eq('10', meths.get_var('U'))
end)
- it('errors and writes when a self-referencing list is stored in a variable',
+ it('ignore when a partial is stored in a variable',
+ function()
+ nvim_command('let P = { -> 1 }')
+ meths.set_var('U', '10')
+ nvim_command('set shada+=!')
+ nvim_command('wshada')
+ reset()
+ nvim_command('set shada+=!')
+ nvim_command('rshada')
+ eq('10', meths.get_var('U'))
+ end)
+
+ it('ignore when a self-referencing list is stored in a variable',
function()
meths.set_var('L', {})
nvim_command('call add(L, L)')
meths.set_var('U', '10')
nvim_command('set shada+=!')
- eq('Vim(wshada):E5005: Unable to dump variable g:L: container references itself in index 0',
- exc_exec('wshada'))
- meths.set_option('shada', '')
- reset('set shada+=!')
+ nvim_command('wshada')
+ reset()
+ nvim_command('rshada')
eq('10', meths.get_var('U'))
end)
end)
diff --git a/test/functional/terminal/buffer_spec.lua b/test/functional/terminal/buffer_spec.lua
index 1763574bf9..6372cd935e 100644
--- a/test/functional/terminal/buffer_spec.lua
+++ b/test/functional/terminal/buffer_spec.lua
@@ -5,6 +5,7 @@ local wait = helpers.wait
local eval, feed_command, source = helpers.eval, helpers.feed_command, helpers.source
local eq, neq = helpers.eq, helpers.neq
local write_file = helpers.write_file
+local command= helpers.command
describe(':terminal buffer', function()
local screen
@@ -16,6 +17,18 @@ describe(':terminal buffer', function()
screen = thelpers.screen_setup()
end)
+ it('terminal-mode forces various options', function()
+ feed([[<C-\><C-N>]])
+ command('setlocal cursorline cursorcolumn scrolloff=4 sidescrolloff=7')
+ eq({ 1, 1, 4, 7 }, eval('[&l:cursorline, &l:cursorcolumn, &l:scrolloff, &l:sidescrolloff]'))
+ eq('n', eval('mode()'))
+
+ -- Enter terminal-mode ("insert" mode in :terminal).
+ feed('i')
+ eq('t', eval('mode()'))
+ eq({ 0, 0, 0, 0 }, eval('[&l:cursorline, &l:cursorcolumn, &l:scrolloff, &l:sidescrolloff]'))
+ end)
+
describe('when a new file is edited', function()
before_each(function()
feed('<c-\\><c-n>:set bufhidden=wipe<cr>:enew<cr>')
@@ -59,7 +72,7 @@ describe(':terminal buffer', function()
end)
it('does not create swap files', function()
- local swapfile = nvim('command_output', 'swapname'):gsub('\n', '')
+ local swapfile = nvim('exec', 'swapname', true):gsub('\n', '')
eq(nil, io.open(swapfile))
end)
@@ -208,22 +221,38 @@ describe(':terminal buffer', function()
feed_command('terminal')
feed('<c-\\><c-n>')
feed_command('confirm bdelete')
- screen:expect{any='Close "term://', attr_ignore=true}
+ screen:expect{any='Close "term://'}
end)
it('with &confirm', function()
feed_command('terminal')
feed('<c-\\><c-n>')
feed_command('bdelete')
- screen:expect{any='E89', attr_ignore=true}
+ screen:expect{any='E89'}
feed('<cr>')
eq('terminal', eval('&buftype'))
feed_command('set confirm | bdelete')
- screen:expect{any='Close "term://', attr_ignore=true}
+ screen:expect{any='Close "term://'}
feed('y')
neq('terminal', eval('&buftype'))
end)
end)
+
+ it('it works with set rightleft #11438', function()
+ local columns = eval('&columns')
+ feed(string.rep('a', columns))
+ command('set rightleft')
+ screen:expect([[
+ ydaer ytt|
+ {1:a}aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa|
+ |
+ |
+ |
+ |
+ {3:-- TERMINAL --} |
+ ]])
+ command('bdelete!')
+ end)
end)
describe('No heap-buffer-overflow when using', function()
@@ -247,3 +276,12 @@ describe('No heap-buffer-overflow when using', function()
feed_command('bdelete!')
end)
end)
+
+describe('No heap-buffer-overflow when', function()
+ it('set nowrap and send long line #11548', function()
+ feed_command('set nowrap')
+ feed_command('autocmd TermOpen * startinsert')
+ feed_command('call feedkeys("4000ai\\<esc>:terminal!\\<cr>")')
+ eq(2, eval('1+1'))
+ end)
+end)
diff --git a/test/functional/terminal/edit_spec.lua b/test/functional/terminal/edit_spec.lua
index d213bae7b3..fabc5524ed 100644
--- a/test/functional/terminal/edit_spec.lua
+++ b/test/functional/terminal/edit_spec.lua
@@ -5,9 +5,12 @@ local curbufmeths = helpers.curbufmeths
local curwinmeths = helpers.curwinmeths
local nvim_dir = helpers.nvim_dir
local command = helpers.command
+local funcs = helpers.funcs
local meths = helpers.meths
local clear = helpers.clear
local eq = helpers.eq
+local matches = helpers.matches
+local pesc = helpers.pesc
describe(':edit term://*', function()
local get_screen = function(columns, lines)
@@ -28,7 +31,8 @@ describe(':edit term://*', function()
command('edit term://')
local termopen_runs = meths.get_var('termopen_runs')
eq(1, #termopen_runs)
- eq(termopen_runs[1], termopen_runs[1]:match('^term://.//%d+:$'))
+ local cwd = funcs.fnamemodify('.', ':p:~'):gsub([[[\/]*$]], '')
+ matches('^term://'..pesc(cwd)..'//%d+:$', termopen_runs[1])
end)
it("runs TermOpen early enough to set buffer-local 'scrollback'", function()
diff --git a/test/functional/terminal/ex_terminal_spec.lua b/test/functional/terminal/ex_terminal_spec.lua
index b0019d2d37..138befd978 100644
--- a/test/functional/terminal/ex_terminal_spec.lua
+++ b/test/functional/terminal/ex_terminal_spec.lua
@@ -245,12 +245,14 @@ describe(':terminal (with fake shell)', function()
it('works with gf', function()
command('set shellxquote=') -- win: avoid extra quotes
terminal_with_fake_shell([[echo "scripts/shadacat.py"]])
+ retry(nil, 4 * screen.timeout, function()
screen:expect([[
^ready $ echo "scripts/shadacat.py" |
|
[Process exited 0] |
:terminal echo "scripts/shadacat.py" |
]])
+ end)
feed([[<C-\><C-N>]])
eq('term://', string.match(eval('bufname("%")'), "^term://"))
feed([[ggf"lgf]])
diff --git a/test/functional/terminal/helpers.lua b/test/functional/terminal/helpers.lua
index f6cab6bd1e..d909888613 100644
--- a/test/functional/terminal/helpers.lua
+++ b/test/functional/terminal/helpers.lua
@@ -52,7 +52,7 @@ local function screen_setup(extra_rows, command, cols, opts)
[3] = {bold = true},
[4] = {foreground = 12},
[5] = {bold = true, reverse = true},
- [6] = {background = 11},
+ -- 6 was a duplicate item
[7] = {foreground = 130},
[8] = {foreground = 15, background = 1}, -- error message
[9] = {foreground = 4},
diff --git a/test/functional/terminal/highlight_spec.lua b/test/functional/terminal/highlight_spec.lua
index 06a6fd6f2b..8d3f0218af 100644
--- a/test/functional/terminal/highlight_spec.lua
+++ b/test/functional/terminal/highlight_spec.lua
@@ -121,13 +121,12 @@ it(':terminal highlight has lower precedence than editor #9964', function()
local screen = Screen.new(30, 4)
screen:set_default_attr_ids({
-- "Normal" highlight emitted by the child nvim process.
- N_child = {foreground = tonumber('0x4040ff'), background = tonumber('0xffff40')},
- -- "Search" highlight emitted by the child nvim process.
- S_child = {background = tonumber('0xffff40'), italic = true, foreground = tonumber('0x4040ff')},
+ N_child = {foreground = tonumber('0x4040ff'), background = tonumber('0xffff40'), fg_indexed=true, bg_indexed=true},
-- "Search" highlight in the parent nvim process.
S = {background = Screen.colors.Green, italic = true, foreground = Screen.colors.Red},
-- "Question" highlight in the parent nvim process.
- Q = {background = tonumber('0xffff40'), bold = true, foreground = Screen.colors.SeaGreen4},
+ -- note: bg is indexed as it comes from the (cterm) child, while fg isn't as it comes from (rgb) parent
+ Q = {background = tonumber('0xffff40'), bold = true, foreground = Screen.colors.SeaGreen4, bg_indexed=true},
})
screen:attach({rgb=true})
-- Child nvim process in :terminal (with cterm colors).
@@ -160,6 +159,54 @@ it(':terminal highlight has lower precedence than editor #9964', function()
]])
end)
+describe(':terminal highlight forwarding', function()
+ local screen
+
+ before_each(function()
+ clear()
+ screen = Screen.new(50, 7)
+ screen:set_rgb_cterm(true)
+ screen:set_default_attr_ids({
+ [1] = {{reverse = true}, {reverse = true}},
+ [2] = {{bold = true}, {bold = true}},
+ [3] = {{fg_indexed = true, foreground = tonumber('0xe0e000')}, {foreground = 3}},
+ [4] = {{foreground = tonumber('0xff8000')}, {}},
+ })
+ screen:attach()
+ command('enew | call termopen(["'..nvim_dir..'/tty-test"])')
+ feed('i')
+ screen:expect([[
+ tty ready |
+ {1: } |
+ |
+ |
+ |
+ |
+ {2:-- TERMINAL --} |
+ ]])
+ end)
+
+ it('will handle cterm and rgb attributes', function()
+ if helpers.pending_win32(pending) then return end
+ thelpers.set_fg(3)
+ thelpers.feed_data('text')
+ thelpers.feed_termcode('[38:2:255:128:0m')
+ thelpers.feed_data('color')
+ thelpers.clear_attrs()
+ thelpers.feed_data('text')
+ screen:expect{grid=[[
+ tty ready |
+ {3:text}{4:color}text{1: } |
+ |
+ |
+ |
+ |
+ {2:-- TERMINAL --} |
+ ]]}
+ end)
+end)
+
+
describe(':terminal highlight with custom palette', function()
local screen
@@ -167,7 +214,7 @@ describe(':terminal highlight with custom palette', function()
clear()
screen = Screen.new(50, 7)
screen:set_default_attr_ids({
- [1] = {foreground = tonumber('0x123456')},
+ [1] = {foreground = tonumber('0x123456')}, -- no fg_indexed when overriden
[2] = {foreground = 12},
[3] = {bold = true, reverse = true},
[5] = {background = 11},
diff --git a/test/functional/terminal/mouse_spec.lua b/test/functional/terminal/mouse_spec.lua
index 64f437f206..0eb5901b3b 100644
--- a/test/functional/terminal/mouse_spec.lua
+++ b/test/functional/terminal/mouse_spec.lua
@@ -31,10 +31,6 @@ describe(':terminal mouse', function()
]])
end)
- after_each(function()
- screen:detach()
- end)
-
describe('when the terminal has focus', function()
it('will exit focus on mouse-scroll', function()
eq('t', eval('mode()'))
@@ -91,6 +87,36 @@ describe(':terminal mouse', function()
{3:-- TERMINAL --} |
]])
end)
+
+ it('will forward mouse clicks to the program with the correct even if set nu', function()
+ if helpers.pending_win32(pending) then return end
+ nvim('command', 'set number')
+ -- When the display area such as a number is clicked, it returns to the
+ -- normal mode.
+ feed('<LeftMouse><3,0>')
+ eq('n', eval('mode()'))
+ screen:expect([[
+ {7: 11 }^line28 |
+ {7: 12 }line29 |
+ {7: 13 }line30 |
+ {7: 14 }mouse enabled |
+ {7: 15 }rows: 6, cols: 46 |
+ {7: 16 }{2: } |
+ |
+ ]])
+ -- If click on the coordinate (0,1) of the region of the terminal
+ -- (i.e. the coordinate (4,1) of vim), 'CSI !"' is sent to the terminal.
+ feed('i<LeftMouse><4,1>')
+ screen:expect([[
+ {7: 11 }line28 |
+ {7: 12 }line29 |
+ {7: 13 }line30 |
+ {7: 14 }mouse enabled |
+ {7: 15 }rows: 6, cols: 46 |
+ {7: 16 } !"{1: } |
+ {3:-- TERMINAL --} |
+ ]])
+ end)
end)
describe('with a split window and other buffer', function()
@@ -152,7 +178,7 @@ describe(':terminal mouse', function()
end)
it('wont lose focus if another window is scrolled', function()
- feed('<ScrollWheelUp><0,0><ScrollWheelUp><0,0>')
+ feed('<ScrollWheelUp><4,0><ScrollWheelUp><4,0>')
screen:expect([[
{7: 21 }line │line30 |
{7: 22 }line │rows: 5, cols: 25 |
@@ -162,7 +188,7 @@ describe(':terminal mouse', function()
========== ========== |
{3:-- TERMINAL --} |
]])
- feed('<S-ScrollWheelDown><0,0>')
+ feed('<S-ScrollWheelDown><4,0>')
screen:expect([[
{7: 26 }line │line30 |
{7: 27 }line │rows: 5, cols: 25 |
diff --git a/test/functional/terminal/scrollback_spec.lua b/test/functional/terminal/scrollback_spec.lua
index 7413081510..1df8df6f6e 100644
--- a/test/functional/terminal/scrollback_spec.lua
+++ b/test/functional/terminal/scrollback_spec.lua
@@ -21,10 +21,6 @@ describe(':terminal scrollback', function()
screen = thelpers.screen_setup(nil, nil, 30)
end)
- after_each(function()
- screen:detach()
- end)
-
describe('when the limit is exceeded', function()
before_each(function()
local lines = {}
@@ -406,8 +402,6 @@ describe("'scrollback' option", function()
feed_data(nvim_dir..'/shell-test REP 31 line'..(iswin() and '\r' or '\n'))
screen:expect{any='30: line '}
retry(nil, nil, function() expect_lines(7) end)
-
- screen:detach()
end)
it('deletes lines (only) if necessary', function()
@@ -438,14 +432,32 @@ describe("'scrollback' option", function()
command('sleep 100m')
feed_data(nvim_dir.."/shell-test REP 41 line"..(iswin() and '\r' or '\n'))
- screen:expect{any='40: line '}
+ if iswin() then
+ screen:expect{grid=[[
+ 37: line |
+ 38: line |
+ 39: line |
+ 40: line |
+ |
+ ${1: } |
+ {3:-- TERMINAL --} |
+ ]]}
+ else
+ screen:expect{grid=[[
+ 36: line |
+ 37: line |
+ 38: line |
+ 39: line |
+ 40: line |
+ {MATCH:.*}|
+ {3:-- TERMINAL --} |
+ ]]}
+ end
+ expect_lines(58)
- retry(nil, nil, function() expect_lines(58) end)
-- Verify off-screen state
eq((iswin() and '36: line' or '35: line'), eval("getline(line('w0') - 1)"))
eq((iswin() and '27: line' or '26: line'), eval("getline(line('w0') - 10)"))
-
- screen:detach()
end)
it('defaults to 10000 in :terminal buffers', function()
diff --git a/test/functional/terminal/tui_spec.lua b/test/functional/terminal/tui_spec.lua
index c55093cb0f..c0578c08e1 100644
--- a/test/functional/terminal/tui_spec.lua
+++ b/test/functional/terminal/tui_spec.lua
@@ -45,10 +45,6 @@ describe('TUI', function()
child_session = helpers.connect(child_server)
end)
- after_each(function()
- screen:detach()
- end)
-
-- Wait for mode in the child Nvim (avoid "typeahead race" #10826).
local function wait_for_mode(mode)
retry(nil, nil, function()
@@ -303,9 +299,54 @@ describe('TUI', function()
feed_data('u')
expect_child_buf_lines({'"pasted from terminal"'})
feed_data('u')
+ expect_child_buf_lines({'""'})
+ feed_data('u')
expect_child_buf_lines({''})
end)
+ it('paste: select-mode', function()
+ feed_data('ithis is line 1\nthis is line 2\nline 3 is here\n\027')
+ wait_for_mode('n')
+ screen:expect{grid=[[
+ this is line 1 |
+ this is line 2 |
+ line 3 is here |
+ {1: } |
+ {5:[No Name] [+] }|
+ |
+ {3:-- TERMINAL --} |
+ ]]}
+ -- Select-mode. Use <C-n> to move down.
+ feed_data('gg04lgh\14\14')
+ wait_for_mode('s')
+ feed_data('\027[200~')
+ feed_data('just paste it™')
+ feed_data('\027[201~')
+ screen:expect{grid=[[
+ thisjust paste it™{1:3} is here |
+ |
+ {4:~ }|
+ {4:~ }|
+ {5:[No Name] [+] }|
+ |
+ {3:-- TERMINAL --} |
+ ]]}
+ -- Undo.
+ feed_data('u')
+ expect_child_buf_lines{
+ 'this is line 1',
+ 'this is line 2',
+ 'line 3 is here',
+ '',
+ }
+ -- Redo.
+ feed_data('\18') -- <C-r>
+ expect_child_buf_lines{
+ 'thisjust paste it™3 is here',
+ '',
+ }
+ end)
+
it('paste: terminal mode', function()
feed_data(':set statusline=^^^^^^^\n')
feed_data(':terminal '..nvim_dir..'/tty-test\n')
@@ -447,7 +488,7 @@ describe('TUI', function()
end)
it('paste: recovers from vim.paste() failure', function()
- child_session:request('nvim_execute_lua', [[
+ child_session:request('nvim_exec_lua', [[
_G.save_paste_fn = vim.paste
vim.paste = function(lines, phase) error("fake fail") end
]], {})
@@ -505,7 +546,7 @@ describe('TUI', function()
{3:-- TERMINAL --} |
]]}
-- Paste works if vim.paste() succeeds.
- child_session:request('nvim_execute_lua', [[
+ child_session:request('nvim_exec_lua', [[
vim.paste = _G.save_paste_fn
]], {})
feed_data('\027[200~line A\nline B\n\027[201~')
@@ -524,7 +565,7 @@ describe('TUI', function()
it('paste: vim.paste() cancel (retval=false) #10865', function()
-- This test only exercises the "cancel" case. Use-case would be "dangling
-- paste", but that is not implemented yet. #10865
- child_session:request('nvim_execute_lua', [[
+ child_session:request('nvim_exec_lua', [[
vim.paste = function(lines, phase) return false end
]], {})
feed_data('\027[200~line A\nline B\n\027[201~')
@@ -539,7 +580,7 @@ describe('TUI', function()
|
{4:~ }|
{5: }|
- {8:paste: Error executing lua: vim.lua:197: Vim:E21: }|
+ {MATCH:paste: Error executing lua: vim.lua:%d+: Vim:E21: }|
{8:Cannot make changes, 'modifiable' is off} |
{10:Press ENTER or type command to continue}{1: } |
{3:-- TERMINAL --} |
@@ -564,9 +605,10 @@ describe('TUI', function()
wait_for_mode('i')
-- "bracketed paste"
feed_data('\027[200~'..expected..'\027[201~')
+ -- FIXME: Data race between the two feeds
+ if uname() == 'freebsd' then screen:sleep(1) end
feed_data(' end')
expected = expected..' end'
- expect_child_buf_lines({expected})
screen:expect([[
zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz|
zzzzzzzzzzzzzz end{1: } |
@@ -576,6 +618,24 @@ describe('TUI', function()
{3:-- INSERT --} |
{3:-- TERMINAL --} |
]])
+ expect_child_buf_lines({expected})
+ end)
+
+ it('paste: less-than sign in cmdline #11088', function()
+ local expected = '<'
+ feed_data(':')
+ wait_for_mode('c')
+ -- "bracketed paste"
+ feed_data('\027[200~'..expected..'\027[201~')
+ screen:expect{grid=[[
+ |
+ {4:~ }|
+ {4:~ }|
+ {4:~ }|
+ {5:[No Name] }|
+ :<{1: } |
+ {3:-- TERMINAL --} |
+ ]]}
end)
it('paste: big burst of input', function()
@@ -667,11 +727,11 @@ describe('TUI', function()
screen:set_option('rgb', true)
screen:set_default_attr_ids({
[1] = {reverse = true},
- [2] = {foreground = tonumber('0x4040ff')},
+ [2] = {foreground = tonumber('0x4040ff'), fg_indexed=true},
[3] = {bold = true, reverse = true},
[4] = {bold = true},
- [5] = {reverse = true, foreground = tonumber('0xe0e000')},
- [6] = {foreground = tonumber('0xe0e000')},
+ [5] = {reverse = true, foreground = tonumber('0xe0e000'), fg_indexed=true},
+ [6] = {foreground = tonumber('0xe0e000'), fg_indexed=true},
[7] = {reverse = true, foreground = Screen.colors.SeaGreen4},
[8] = {foreground = Screen.colors.SeaGreen4},
[9] = {bold = true, foreground = Screen.colors.Blue1},
@@ -715,6 +775,54 @@ describe('TUI', function()
]])
end)
+ it('forwards :term palette colors with termguicolors', function()
+ screen:set_rgb_cterm(true)
+ screen:set_default_attr_ids({
+ [1] = {{reverse = true}, {reverse = true}},
+ [2] = {{bold = true, reverse = true}, {bold = true, reverse = true}},
+ [3] = {{bold = true}, {bold = true}},
+ [4] = {{fg_indexed = true, foreground = tonumber('0xe0e000')}, {foreground = 3}},
+ [5] = {{foreground = tonumber('0xff8000')}, {}},
+ })
+
+ feed_data(':set statusline=^^^^^^^\n')
+ feed_data(':set termguicolors\n')
+ feed_data(':terminal '..nvim_dir..'/tty-test\n')
+ -- Depending on platform the above might or might not fit in the cmdline
+ -- so clear it for consistent behavior.
+ feed_data(':\027')
+ screen:expect{grid=[[
+ {1:t}ty ready |
+ |
+ |
+ |
+ {2:^^^^^^^ }|
+ |
+ {3:-- TERMINAL --} |
+ ]]}
+ feed_data(':call chansend(&channel, "\\033[38;5;3mtext\\033[38:2:255:128:0mcolor\\033[0;10mtext")\n')
+ screen:expect{grid=[[
+ {1:t}ty ready |
+ {4:text}{5:color}text |
+ |
+ |
+ {2:^^^^^^^ }|
+ |
+ {3:-- TERMINAL --} |
+ ]]}
+
+ feed_data(':set notermguicolors\n')
+ screen:expect{grid=[[
+ {1:t}ty ready |
+ {4:text}colortext |
+ |
+ |
+ {2:^^^^^^^ }|
+ :set notermguicolors |
+ {3:-- TERMINAL --} |
+ ]]}
+ end)
+
it('is included in nvim_list_uis()', function()
feed_data(':echo map(nvim_list_uis(), {k,v -> sort(items(filter(v, {k,v -> k[:3] !=# "ext_" })))})\r')
screen:expect([=[
@@ -911,7 +1019,15 @@ describe('TUI FocusGained/FocusLost', function()
feed_data(':terminal\n')
-- Wait for terminal to be ready.
- screen:expect{any='-- TERMINAL --'}
+ screen:expect{grid=[[
+ {1:r}eady $ |
+ [Process exited 0] |
+ |
+ |
+ |
+ :terminal |
+ {3:-- TERMINAL --} |
+ ]]}
feed_data('\027[I')
screen:expect{grid=[[
@@ -922,7 +1038,7 @@ describe('TUI FocusGained/FocusLost', function()
|
gained |
{3:-- TERMINAL --} |
- ]], timeout=(3 * screen.timeout)}
+ ]], timeout=(4 * screen.timeout)}
feed_data('\027[O')
screen:expect([[
@@ -1266,7 +1382,7 @@ describe("TUI 'term' option", function()
elseif is_macos then
local status, _ = pcall(assert_term, "xterm", "xterm")
if not status then
- pending("macOS: unibilium could not find terminfo", function() end)
+ pending("macOS: unibilium could not find terminfo")
end
else
assert_term("xterm", "xterm")
@@ -1328,125 +1444,26 @@ describe("TUI", function()
end)
-describe('TUI background color', function()
- local screen
-
- before_each(function()
- clear()
- screen = thelpers.screen_setup(0, '["'..nvim_prog
- ..'", "-u", "NONE", "-i", "NONE", "--cmd", "set noswapfile"]')
- end)
-
- it("triggers OptionSet event on terminal-response", function()
- feed_data('\027:autocmd OptionSet background echo "did OptionSet, yay!"\n')
-
- -- Wait for the child Nvim to register the OptionSet handler.
- feed_data('\027:autocmd OptionSet\n')
- screen:expect({any='--- Autocommands ---'})
-
- feed_data('\012') -- CTRL-L: clear the screen
- screen:expect([[
- {1: } |
- {4:~ }|
- {4:~ }|
- {4:~ }|
- {5:[No Name] 0,0-1 All}|
- |
- {3:-- TERMINAL --} |
- ]])
- feed_data('\027]11;rgb:ffff/ffff/ffff\007')
- screen:expect{any='did OptionSet, yay!'}
- end)
-
- it("handles deferred background color", function()
- local last_bg = 'dark'
- local function wait_for_bg(bg)
- -- Retry until the terminal response is handled.
- retry(100, nil, function()
- feed_data(':echo &background\n')
- screen:expect({
- timeout=40,
- grid=string.format([[
- {1: } |
- {4:~ }|
- {4:~ }|
- {4:~ }|
- {5:[No Name] 0,0-1 All}|
- %-5s |
- {3:-- TERMINAL --} |
- ]], bg)
- })
- end)
- last_bg = bg
- end
-
- local function assert_bg(colorspace, color, bg)
- -- Ensure the opposite of the expected bg is active.
- local other_bg = (bg == 'dark' and 'light' or 'dark')
- if last_bg ~= other_bg then
- feed_data(other_bg == 'light' and '\027]11;rgb:f/f/f\007'
- or '\027]11;rgb:0/0/0\007')
- wait_for_bg(other_bg)
- end
+it('TUI bg color triggers OptionSet event on terminal-response', function()
+ -- Only single integration test.
+ -- See test/unit/tui_spec.lua for unit tests.
+ clear()
+ local screen = thelpers.screen_setup(0, '["'..nvim_prog
+ ..'", "-u", "NONE", "-i", "NONE", "--cmd", "set noswapfile", '
+ ..'"-c", "autocmd OptionSet background echo \\"did OptionSet, yay!\\""]')
- feed_data('\027]11;'..colorspace..':'..color..'\007')
- wait_for_bg(bg)
- end
+ screen:expect([[
+ {1: } |
+ {4:~ }|
+ {4:~ }|
+ {4:~ }|
+ {5:[No Name] 0,0-1 All}|
+ |
+ {3:-- TERMINAL --} |
+ ]])
+ feed_data('\027]11;rgb:ffff/ffff/ffff\007')
+ screen:expect{any='did OptionSet, yay!'}
- assert_bg('rgb', '0000/0000/0000', 'dark')
- assert_bg('rgb', 'ffff/ffff/ffff', 'light')
- assert_bg('rgb', '000/000/000', 'dark')
- assert_bg('rgb', 'fff/fff/fff', 'light')
- assert_bg('rgb', '00/00/00', 'dark')
- assert_bg('rgb', 'ff/ff/ff', 'light')
- assert_bg('rgb', '0/0/0', 'dark')
- assert_bg('rgb', 'f/f/f', 'light')
-
- assert_bg('rgb', 'f/0/0', 'dark')
- assert_bg('rgb', '0/f/0', 'light')
- assert_bg('rgb', '0/0/f', 'dark')
-
- assert_bg('rgb', '1/1/1', 'dark')
- assert_bg('rgb', '2/2/2', 'dark')
- assert_bg('rgb', '3/3/3', 'dark')
- assert_bg('rgb', '4/4/4', 'dark')
- assert_bg('rgb', '5/5/5', 'dark')
- assert_bg('rgb', '6/6/6', 'dark')
- assert_bg('rgb', '7/7/7', 'dark')
- assert_bg('rgb', '8/8/8', 'light')
- assert_bg('rgb', '9/9/9', 'light')
- assert_bg('rgb', 'a/a/a', 'light')
- assert_bg('rgb', 'b/b/b', 'light')
- assert_bg('rgb', 'c/c/c', 'light')
- assert_bg('rgb', 'd/d/d', 'light')
- assert_bg('rgb', 'e/e/e', 'light')
-
- assert_bg('rgb', '0/e/0', 'light')
- assert_bg('rgb', '0/d/0', 'light')
- assert_bg('rgb', '0/c/0', 'dark')
- assert_bg('rgb', '0/b/0', 'dark')
-
- assert_bg('rgb', 'f/0/f', 'dark')
- assert_bg('rgb', 'f/1/f', 'dark')
- assert_bg('rgb', 'f/2/f', 'dark')
- assert_bg('rgb', 'f/3/f', 'light')
- assert_bg('rgb', 'f/4/f', 'light')
-
- assert_bg('rgba', '0000/0000/0000/0000', 'dark')
- assert_bg('rgba', '0000/0000/0000/ffff', 'dark')
- assert_bg('rgba', 'ffff/ffff/ffff/0000', 'light')
- assert_bg('rgba', 'ffff/ffff/ffff/ffff', 'light')
- assert_bg('rgba', '000/000/000/000', 'dark')
- assert_bg('rgba', '000/000/000/fff', 'dark')
- assert_bg('rgba', 'fff/fff/fff/000', 'light')
- assert_bg('rgba', 'fff/fff/fff/fff', 'light')
- assert_bg('rgba', '00/00/00/00', 'dark')
- assert_bg('rgba', '00/00/00/ff', 'dark')
- assert_bg('rgba', 'ff/ff/ff/00', 'light')
- assert_bg('rgba', 'ff/ff/ff/ff', 'light')
- assert_bg('rgba', '0/0/0/0', 'dark')
- assert_bg('rgba', '0/0/0/f', 'dark')
- assert_bg('rgba', 'f/f/f/0', 'light')
- assert_bg('rgba', 'f/f/f/f', 'light')
- end)
+ feed_data(':echo "new_bg=".&background\n')
+ screen:expect{any='new_bg=light'}
end)
diff --git a/test/functional/terminal/window_split_tab_spec.lua b/test/functional/terminal/window_split_tab_spec.lua
index 7b49a38e77..03bd336aec 100644
--- a/test/functional/terminal/window_split_tab_spec.lua
+++ b/test/functional/terminal/window_split_tab_spec.lua
@@ -103,4 +103,14 @@ describe(':terminal', function()
|
]])
end)
+
+ it('stays in terminal mode with <Cmd>wincmd', function()
+ command('terminal')
+ command('split')
+ command('terminal')
+ feed('a<Cmd>wincmd j<CR>')
+ eq(2, eval("winnr()"))
+ eq('t', eval('mode()'))
+ end)
+
end)
diff --git a/test/functional/ui/bufhl_spec.lua b/test/functional/ui/bufhl_spec.lua
index bcccef84b6..3cb592c714 100644
--- a/test/functional/ui/bufhl_spec.lua
+++ b/test/functional/ui/bufhl_spec.lua
@@ -5,6 +5,7 @@ local clear, feed, insert = helpers.clear, helpers.feed, helpers.insert
local command, neq = helpers.command, helpers.neq
local meths = helpers.meths
local curbufmeths, eq = helpers.curbufmeths, helpers.eq
+local pcall_err = helpers.pcall_err
describe('Buffer highlighting', function()
local screen
@@ -31,13 +32,13 @@ describe('Buffer highlighting', function()
[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},
+ [17] = {foreground = Screen.colors.Magenta, background = Screen.colors.LightRed},
+ [18] = {background = Screen.colors.LightRed},
+ [19] = {foreground = Screen.colors.Blue1, background = Screen.colors.LightRed},
+ [20] = {underline = true, bold = true, foreground = Screen.colors.Cyan4},
})
end)
- after_each(function()
- screen:detach()
- end)
-
local add_highlight = curbufmeths.add_highlight
local clear_namespace = curbufmeths.clear_namespace
@@ -206,21 +207,272 @@ describe('Buffer highlighting', function()
|
]])
- command(':3move 4')
- screen:expect([[
+ -- TODO(bfedl): this behaves a bit weirdly due to the highlight on
+ -- the deleted line wrapping around. we should invalidate
+ -- highlights when they are completely inside deleted text
+ command('3move 4')
+ screen:expect{grid=[[
a {5:longer} example |
|
+ {8:from different sources} |
+ {8:^in }{20:order}{8: to demonstrate} |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ |
+ ]]}
+ --screen:expect([[
+ -- a {5:longer} example |
+ -- |
+ -- {9:from }{8:diff}{7:erent} sources |
+ -- ^in {6:order} to {7:de}{5:monstr}{7:ate} |
+ -- {1:~ }|
+ -- {1:~ }|
+ -- {1:~ }|
+ -- |
+ --]])
+
+ command('undo')
+ screen:expect{grid=[[
+ a {5:longer} example |
+ ^ |
+ in {6:order} to {7:de}{5:monstr}{7:ate} |
+ {9:from }{8:diff}{7:erent} sources |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ 1 change; before #4 {MATCH:.*}|
+ ]]}
+
+ command('undo')
+ screen:expect{grid=[[
+ ^a {5:longer} example |
+ in {6:order} to {7:de}{5:monstr}{7:ate} |
+ {9:from }{8:diff}{7:erent} sources |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ 1 line less; before #3 {MATCH:.*}|
+ ]]}
+
+ command('undo')
+ screen:expect{grid=[[
+ a {5:longer} example |
+ in {6:order} to {7:de}{5:monstr}{7:ate} |
+ {7:^combin}{8:ing}{9: hi}ghlights |
{9:from }{8:diff}{7:erent} sources |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ 1 more line; before #2 {MATCH:.*}|
+ ]]}
+ end)
+
+ it('and moving lines around', function()
+ command('2move 3')
+ screen:expect{grid=[[
+ a {5:longer} example |
+ {7:combin}{8:ing}{9: hi}ghlights |
^in {6:order} to {7:de}{5:monstr}{7:ate} |
+ {9:from }{8:diff}{7:erent} sources |
{1:~ }|
{1:~ }|
{1:~ }|
|
- ]])
+ ]]}
+
+ command('1,2move 4')
+ screen:expect{grid=[[
+ in {6:order} to {7:de}{5:monstr}{7:ate} |
+ {9:from }{8:diff}{7:erent} sources |
+ a {5:longer} example |
+ {7:^combin}{8:ing}{9: hi}ghlights |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ |
+ ]]}
+
+ command('undo')
+ screen:expect{grid=[[
+ a {5:longer} example |
+ {7:combin}{8:ing}{9: hi}ghlights |
+ ^in {6:order} to {7:de}{5:monstr}{7:ate} |
+ {9:from }{8:diff}{7:erent} sources |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ 2 change3; before #3 {MATCH:.*}|
+ ]]}
+
+ command('undo')
+ screen:expect{grid=[[
+ a {5:longer} example |
+ ^in {6:order} to {7:de}{5:monstr}{7:ate} |
+ {7:combin}{8:ing}{9: hi}ghlights |
+ {9:from }{8:diff}{7:erent} sources |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ 1 change; before #2 {MATCH:.*}|
+ ]]}
+ end)
+
+ it('and adjusting columns', function()
+ -- insert before
+ feed('ggiquite <esc>')
+ screen:expect{grid=[[
+ quite^ a {5:longer} example |
+ in {6:order} to {7:de}{5:monstr}{7:ate} |
+ {7:combin}{8:ing}{9: hi}ghlights |
+ {9:from }{8:diff}{7:erent} sources |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ |
+ ]]}
+
+ feed('u')
+ screen:expect{grid=[[
+ ^a {5:longer} example |
+ in {6:order} to {7:de}{5:monstr}{7:ate} |
+ {7:combin}{8:ing}{9: hi}ghlights |
+ {9:from }{8:diff}{7:erent} sources |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ 1 change; before #2 {MATCH:.*}|
+ ]]}
+
+ -- change/insert in the middle
+ feed('+fesAAAA')
+ screen:expect{grid=[[
+ a {5:longer} example |
+ in {6:ordAAAA^r} to {7:de}{5:monstr}{7:ate} |
+ {7:combin}{8:ing}{9: hi}ghlights |
+ {9:from }{8:diff}{7:erent} sources |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {7:-- INSERT --} |
+ ]]}
+
+ feed('<esc>tdD')
+ screen:expect{grid=[[
+ a {5:longer} example |
+ in {6:ordAAAAr} t^o |
+ {7:combin}{8:ing}{9: hi}ghlights |
+ {9:from }{8:diff}{7:erent} sources |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ |
+ ]]}
+
+ feed('u')
+ screen:expect{grid=[[
+ a {5:longer} example |
+ in {6:ordAAAAr} to^ {7:de}{5:monstr}{7:ate} |
+ {7:combin}{8:ing}{9: hi}ghlights |
+ {9:from }{8:diff}{7:erent} sources |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ 1 change; before #4 {MATCH:.*}|
+ ]]}
+
+ feed('u')
+ screen:expect{grid=[[
+ a {5:longer} example |
+ in {6:ord^er} to {7:de}{5:monstr}{7:ate} |
+ {7:combin}{8:ing}{9: hi}ghlights |
+ {9:from }{8:diff}{7:erent} sources |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ 1 change; before #3 {MATCH:.*}|
+ ]]}
+ end)
+
+ it('and joining lines', function()
+ feed('ggJJJ')
+ screen:expect{grid=[[
+ a {5:longer} example in {6:order} to {7:de}{5:monstr}{7:ate}|
+ {7:combin}{8:ing}{9: hi}ghlights^ {9:from }{8:diff}{7:erent} sou|
+ rces |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ |
+ ]]}
+
+ feed('uuu')
+ screen:expect{grid=[[
+ ^a {5:longer} example |
+ in {6:order} to {7:de}{5:monstr}{7:ate} |
+ {7:combin}{8:ing}{9: hi}ghlights |
+ {9:from }{8:diff}{7:erent} sources |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ 1 more line; before #2 {MATCH:.*}|
+ ]]}
+ end)
+
+ it('and splitting lines', function()
+ feed('2Gtti<cr>')
+ screen:expect{grid=[[
+ a {5:longer} example |
+ in {6:order} |
+ ^ to {7:de}{5:monstr}{7:ate} |
+ {7:combin}{8:ing}{9: hi}ghlights |
+ {9:from }{8:diff}{7:erent} sources |
+ {1:~ }|
+ {1:~ }|
+ {7:-- INSERT --} |
+ ]]}
+
+ feed('<esc>tsi<cr>')
+ screen:expect{grid=[[
+ a {5:longer} example |
+ in {6:order} |
+ to {7:de}{5:mo} |
+ {5:^nstr}{7:ate} |
+ {7:combin}{8:ing}{9: hi}ghlights |
+ {9:from }{8:diff}{7:erent} sources |
+ {1:~ }|
+ {7:-- INSERT --} |
+ ]]}
+
+ feed('<esc>u')
+ screen:expect{grid=[[
+ a {5:longer} example |
+ in {6:order} |
+ to {7:de}{5:mo^nstr}{7:ate} |
+ {7:combin}{8:ing}{9: hi}ghlights |
+ {9:from }{8:diff}{7:erent} sources |
+ {1:~ }|
+ {1:~ }|
+ 1 line less; before #3 {MATCH:.*}|
+ ]]}
+
+ feed('<esc>u')
+ screen:expect{grid=[[
+ a {5:longer} example |
+ in {6:order}^ to {7:de}{5:monstr}{7:ate} |
+ {7:combin}{8:ing}{9: hi}ghlights |
+ {9:from }{8:diff}{7:erent} sources |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ 1 line less; before #2 {MATCH:.*}|
+ ]]}
end)
end)
- it('prioritizes latest added highlight', function()
+ pending('prioritizes latest added highlight', function()
insert([[
three overlapping colors]])
add_highlight(0, "Identifier", 0, 6, 17)
@@ -251,6 +503,37 @@ describe('Buffer highlighting', function()
]])
end)
+ it('prioritizes earlier highlight groups (TEMP)', function()
+ insert([[
+ three overlapping colors]])
+ add_highlight(0, "Identifier", 0, 6, 17)
+ add_highlight(0, "String", 0, 14, 23)
+ local id = add_highlight(0, "Special", 0, 0, 9)
+
+ screen:expect{grid=[[
+ {4:three }{6:overlapp}{2:ing color}^s |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ |
+ ]]}
+
+ clear_namespace(id, 0, 1)
+ screen:expect{grid=[[
+ three {6:overlapp}{2:ing color}^s |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ |
+ ]]}
+ end)
+
it('works with multibyte text', function()
insert([[
Ta båten över sjön!]])
@@ -297,7 +580,7 @@ describe('Buffer highlighting', function()
]])
end)
- describe('virtual text annotations', function()
+ describe('virtual text decorations', function()
local set_virtual_text = curbufmeths.set_virtual_text
local id1, id2
before_each(function()
@@ -375,16 +658,53 @@ describe('Buffer highlighting', function()
]])
feed("2Gdd")
- screen:expect([[
+ -- TODO(bfredl): currently decorations get moved from a deleted line
+ -- to the next one. We might want to add "invalidation" when deleting
+ -- over a decoration.
+ screen:expect{grid=[[
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|
+ , 5, 5, 5, 5, 5, 5, {12:暗x事zz速野谷質結育}|
x = 4 |
{1:~ }|
{1:~ }|
{1:~ }|
|
- ]])
+ ]]}
+ --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('validates contents', function()
+ -- this used to leak memory
+ eq('Chunk is not an array', pcall_err(set_virtual_text, id1, 0, {"texty"}, {}))
+ eq('Chunk is not an array', pcall_err(set_virtual_text, id1, 0, {{"very"}, "texty"}, {}))
+ end)
+
+ it('can be retrieved', function()
+ local get_virtual_text = curbufmeths.get_virtual_text
+ local line_count = curbufmeths.line_count
+
+ local s1 = {{'Köttbullar', 'Comment'}, {'Kräuterbutter'}}
+ local s2 = {{'こんにちは', 'Comment'}}
+
+ -- TODO: only a virtual text from the same ns curretly overrides
+ -- an existing virtual text. We might add a prioritation system.
+ set_virtual_text(id1, 0, s1, {})
+ eq(s1, get_virtual_text(0))
+
+ set_virtual_text(-1, line_count(), s2, {})
+ eq(s2, get_virtual_text(line_count()))
+
+ eq({}, get_virtual_text(line_count() + 9000))
end)
it('is not highlighted by visual selection', function()
@@ -516,6 +836,32 @@ describe('Buffer highlighting', function()
|
]])
end)
+
+ it('works with color column', function()
+ eq(-1, set_virtual_text(-1, 3, {{"暗x事", "Comment"}}, {}))
+ screen:expect{grid=[[
+ ^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 {12:暗x事} |
+ {1:~ }|
+ {1:~ }|
+ |
+ ]]}
+
+ command("set colorcolumn=9")
+ screen:expect{grid=[[
+ ^1 + 2 {3:=}{2: }{17:3} |
+ 3 + {11:ERROR:} invalid syntax |
+ 5, 5, 5,{18: }5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5|
+ , 5, 5, 5, 5, 5, 5, Lorem ipsum dolor s|
+ x = 4 {12:暗}{19:x}{12:事} |
+ {1:~ }|
+ {1:~ }|
+ |
+ ]]}
+ end)
end)
it('and virtual text use the same namespace counter', function()
diff --git a/test/functional/ui/cmdline_highlight_spec.lua b/test/functional/ui/cmdline_highlight_spec.lua
index 052414a43d..9c746b99bd 100644
--- a/test/functional/ui/cmdline_highlight_spec.lua
+++ b/test/functional/ui/cmdline_highlight_spec.lua
@@ -362,7 +362,7 @@ describe('Command-line coloring', function()
{EOB:~ }|
:e^ |
]])
- eq('', meths.command_output('messages'))
+ eq('', meths.exec('messages', true))
end)
it('silences :echon', function()
set_color_cb('Echoning')
@@ -377,7 +377,7 @@ describe('Command-line coloring', function()
{EOB:~ }|
:e^ |
]])
- eq('', meths.command_output('messages'))
+ eq('', meths.exec('messages', true))
end)
it('silences :echomsg', function()
set_color_cb('Echomsging')
@@ -392,7 +392,7 @@ describe('Command-line coloring', function()
{EOB:~ }|
:e^ |
]])
- eq('', meths.command_output('messages'))
+ eq('', meths.exec('messages', true))
end)
it('does the right thing when throwing', function()
set_color_cb('Throwing')
@@ -857,7 +857,7 @@ describe('Ex commands coloring', function()
]])
feed('<CR>')
eq('Error detected while processing :\nE605: Exception not caught: 42\nE749: empty buffer',
- meths.command_output('messages'))
+ meths.exec('messages', true))
end)
it('errors out when failing to get callback', function()
meths.set_var('Nvim_color_cmdline', 42)
diff --git a/test/functional/ui/cmdline_spec.lua b/test/functional/ui/cmdline_spec.lua
index f9769c706f..21c01b3458 100644
--- a/test/functional/ui/cmdline_spec.lua
+++ b/test/functional/ui/cmdline_spec.lua
@@ -25,10 +25,6 @@ local function test_cmdline(linegrid)
screen = new_screen({rgb=true, ext_cmdline=true, ext_linegrid=linegrid})
end)
- after_each(function()
- screen:detach()
- end)
-
it('works', function()
feed(':')
screen:expect{grid=[[
@@ -779,7 +775,7 @@ local function test_cmdline(linegrid)
}}}
-- This used to send an invalid event where pos where larger than the total
- -- lenght of content. Checked in _handle_cmdline_show.
+ -- length of content. Checked in _handle_cmdline_show.
feed('<esc>')
screen:expect([[
^ |
@@ -804,10 +800,6 @@ describe('cmdline redraw', function()
screen = new_screen({rgb=true})
end)
- after_each(function()
- screen:detach()
- end)
-
it('with timer', function()
feed(':012345678901234567890123456789')
screen:expect{grid=[[
@@ -829,8 +821,7 @@ describe('cmdline redraw', function()
it('with <Cmd>', function()
if 'openbsd' == helpers.uname() then
- pending('FIXME #10804', function() end)
- return
+ pending('FIXME #10804')
end
command('cmap a <Cmd>call sin(0)<CR>') -- no-op
feed(':012345678901234567890123456789')
diff --git a/test/functional/ui/cursor_spec.lua b/test/functional/ui/cursor_spec.lua
index 67aba919b0..6c913124ac 100644
--- a/test/functional/ui/cursor_spec.lua
+++ b/test/functional/ui/cursor_spec.lua
@@ -13,10 +13,6 @@ describe('ui/cursor', function()
screen:attach()
end)
- after_each(function()
- screen:detach()
- end)
-
it("'guicursor' is published as a UI event", function()
local expected_mode_info = {
[1] = {
@@ -249,6 +245,25 @@ describe('ui/cursor', function()
eq('normal', screen.mode)
end)
+ -- update the highlight again to hide cursor
+ helpers.command('hi Cursor blend=100')
+
+ for _, m in ipairs(expected_mode_info) do
+ if m.hl_id then
+ m.attr = {background = Screen.colors.Red, blend = 100}
+ end
+ end
+ screen:expect{grid=[[
+ ^ |
+ ~ |
+ ~ |
+ ~ |
+ test |
+ ]], condition=function()
+ eq(expected_mode_info, screen._mode_info)
+ 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/diff_spec.lua b/test/functional/ui/diff_spec.lua
index 8eb2bbf779..69b6ab8cf0 100644
--- a/test/functional/ui/diff_spec.lua
+++ b/test/functional/ui/diff_spec.lua
@@ -3,7 +3,10 @@ local Screen = require('test.functional.ui.screen')
local feed = helpers.feed
local clear = helpers.clear
+local command = helpers.command
+local insert = helpers.insert
local write_file = helpers.write_file
+local source = helpers.source
describe('Diff mode screen', function()
local fname = 'Xtest-functional-diff-screen-1'
@@ -957,3 +960,151 @@ int main(int argc, char **argv)
end)
end)
end)
+
+it('win_update redraws lines properly', function()
+ local screen
+ clear()
+ screen = Screen.new(50, 10)
+ screen:attach()
+ screen:set_default_attr_ids({
+ [1] = {bold = true, foreground = Screen.colors.Blue1},
+ [2] = {foreground = Screen.colors.Grey100, background = Screen.colors.Red},
+ [3] = {background = Screen.colors.Red, foreground = Screen.colors.Grey100, special = Screen.colors.Yellow},
+ [4] = {bold = true, foreground = Screen.colors.SeaGreen4},
+ [5] = {special = Screen.colors.Yellow},
+ [6] = {special = Screen.colors.Yellow, bold = true, foreground = Screen.colors.SeaGreen4},
+ [7] = {foreground = Screen.colors.Grey0, background = Screen.colors.Grey100},
+ [8] = {foreground = Screen.colors.Gray90, background = Screen.colors.Grey100},
+ [9] = {foreground = tonumber('0x00000c'), background = Screen.colors.Grey100},
+ [10] = {background = Screen.colors.Grey100, bold = true, foreground = tonumber('0xe5e5ff')},
+ [11] = {background = Screen.colors.Grey100, bold = true, foreground = tonumber('0x2b8452')},
+ [12] = {bold = true, reverse = true},
+ [13] = {foreground = Screen.colors.DarkBlue, background = Screen.colors.WebGray},
+ [14] = {reverse = true},
+ [15] = {background = Screen.colors.LightBlue},
+ [16] = {background = Screen.colors.LightCyan1, bold = true, foreground = Screen.colors.Blue1},
+ [17] = {bold = true, background = Screen.colors.Red},
+ [18] = {background = Screen.colors.LightMagenta},
+ })
+
+ insert([[
+ 1
+
+
+ 2
+ 1a
+ ]])
+ command("vnew left")
+ insert([[
+ 2
+ 2a
+ 2b
+ ]])
+ command("windo diffthis")
+ command("windo 1")
+ screen:expect{grid=[[
+ {13: }{16:-----------------------}{14:│}{13: }{15:^1 }|
+ {13: }{16:-----------------------}{14:│}{13: }{15: }|
+ {13: }{16:-----------------------}{14:│}{13: }{15: }|
+ {13: }2 {14:│}{13: }2 |
+ {13: }{17:2}{18:a }{14:│}{13: }{17:1}{18:a }|
+ {13: }{15:2b }{14:│}{13: }{16:----------------------}|
+ {13: } {14:│}{13: } |
+ {1:~ }{14:│}{1:~ }|
+ {14:left [+] }{12:[No Name] [+] }|
+ |
+ ]]}
+ feed('<C-e>')
+ feed('<C-e>')
+ feed('<C-y>')
+ feed('<C-y>')
+ feed('<C-y>')
+ screen:expect{grid=[[
+ {13: }{16:-----------------------}{14:│}{13: }{15:1 }|
+ {13: }{16:-----------------------}{14:│}{13: }{15: }|
+ {13: }{16:-----------------------}{14:│}{13: }{15:^ }|
+ {13: }2 {14:│}{13: }2 |
+ {13: }{17:2}{18:a }{14:│}{13: }{17:1}{18:a }|
+ {13: }{15:2b }{14:│}{13: }{16:----------------------}|
+ {13: } {14:│}{13: } |
+ {1:~ }{14:│}{1:~ }|
+ {14:left [+] }{12:[No Name] [+] }|
+ |
+ ]]}
+end)
+
+it('diff updates line numbers below filler lines', function()
+ clear()
+ local screen = Screen.new(40, 14)
+ screen:attach()
+ screen:set_default_attr_ids({
+ [1] = {foreground = Screen.colors.DarkBlue, background = Screen.colors.WebGray},
+ [2] = {background = Screen.colors.LightCyan1, bold = true, foreground = Screen.colors.Blue1},
+ [3] = {reverse = true},
+ [4] = {background = Screen.colors.LightBlue},
+ [5] = {foreground = Screen.colors.DarkBlue, background = Screen.colors.LightGrey},
+ [6] = {bold = true, foreground = Screen.colors.Blue1},
+ [7] = {bold = true, reverse = true},
+ [8] = {bold = true, background = Screen.colors.Red},
+ [9] = {background = Screen.colors.LightMagenta},
+ [10] = {bold = true, foreground = Screen.colors.Brown},
+ [11] = {foreground = Screen.colors.Brown},
+ })
+ source([[
+ call setline(1, ['a', 'a', 'a', 'y', 'b', 'b', 'b', 'b', 'b'])
+ vnew
+ call setline(1, ['a', 'a', 'a', 'x', 'x', 'x', 'b', 'b', 'b', 'b', 'b'])
+ windo diffthis
+ setlocal number rnu foldcolumn=0
+ ]])
+ screen:expect([[
+ {1: }a {3:│}{10:1 }^a |
+ {1: }a {3:│}{11: 1 }a |
+ {1: }a {3:│}{11: 2 }a |
+ {1: }{8:x}{9: }{3:│}{11: 3 }{8:y}{9: }|
+ {1: }{4:x }{3:│}{11: }{2:----------------}|
+ {1: }{4:x }{3:│}{11: }{2:----------------}|
+ {1: }b {3:│}{11: 4 }b |
+ {1: }b {3:│}{11: 5 }b |
+ {1: }b {3:│}{11: 6 }b |
+ {1: }b {3:│}{11: 7 }b |
+ {1: }b {3:│}{11: 8 }b |
+ {6:~ }{3:│}{6:~ }|
+ {3:[No Name] [+] }{7:[No Name] [+] }|
+ |
+ ]])
+ feed('j')
+ screen:expect([[
+ {1: }a {3:│}{11: 1 }a |
+ {1: }a {3:│}{10:2 }^a |
+ {1: }a {3:│}{11: 1 }a |
+ {1: }{8:x}{9: }{3:│}{11: 2 }{8:y}{9: }|
+ {1: }{4:x }{3:│}{11: }{2:----------------}|
+ {1: }{4:x }{3:│}{11: }{2:----------------}|
+ {1: }b {3:│}{11: 3 }b |
+ {1: }b {3:│}{11: 4 }b |
+ {1: }b {3:│}{11: 5 }b |
+ {1: }b {3:│}{11: 6 }b |
+ {1: }b {3:│}{11: 7 }b |
+ {6:~ }{3:│}{6:~ }|
+ {3:[No Name] [+] }{7:[No Name] [+] }|
+ |
+ ]])
+ feed('j')
+ screen:expect([[
+ {1: }a {3:│}{11: 2 }a |
+ {1: }a {3:│}{11: 1 }a |
+ {1: }a {3:│}{10:3 }^a |
+ {1: }{8:x}{9: }{3:│}{11: 1 }{8:y}{9: }|
+ {1: }{4:x }{3:│}{11: }{2:----------------}|
+ {1: }{4:x }{3:│}{11: }{2:----------------}|
+ {1: }b {3:│}{11: 2 }b |
+ {1: }b {3:│}{11: 3 }b |
+ {1: }b {3:│}{11: 4 }b |
+ {1: }b {3:│}{11: 5 }b |
+ {1: }b {3:│}{11: 6 }b |
+ {6:~ }{3:│}{6:~ }|
+ {3:[No Name] [+] }{7:[No Name] [+] }|
+ |
+ ]])
+end)
diff --git a/test/functional/ui/embed_spec.lua b/test/functional/ui/embed_spec.lua
index f3cd223f53..8218c8e12d 100644
--- a/test/functional/ui/embed_spec.lua
+++ b/test/functional/ui/embed_spec.lua
@@ -50,8 +50,7 @@ local function test_embed(ext_linegrid)
it("doesn't erase output when setting color scheme", function()
if 'openbsd' == helpers.uname() then
- pending('FIXME #10804', function() end)
- return
+ pending('FIXME #10804')
end
startup('--cmd', 'echoerr "foo"', '--cmd', 'color default', '--cmd', 'echoerr "bar"')
screen:expect([[
diff --git a/test/functional/ui/float_spec.lua b/test/functional/ui/float_spec.lua
index dbaf6f802b..11fe861de8 100644
--- a/test/functional/ui/float_spec.lua
+++ b/test/functional/ui/float_spec.lua
@@ -2,9 +2,11 @@ local helpers = require('test.functional.helpers')(after_each)
local Screen = require('test.functional.ui.screen')
local os = require('os')
local clear, feed = helpers.clear, helpers.feed
+local assert_alive = helpers.assert_alive
local command, feed_command = helpers.command, helpers.feed_command
local eval = helpers.eval
local eq = helpers.eq
+local exec_lua = helpers.exec_lua
local insert = helpers.insert
local meths = helpers.meths
local curbufmeths = helpers.curbufmeths
@@ -12,7 +14,7 @@ local funcs = helpers.funcs
local run = helpers.run
local pcall_err = helpers.pcall_err
-describe('floating windows', function()
+describe('floatwin', function()
before_each(function()
clear()
end)
@@ -39,6 +41,7 @@ describe('floating windows', function()
[19] = {foreground = Screen.colors.DarkBlue, background = Screen.colors.WebGray},
[20] = {bold = true, foreground = Screen.colors.Brown},
[21] = {background = Screen.colors.Gray90},
+ [22] = {background = Screen.colors.LightRed},
}
it('behavior', function()
@@ -55,6 +58,31 @@ describe('floating windows', function()
eq(1000, funcs.win_getid())
end)
+ it('closed immediately by autocmd #11383', function()
+ eq('Error executing lua: [string "<nvim>"]:4: Window was closed immediately',
+ pcall_err(exec_lua, [[
+ local a = vim.api
+ local function crashes(contents)
+ local buf = a.nvim_create_buf(false, true)
+ local floatwin = a.nvim_open_win(buf, true, {
+ relative = 'cursor';
+ style = 'minimal';
+ row = 0; col = 0;
+ height = #contents;
+ width = 10;
+ })
+ a.nvim_buf_set_lines(buf, 0, -1, true, contents)
+ local winnr = vim.fn.win_id2win(floatwin)
+ a.nvim_command('wincmd p')
+ a.nvim_command('autocmd CursorMoved * ++once '..winnr..'wincmd c')
+ return buf, floatwin
+ end
+ crashes{'foo'}
+ crashes{'bar'}
+ ]]))
+ assert_alive()
+ end)
+
local function with_ext_multigrid(multigrid)
local screen
before_each(function()
@@ -398,6 +426,7 @@ describe('floating windows', function()
it("can use 'minimal' style", function()
command('set number')
command('set signcolumn=yes')
+ command('set colorcolumn=1')
command('set cursorline')
command('set foldcolumn=1')
command('hi NormalFloat guibg=#333333')
@@ -414,9 +443,9 @@ describe('floating windows', function()
[2:----------------------------------------]|
[3:----------------------------------------]|
## grid 2
- {19: }{20: 1 }{21:^x }|
- {19: }{14: 2 }y |
- {19: }{14: 3 } |
+ {19: }{20: 1 }{22:^x}{21: }|
+ {19: }{14: 2 }{22:y} |
+ {19: }{14: 3 }{22: } |
{0:~ }|
{0:~ }|
{0:~ }|
@@ -430,9 +459,9 @@ describe('floating windows', function()
]], float_pos={[4] = {{id = 1001}, "NW", 1, 4, 10, true}}}
else
screen:expect{grid=[[
- {19: }{20: 1 }{21:^x }|
- {19: }{14: 2 }y |
- {19: }{14: 3 } {15:x } |
+ {19: }{20: 1 }{22:^x}{21: }|
+ {19: }{14: 2 }{22:y} |
+ {19: }{14: 3 }{22: } {15:x } |
{0:~ }{15:y }{0: }|
{0:~ }{15: }{0: }|
{0:~ }{15: }{0: }|
@@ -454,9 +483,9 @@ describe('floating windows', function()
[2:----------------------------------------]|
[3:----------------------------------------]|
## grid 2
- {19: }{17:𐌢̀́̂̃̅̄𐌢̀́̂̃̅̄}{20: 1 }{21:^x }|
- {19: }{14: 2 }y |
- {19: }{14: 3 } |
+ {19: }{17:𐌢̀́̂̃̅̄𐌢̀́̂̃̅̄}{20: 1 }{22:^x}{21: }|
+ {19: }{14: 2 }{22:y} |
+ {19: }{14: 3 }{22: } |
{0:~ }|
{0:~ }|
{0:~ }|
@@ -471,9 +500,9 @@ describe('floating windows', function()
else
screen:expect([[
- {19: }{17:𐌢̀́̂̃̅̄𐌢̀́̂̃̅̄}{20: 1 }{21:^x }|
- {19: }{14: 2 }y |
- {19: }{14: 3 } {17:𐌢̀́̂̃̅̄𐌢̀́̂̃̅̄}{15:x } |
+ {19: }{17:𐌢̀́̂̃̅̄𐌢̀́̂̃̅̄}{20: 1 }{22:^x}{21: }|
+ {19: }{14: 2 }{22:y} |
+ {19: }{14: 3 }{22: } {17:𐌢̀́̂̃̅̄𐌢̀́̂̃̅̄}{15:x } |
{0:~ }{19: }{15:y }{0: }|
{0:~ }{19: }{15: }{0: }|
{0:~ }{15: }{0: }|
@@ -495,9 +524,9 @@ describe('floating windows', function()
[2:----------------------------------------]|
[3:----------------------------------------]|
## grid 2
- {19: }{20: 1 }{21:^x }|
- {19: }{14: 2 }y |
- {19: }{14: 3 } |
+ {19: }{20: 1 }{22:^x}{21: }|
+ {19: }{14: 2 }{22:y} |
+ {19: }{14: 3 }{22: } |
{0:~ }|
{0:~ }|
{0:~ }|
@@ -511,9 +540,9 @@ describe('floating windows', function()
]], float_pos={[4] = {{id = 1001}, "NW", 1, 4, 10, true}}}
else
screen:expect([[
- {19: }{20: 1 }{21:^x }|
- {19: }{14: 2 }y |
- {19: }{14: 3 } {15: } |
+ {19: }{20: 1 }{22:^x}{21: }|
+ {19: }{14: 2 }{22:y} |
+ {19: }{14: 3 }{22: } {15: } |
{0:~ }{15: }{0: }|
{0:~ }{15: }{0: }|
{0:~ }{15: }{0: }|
@@ -947,6 +976,28 @@ describe('floating windows', function()
{2:~ }|
]], float_pos={
[5] = {{id = 1002}, "NE", 4, 0, 50, true}
+ }, win_viewport = {
+ [2] = {
+ topline = 0,
+ botline = 3,
+ curline = 0,
+ curcol = 3,
+ win = { id = 1000 }
+ },
+ [4] = {
+ topline = 0,
+ botline = 3,
+ curline = 0,
+ curcol = 3,
+ win = { id = 1001 }
+ },
+ [5] = {
+ topline = 0,
+ botline = 2,
+ curline = 0,
+ curcol = 0,
+ win = { id = 1002 }
+ }
}}
else
screen:expect([[
@@ -2003,10 +2054,10 @@ describe('floating windows', function()
screen:expect{grid=[[
## grid 1
[2:----------------------------------------]|
- [2:----------------------------------------]|
- [2:----------------------------------------]|
- [2:----------------------------------------]|
- [2:----------------------------------------]|
+ {5:[No Name] }|
+ [5:----------------------------------------]|
+ [5:----------------------------------------]|
+ [5:----------------------------------------]|
{5:[Preview] }|
[3:----------------------------------------]|
## grid 2
@@ -2017,6 +2068,10 @@ describe('floating windows', function()
{17:f}{1:oo }|
{17:b}{1:ar }|
{1: }|
+ ## grid 5
+ |1| {17:f}oo |
+ |2| {17:b}ar |
+ {0:~ }|
]], float_pos=expected_pos}
else
screen:expect([[
diff --git a/test/functional/ui/fold_spec.lua b/test/functional/ui/fold_spec.lua
index c5ef718883..6ec45064da 100644
--- a/test/functional/ui/fold_spec.lua
+++ b/test/functional/ui/fold_spec.lua
@@ -24,10 +24,6 @@ describe("folded lines", function()
})
end)
- after_each(function()
- screen:detach()
- end)
-
it("work with more than one signcolumn", function()
command("set signcolumn=yes:9")
feed("i<cr><esc>")
@@ -64,6 +60,57 @@ describe("folded lines", function()
]])
end)
+ it("works with multibyte fillchars", function()
+ insert([[
+ aa
+ bb
+ cc
+ dd
+ ee
+ ff]])
+ command("set fillchars+=foldopen:▾,foldsep:│,foldclose:▸")
+ feed_command('1')
+ command("set foldcolumn=2")
+ feed('zf4j')
+ feed('zf2j')
+ feed('zO')
+ screen:expect{grid=[[
+ {7:▾▾}^aa |
+ {7:││}bb |
+ {7:││}cc |
+ {7:││}dd |
+ {7:││}ee |
+ {7:│ }ff |
+ {1:~ }|
+ :1 |
+ ]]}
+
+ feed_command("set rightleft")
+ screen:expect{grid=[[
+ a^a{7:▾▾}|
+ bb{7:││}|
+ cc{7:││}|
+ dd{7:││}|
+ ee{7:││}|
+ ff{7: │}|
+ {1: ~}|
+ :set rightleft |
+ ]]}
+
+ feed_command("set norightleft")
+ meths.input_mouse('left', 'press', '', 0, 0, 1)
+ screen:expect{grid=[[
+ {7:▾▸}{5:^+--- 5 lines: aa··························}|
+ {7:│ }ff |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ :set norightleft |
+ ]]}
+ end)
+
it("works with multibyte text", function()
-- Currently the only allowed value of 'maxcombine'
eq(6, meths.get_option('maxcombine'))
@@ -248,4 +295,64 @@ describe("folded lines", function()
]])
end)
+
+ it("work with autoresize", function()
+
+ funcs.setline(1, 'line 1')
+ funcs.setline(2, 'line 2')
+ funcs.setline(3, 'line 3')
+ funcs.setline(4, 'line 4')
+
+ feed("zfj")
+ command("set foldcolumn=0")
+ screen:expect{grid=[[
+ {5:^+-- 2 lines: line 1·························}|
+ line 3 |
+ line 4 |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ |
+ ]]}
+ -- should adapt to the current nesting of folds (e.g., 1)
+ command("set foldcolumn=auto:1")
+ screen:expect{grid=[[
+ {7:+}{5:^+-- 2 lines: line 1························}|
+ {7: }line 3 |
+ {7: }line 4 |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ |
+ ]]}
+ -- fdc should not change with a new fold as the maximum is 1
+ feed("zf3j")
+
+ screen:expect{grid=[[
+ {7:+}{5:^+-- 4 lines: line 1························}|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ |
+ ]]}
+
+ -- relax the maximum fdc thus fdc should expand to
+ -- accomodate the current number of folds
+ command("set foldcolumn=auto:4")
+ screen:expect{grid=[[
+ {7:+ }{5:^+-- 4 lines: line 1·······················}|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ |
+ ]]}
+ end)
end)
diff --git a/test/functional/ui/highlight_spec.lua b/test/functional/ui/highlight_spec.lua
index f40f658275..28e4e88326 100644
--- a/test/functional/ui/highlight_spec.lua
+++ b/test/functional/ui/highlight_spec.lua
@@ -35,7 +35,6 @@ describe('highlight: `:syntax manual`', function()
end)
after_each(function()
- screen:detach()
os.remove('Xtest-functional-ui-highlight.tmp.vim')
end)
@@ -97,10 +96,6 @@ describe('highlight defaults', function()
command("set display-=msgsep")
end)
- after_each(function()
- screen:detach()
- end)
-
it('window status bar', function()
screen:set_default_attr_ids({
[0] = {bold=true, foreground=Screen.colors.Blue},
@@ -346,17 +341,10 @@ describe('highlight defaults', function()
end)
describe('highlight', function()
- local screen
-
- before_each(function()
- clear()
- screen = Screen.new(25,10)
- screen:attach()
- end)
+ before_each(clear)
it('visual', function()
- screen:detach()
- screen = Screen.new(20,4)
+ local screen = Screen.new(20,4)
screen:attach()
screen:set_default_attr_ids({
[1] = {background = Screen.colors.LightGrey},
@@ -389,8 +377,7 @@ describe('highlight', function()
end)
it('cterm=standout gui=standout', function()
- screen:detach()
- screen = Screen.new(20,5)
+ local screen = Screen.new(20,5)
screen:attach()
screen:set_default_attr_ids({
[1] = {bold = true, foreground = Screen.colors.Blue1},
@@ -413,8 +400,7 @@ describe('highlight', function()
end)
it('strikethrough', function()
- screen:detach()
- screen = Screen.new(25,6)
+ local screen = Screen.new(25,6)
screen:attach()
feed_command('syntax on')
feed_command('syn keyword TmpKeyword foo')
@@ -438,7 +424,56 @@ describe('highlight', function()
})
end)
+ it('nocombine', function()
+ local screen = Screen.new(25,6)
+ screen:set_default_attr_ids{
+ [1] = {foreground = Screen.colors.SlateBlue, underline = true},
+ [2] = {bold = true, foreground = Screen.colors.Blue1},
+ [3] = {underline = true, reverse = true, foreground = Screen.colors.SlateBlue},
+ [4] = {background = Screen.colors.Yellow, reverse = true, foreground = Screen.colors.SlateBlue},
+ [5] = {foreground = Screen.colors.Red},
+ }
+ screen:attach()
+ feed_command('syntax on')
+ feed_command('hi! Underlined cterm=underline gui=underline')
+ feed_command('syn keyword Underlined foobar')
+ feed_command('hi Search cterm=inverse,nocombine gui=inverse,nocombine')
+ insert([[
+ foobar
+ foobar
+ ]])
+ screen:expect{grid=[[
+ {1:foobar} |
+ {1:foobar} |
+ ^ |
+ {2:~ }|
+ {2:~ }|
+ |
+ ]]}
+
+ feed('/foo')
+ screen:expect{grid=[[
+ {3:foo}{1:bar} |
+ {4:foo}{1:bar} |
+ |
+ {2:~ }|
+ {2:~ }|
+ /foo^ |
+ ]]}
+ feed('<cr>')
+ screen:expect{grid=[[
+ {4:^foo}{1:bar} |
+ {4:foo}{1:bar} |
+ |
+ {2:~ }|
+ {2:~ }|
+ {5:search hit...uing at TOP} |
+ ]]}
+ end)
+
it('guisp (special/undercurl)', function()
+ local screen = Screen.new(25,10)
+ screen:attach()
feed_command('syntax on')
feed_command('syn keyword TmpKeyword neovim')
feed_command('syn keyword TmpKeyword1 special')
@@ -494,10 +529,6 @@ describe("'listchars' highlight", function()
screen:attach()
end)
- after_each(function()
- screen:detach()
- end)
-
it("'cursorline' and 'cursorcolumn'", function()
screen:set_default_attr_ids({
[0] = {bold=true, foreground=Screen.colors.Blue},
@@ -657,6 +688,30 @@ describe("'listchars' highlight", function()
]])
end)
+ it("'listchar' with wrap", function()
+ screen:set_default_attr_ids({
+ [0] = {bold=true, foreground=Screen.colors.Blue},
+ })
+ feed_command('set wrap')
+ feed_command('set listchars=eol:¬,precedes:< list')
+ feed('90ia<esc>')
+ screen:expect([[
+ {0:<}aaaaaaaaaaaaaaaaaaa|
+ aaaaaaaaaaaaaaaaaaaa|
+ aaaaaaaaaaaaaaaaaaaa|
+ aaaaaaaaa^a{0:¬} |
+ |
+ ]])
+ feed('0')
+ screen:expect([[
+ ^aaaaaaaaaaaaaaaaaaaa|
+ aaaaaaaaaaaaaaaaaaaa|
+ aaaaaaaaaaaaaaaaaaaa|
+ aaaaaaaaaaaaaaaaaaaa|
+ |
+ ]])
+ end)
+
it("'listchar' in visual mode", function()
screen:set_default_attr_ids({
[1] = {background=Screen.colors.Grey90},
@@ -1131,6 +1186,7 @@ describe("'winhighlight' highlight", function()
[25] = {bold = true, foreground = Screen.colors.Green1},
[26] = {background = Screen.colors.Red},
[27] = {background = Screen.colors.DarkBlue, bold = true, foreground = Screen.colors.Green1},
+ [28] = {bold = true, foreground = Screen.colors.Brown},
})
command("hi Background1 guibg=DarkBlue")
command("hi Background2 guibg=DarkGreen")
@@ -1543,4 +1599,45 @@ describe("'winhighlight' highlight", function()
{21:-- }{22:match 1 of 3} |
]])
end)
+
+ it('can override CursorLine and CursorLineNr', function()
+ -- CursorLine used to be parsed as CursorLineNr, because strncmp
+ command('set cursorline number')
+ command('split')
+ command('set winhl=CursorLine:Background1')
+ screen:expect{grid=[[
+ {28: 1 }{1:^ }|
+ {0:~ }|
+ {0:~ }|
+ {3:[No Name] }|
+ {28: 1 }{18: }|
+ {0:~ }|
+ {4:[No Name] }|
+ |
+ ]]}
+
+ command('set winhl=CursorLineNr:Background2,CursorLine:Background1')
+ screen:expect{grid=[[
+ {5: 1 }{1:^ }|
+ {0:~ }|
+ {0:~ }|
+ {3:[No Name] }|
+ {28: 1 }{18: }|
+ {0:~ }|
+ {4:[No Name] }|
+ |
+ ]]}
+
+ feed('<c-w>w')
+ screen:expect{grid=[[
+ {5: 1 }{1: }|
+ {0:~ }|
+ {0:~ }|
+ {4:[No Name] }|
+ {28: 1 }{18:^ }|
+ {0:~ }|
+ {3:[No Name] }|
+ |
+ ]]}
+ end)
end)
diff --git a/test/functional/ui/hlstate_spec.lua b/test/functional/ui/hlstate_spec.lua
index d1c115587e..2a567b28ee 100644
--- a/test/functional/ui/hlstate_spec.lua
+++ b/test/functional/ui/hlstate_spec.lua
@@ -181,11 +181,11 @@ describe('ext_hlstate detailed highlights', function()
it("work with :terminal", function()
screen:set_default_attr_ids({
[1] = {{}, {{hi_name = "TermCursorNC", ui_name = "TermCursorNC", kind = "ui"}}},
- [2] = {{foreground = 52479}, {{kind = "term"}}},
- [3] = {{bold = true, foreground = 52479}, {{kind = "term"}}},
- [4] = {{foreground = 52479}, {2, 1}},
- [5] = {{foreground = 4259839}, {{kind = "term"}}},
- [6] = {{foreground = 4259839}, {5, 1}},
+ [2] = {{foreground = tonumber('0x00ccff'), fg_indexed=true}, {{kind = "term"}}},
+ [3] = {{bold = true, foreground = tonumber('0x00ccff'), fg_indexed=true}, {{kind = "term"}}},
+ [4] = {{foreground = tonumber('0x00ccff'), fg_indexed=true}, {2, 1}},
+ [5] = {{foreground = tonumber('0x40ffff'), fg_indexed=true}, {{kind = "term"}}},
+ [6] = {{foreground = tonumber('0x40ffff'), fg_indexed=true}, {5, 1}},
[7] = {{}, {{hi_name = "MsgArea", ui_name = "MsgArea", kind = "ui"}}},
})
command('enew | call termopen(["'..nvim_dir..'/tty-test"])')
@@ -259,7 +259,7 @@ describe('ext_hlstate detailed highlights', function()
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_rgb_cterm(true)
screen:set_default_attr_ids({
[1] = {{bold = true, foreground = Screen.colors.Blue1}, {foreground = 12}, {{hi_name = "NonText", ui_name = "EndOfBuffer", kind = "ui"}}},
diff --git a/test/functional/ui/inccommand_spec.lua b/test/functional/ui/inccommand_spec.lua
index 351c4b4bcf..afb0c9cfa6 100644
--- a/test/functional/ui/inccommand_spec.lua
+++ b/test/functional/ui/inccommand_spec.lua
@@ -18,6 +18,7 @@ local wait = helpers.wait
local nvim = helpers.nvim
local sleep = helpers.sleep
local nvim_dir = helpers.nvim_dir
+local assert_alive = helpers.assert_alive
local default_text = [[
Inc substitution on
@@ -84,18 +85,19 @@ local function common_setup(screen, inccommand, text)
[14] = {foreground = Screen.colors.White, background = Screen.colors.Red},
[15] = {bold=true, foreground=Screen.colors.Blue},
[16] = {background=Screen.colors.Grey90}, -- cursorline
+ [17] = {foreground = Screen.colors.Blue1},
vis = {background=Screen.colors.LightGrey}
})
end
- command("set inccommand=" .. (inccommand and inccommand or ""))
+ command("set inccommand=" .. (inccommand or ""))
if text then
insert(text)
end
end
-describe(":substitute, inccommand=split", function()
+describe(":substitute, inccommand=split interactivity", function()
before_each(function()
clear()
common_setup(nil, "split", default_text)
@@ -556,7 +558,6 @@ describe(":substitute, 'inccommand' preserves undo", function()
]])
end
end
- screen:detach()
end)
it('with undolevels=2', function()
@@ -647,7 +648,6 @@ describe(":substitute, 'inccommand' preserves undo", function()
Already ...t change |
]])
end
- screen:detach()
end
end)
@@ -713,7 +713,6 @@ describe(":substitute, 'inccommand' preserves undo", function()
Already ...t change |
]])
end
- screen:detach()
end)
end)
@@ -726,19 +725,15 @@ describe(":substitute, inccommand=split", function()
common_setup(screen, "split", default_text .. default_text)
end)
- after_each(function()
- screen:detach()
- end)
-
it("preserves 'modified' buffer flag", function()
feed_command("set nomodified")
feed(":%s/tw")
screen:expect([[
Inc substitution on |
{12:tw}o lines |
+ Inc substitution on |
+ {12:tw}o lines |
|
- {15:~ }|
- {15:~ }|
{11:[No Name] }|
|2| {12:tw}o lines |
|4| {12:tw}o lines |
@@ -786,6 +781,59 @@ describe(":substitute, inccommand=split", function()
{15:~ }|
:silent tabedit %s/tw/to^ |
]])
+ feed('<Esc>')
+
+ -- leading colons
+ feed(':::%s/tw/to')
+ screen:expect{any=[[{12:to}o lines]]}
+ feed('<Esc>')
+ screen:expect{any=[[two lines]]}
+ end)
+
+ it("ignores new-window modifiers when splitting the preview window", function()
+ -- one modifier
+ feed(':topleft %s/tw/to')
+ screen:expect([[
+ Inc substitution on |
+ {12:to}o lines |
+ Inc substitution on |
+ {12:to}o lines |
+ |
+ {11:[No Name] [+] }|
+ |2| {12:to}o lines |
+ |4| {12:to}o lines |
+ {15:~ }|
+ {15:~ }|
+ {15:~ }|
+ {15:~ }|
+ {15:~ }|
+ {10:[Preview] }|
+ :topleft %s/tw/to^ |
+ ]])
+ feed('<Esc>')
+ screen:expect{any=[[two lines]]}
+
+ -- multiple modifiers
+ feed(':topleft vert %s/tw/to')
+ screen:expect([[
+ Inc substitution on |
+ {12:to}o lines |
+ Inc substitution on |
+ {12:to}o lines |
+ |
+ {11:[No Name] [+] }|
+ |2| {12:to}o lines |
+ |4| {12:to}o lines |
+ {15:~ }|
+ {15:~ }|
+ {15:~ }|
+ {15:~ }|
+ {15:~ }|
+ {10:[Preview] }|
+ :topleft vert %s/tw/to^ |
+ ]])
+ feed('<Esc>')
+ screen:expect{any=[[two lines]]}
end)
it('shows split window when typing the pattern', function()
@@ -793,9 +841,9 @@ describe(":substitute, inccommand=split", function()
screen:expect([[
Inc substitution on |
{12:tw}o lines |
+ Inc substitution on |
+ {12:tw}o lines |
|
- {15:~ }|
- {15:~ }|
{11:[No Name] [+] }|
|2| {12:tw}o lines |
|4| {12:tw}o lines |
@@ -814,9 +862,9 @@ describe(":substitute, inccommand=split", function()
screen:expect([[
Inc substitution on |
o lines |
+ Inc substitution on |
+ o lines |
|
- {15:~ }|
- {15:~ }|
{11:[No Name] [+] }|
|2| o lines |
|4| o lines |
@@ -833,9 +881,9 @@ describe(":substitute, inccommand=split", function()
screen:expect([[
Inc substitution on |
{12:x}o lines |
+ Inc substitution on |
+ {12:x}o lines |
|
- {15:~ }|
- {15:~ }|
{11:[No Name] [+] }|
|2| {12:x}o lines |
|4| {12:x}o lines |
@@ -852,9 +900,9 @@ describe(":substitute, inccommand=split", function()
screen:expect([[
Inc substitution on |
o lines |
+ Inc substitution on |
+ o lines |
|
- {15:~ }|
- {15:~ }|
{11:[No Name] [+] }|
|2| o lines |
|4| o lines |
@@ -874,9 +922,9 @@ describe(":substitute, inccommand=split", function()
screen:expect([[
Inc substitution on |
{12:XX}o lines |
+ Inc substitution on |
+ {12:XX}o lines |
|
- {15:~ }|
- {15:~ }|
{11:[No Name] [+] }|
|2| {12:XX}o lines |
|4| {12:XX}o lines |
@@ -938,11 +986,11 @@ describe(":substitute, inccommand=split", function()
feed(":%s/tw")
-- 'cursorline' is NOT active during preview.
screen:expect([[
+ Inc substitution on |
{12:tw}o lines |
Inc substitution on |
{12:tw}o lines |
|
- {15:~ }|
{11:[No Name] [+] }|
|2| {12:tw}o lines |
|4| {12:tw}o lines |
@@ -1241,10 +1289,6 @@ describe("inccommand=nosplit", function()
common_setup(screen, "nosplit", default_text .. default_text)
end)
- after_each(function()
- if screen then screen:detach() end
- end)
-
it("works with :smagic, :snomagic", function()
feed_command("set hlsearch")
insert("Line *.3.* here")
@@ -1719,10 +1763,6 @@ describe("'inccommand' split windows", function()
common_setup(screen, "split", default_text)
end
- after_each(function()
- screen:detach()
- end)
-
it('work after more splits', function()
refresh()
@@ -2205,11 +2245,11 @@ describe(":substitute", function()
feed("/KKK")
screen:expect([[
+ T T123 T T123 T2T TT T23423424|
+ x |
afa {12:KKK}adf la;lkd {12:KKK}alx |
|
{15:~ }|
- {15:~ }|
- {15:~ }|
{11:[No Name] [+] }|
|3| afa {12:KKK}adf la;lkd {12:KKK}alx |
{15:~ }|
@@ -2253,6 +2293,76 @@ describe(":substitute", function()
]])
end)
+ it("inccommand=split, contraction of two subsequent NL chars", function()
+ -- luacheck: push ignore 611
+ local text = [[
+ AAA AA
+
+ BBB BB
+
+ CCC CC
+
+]]
+ -- luacheck: pop
+
+ -- This used to crash, but more than 20 highlight entries are required
+ -- to reproduce it (so that the marktree has multiple nodes)
+ common_setup(screen, "split", string.rep(text,10))
+ feed(":%s/\\n\\n/<c-v><c-m>/g")
+ screen:expect{grid=[[
+ CCC CC |
+ AAA AA |
+ BBB BB |
+ CCC CC |
+ |
+ {11:[No Name] [+] }|
+ | 1| AAA AA |
+ | 2|{12: }BBB BB |
+ | 3|{12: }CCC CC |
+ | 4|{12: }AAA AA |
+ | 5|{12: }BBB BB |
+ | 6|{12: }CCC CC |
+ | 7|{12: }AAA AA |
+ {10:[Preview] }|
+ :%s/\n\n/{17:^M}/g^ |
+ ]]}
+ assert_alive()
+ end)
+
+ it("inccommand=nosplit, contraction of two subsequent NL chars", function()
+ -- luacheck: push ignore 611
+ local text = [[
+ AAA AA
+
+ BBB BB
+
+ CCC CC
+
+]]
+ -- luacheck: pop
+
+ common_setup(screen, "nosplit", string.rep(text,10))
+ feed(":%s/\\n\\n/<c-v><c-m>/g")
+ screen:expect{grid=[[
+ CCC CC |
+ AAA AA |
+ BBB BB |
+ CCC CC |
+ AAA AA |
+ BBB BB |
+ CCC CC |
+ AAA AA |
+ BBB BB |
+ CCC CC |
+ AAA AA |
+ BBB BB |
+ CCC CC |
+ |
+ :%s/\n\n/{17:^M}/g^ |
+ ]]}
+ assert_alive()
+ end)
+
it("inccommand=split, multibyte text", function()
common_setup(screen, "split", multibyte_text)
feed(":%s/£.*ѫ/X¥¥")
@@ -2485,11 +2595,11 @@ describe(":substitute", function()
wait()
feed([[:%s/\(some\)\@<lt>!thing/one/]])
screen:expect([[
+ something |
every{12:one} |
someone |
{15:~ }|
{15:~ }|
- {15:~ }|
{11:[No Name] [+] }|
|2| every{12:one} |
{15:~ }|
@@ -2527,11 +2637,11 @@ describe(":substitute", function()
wait()
feed([[:%s/some\(thing\)\@!/every/]])
screen:expect([[
+ something |
+ everything |
{12:every}one |
{15:~ }|
{15:~ }|
- {15:~ }|
- {15:~ }|
{11:[No Name] [+] }|
|3| {12:every}one |
{15:~ }|
@@ -2544,6 +2654,49 @@ describe(":substitute", function()
:%s/some\(thing\)\@!/every/^ |
]])
end)
+
+ it("doesn't prompt to swap cmd range", function()
+ screen = Screen.new(50, 8) -- wide to avoid hit-enter prompt
+ common_setup(screen, "split", default_text)
+ feed(':2,1s/tw/MO/g')
+
+ -- substitution preview should have been made, without prompting
+ screen:expect([[
+ {12:MO}o lines |
+ {11:[No Name] [+] }|
+ |2| {12:MO}o lines |
+ {15:~ }|
+ {15:~ }|
+ {15:~ }|
+ {10:[Preview] }|
+ :2,1s/tw/MO/g^ |
+ ]])
+
+ -- but should be prompted on hitting enter
+ feed('<CR>')
+ screen:expect([[
+ {12:MO}o lines |
+ {11:[No Name] [+] }|
+ |2| {12:MO}o lines |
+ {15:~ }|
+ {15:~ }|
+ {15:~ }|
+ {10:[Preview] }|
+ {13:Backwards range given, OK to swap (y/n)?}^ |
+ ]])
+
+ feed('y')
+ screen:expect([[
+ Inc substitution on |
+ ^MOo lines |
+ |
+ {15:~ }|
+ {15:~ }|
+ {15:~ }|
+ {15:~ }|
+ {13:Backwards range given, OK to swap (y/n)?}y |
+ ]])
+ end)
end)
it(':substitute with inccommand during :terminal activity', function()
diff --git a/test/functional/ui/messages_spec.lua b/test/functional/ui/messages_spec.lua
index 875e4092a6..efc02db159 100644
--- a/test/functional/ui/messages_spec.lua
+++ b/test/functional/ui/messages_spec.lua
@@ -122,7 +122,7 @@ describe('ui/ext_messages', function()
feed('G$x')
screen:expect{grid=[[
line 1 |
- {IGNORE}|
+ {MATCH:.*}|
{1:~ }|
{1:~ }|
{1:~ }|
@@ -747,7 +747,7 @@ describe('ui/ext_messages', function()
{1:~ }|
{1:~ }|
]], messages={{
- content = {{'E5105: Error while calling lua chunk: [string "<VimL compiled string>"]:1: such\nmultiline\nerror', 2}},
+ content = {{'E5108: Error executing lua [string ":lua"]:1: such\nmultiline\nerror', 2}},
kind = "lua_error"
}}}
end)
@@ -810,6 +810,9 @@ describe('ui/builtin messages', function()
[4] = {bold = true, foreground = Screen.colors.SeaGreen4},
[5] = {foreground = Screen.colors.Blue1},
[6] = {bold = true, foreground = Screen.colors.Magenta},
+ [7] = {background = Screen.colors.Grey20},
+ [8] = {reverse = true},
+ [9] = {background = Screen.colors.LightRed}
})
end)
@@ -860,7 +863,7 @@ describe('ui/builtin messages', function()
-- screen size doesn't affect internal output #10285
eq('ErrorMsg xxx ctermfg=15 ctermbg=1 guifg=White guibg=Red',
- meths.command_output("hi ErrorMsg"))
+ meths.exec("hi ErrorMsg", true))
end)
it(':syntax list langGroup output', function()
@@ -899,9 +902,153 @@ vimComment xxx match /\s"[^\-:.%#=*].*$/ms=s+1,lc=1 excludenl contains=@vim
match /\<endif\s\+".*$/ms=s+5,lc=5 contains=@vimCommentGroup,vimCommentString
match /\<else\s\+".*$/ms=s+4,lc=4 contains=@vimCommentGroup,vimCommentString
links to Comment]],
- meths.command_output('syntax list vimComment'))
+ meths.exec('syntax list vimComment', true))
-- luacheck: pop
end)
+
+ it('supports ruler with laststatus=0', function()
+ command("set ruler laststatus=0")
+ screen:expect{grid=[[
+ ^ |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ 0,0-1 All |
+ ]]}
+
+ command("hi MsgArea guibg=#333333")
+ screen:expect{grid=[[
+ ^ |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {7: 0,0-1 All }|
+ ]]}
+
+ command("set rulerformat=%15(%c%V\\ %p%%%)")
+ screen:expect{grid=[[
+ ^ |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {7: 0,0-1 100% }|
+ ]]}
+ end)
+
+ it('supports echo with CRLF line separators', function()
+ feed(':echo "line 1\\r\\nline 2"<cr>')
+ screen:expect{grid=[[
+ |
+ {1:~ }|
+ {1:~ }|
+ {3: }|
+ line 1 |
+ line 2 |
+ {4:Press ENTER or type command to continue}^ |
+ ]]}
+
+ feed('<cr>:echo "abc\\rz"<cr>')
+ screen:expect{grid=[[
+ ^ |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ zbc |
+ ]]}
+ end)
+
+ it('redraws NOT_VALID correctly after message', function()
+ -- edge case: only one window was set NOT_VALID. Orginal report
+ -- used :make, but fake it using one command to set the current
+ -- window NOT_VALID and another to show a long message.
+ command("set more")
+ feed(':new<cr><c-w><c-w>')
+ screen:expect{grid=[[
+ |
+ {1:~ }|
+ {8:[No Name] }|
+ ^ |
+ {1:~ }|
+ {3:[No Name] }|
+ :new |
+ ]]}
+
+ feed(':set colorcolumn=10 | digraphs<cr>')
+ screen:expect{grid=[[
+ :set colorcolumn=10 | digraphs |
+ NU {5:^@} 10 SH {5:^A} 1 SX {5:^B} 2 EX {5:^C} 3 |
+ ET {5:^D} 4 EQ {5:^E} 5 AK {5:^F} 6 BL {5:^G} 7 |
+ BS {5:^H} 8 HT {5:^I} 9 LF {5:^@} 10 VT {5:^K} 11 |
+ FF {5:^L} 12 CR {5:^M} 13 SO {5:^N} 14 SI {5:^O} 15 |
+ DL {5:^P} 16 D1 {5:^Q} 17 D2 {5:^R} 18 D3 {5:^S} 19 |
+ {4:-- More --}^ |
+ ]]}
+
+ feed('q')
+ screen:expect{grid=[[
+ |
+ {1:~ }|
+ {8:[No Name] }|
+ ^ {9: } |
+ {1:~ }|
+ {3:[No Name] }|
+ |
+ ]]}
+
+ -- edge case: just covers statusline
+ feed(':set colorcolumn=5 | lua error("x\\n\\nx")<cr>')
+ screen:expect{grid=[[
+ |
+ {1:~ }|
+ {3: }|
+ {2:E5108: Error executing lua [string ":lua"]:1: x} |
+ |
+ {2:x} |
+ {4:Press ENTER or type command to continue}^ |
+ ]]}
+
+ feed('<cr>')
+ screen:expect{grid=[[
+ |
+ {1:~ }|
+ {8:[No Name] }|
+ ^ {9: } |
+ {1:~ }|
+ {3:[No Name] }|
+ |
+ ]]}
+
+ -- edge case: just covers lowest window line
+ feed(':set colorcolumn=5 | lua error("x\\n\\n\\nx")<cr>')
+ screen:expect{grid=[[
+ |
+ {3: }|
+ {2:E5108: Error executing lua [string ":lua"]:1: x} |
+ |
+ |
+ {2:x} |
+ {4:Press ENTER or type command to continue}^ |
+ ]]}
+
+ feed('<cr>')
+ screen:expect{grid=[[
+ |
+ {1:~ }|
+ {8:[No Name] }|
+ ^ {9: } |
+ {1:~ }|
+ {3:[No Name] }|
+ |
+ ]]}
+ end)
end)
describe('ui/ext_messages', function()
@@ -930,7 +1077,7 @@ describe('ui/ext_messages', function()
{1:~ }|
{1:~ }|
{1:~ }|
- {IGNORE}|
+ {MATCH:.*}|
{1:~ }|
{1:~ }Nvim is open source and freely distributable{1: }|
{1:~ }https://neovim.io/#chat{1: }|
@@ -940,8 +1087,8 @@ describe('ui/ext_messages', function()
{1:~ }type :q{5:<Enter>} to exit {1: }|
{1:~ }type :help{5:<Enter>} for help {1: }|
{1:~ }|
- {IGNORE}|
- {IGNORE}|
+ {MATCH:.*}|
+ {MATCH:.*}|
{1:~ }|
{1:~ }|
{1:~ }|
@@ -986,7 +1133,7 @@ describe('ui/ext_messages', function()
|
|
|
- {IGNORE}|
+ {MATCH:.*}|
|
Nvim is open source and freely distributable |
https://neovim.io/#chat |
@@ -996,8 +1143,8 @@ describe('ui/ext_messages', function()
type :q{5:<Enter>} to exit |
type :help{5:<Enter>} for help |
|
- {IGNORE}|
- {IGNORE}|
+ {MATCH:.*}|
+ {MATCH:.*}|
|
|
|
@@ -1089,7 +1236,7 @@ aliquip ex ea commodo consequat.]])
it('can be quit', function()
screen:try_resize(25,5)
- feed(':echon join(map(range(0, &lines*2), "v:val"), "\\n")<cr>')
+ feed(':echon join(map(range(0, &lines*10), "v:val"), "\\n")<cr>')
screen:expect{grid=[[
0 |
1 |
@@ -1110,97 +1257,96 @@ aliquip ex ea commodo consequat.]])
it('handles wrapped lines with line scroll', function()
feed(':lua error(_G.x)<cr>')
screen:expect{grid=[[
- {2:E5105: Error while calling lua chun}|
- {2:k: [string "<VimL compiled string>"}|
- {2:]:1: Lorem ipsum dolor sit amet, co}|
- {2:nsectetur} |
+ {2:E5108: Error executing lua [string }|
+ {2:":lua"]:1: Lorem ipsum dolor sit am}|
+ {2:et, consectetur} |
{2:adipisicing elit, sed do eiusmod te}|
{2:mpor} |
{2:incididunt ut labore et dolore magn}|
+ {2:a aliqua.} |
{4:-- More --}^ |
]]}
feed('j')
screen:expect{grid=[[
- {2:k: [string "<VimL compiled string>"}|
- {2:]:1: Lorem ipsum dolor sit amet, co}|
- {2:nsectetur} |
+ {2:":lua"]:1: Lorem ipsum dolor sit am}|
+ {2:et, consectetur} |
{2:adipisicing elit, sed do eiusmod te}|
{2:mpor} |
{2:incididunt ut labore et dolore magn}|
{2:a aliqua.} |
+ {2:Ut enim ad minim veniam, quis nostr}|
{4:-- More --}^ |
]]}
feed('k')
screen:expect{grid=[[
- {2:E5105: Error while calling lua chun}|
- {2:k: [string "<VimL compiled string>"}|
- {2:]:1: Lorem ipsum dolor sit amet, co}|
- {2:nsectetur} |
+ {2:E5108: Error executing lua [string }|
+ {2:":lua"]:1: Lorem ipsum dolor sit am}|
+ {2:et, consectetur} |
{2:adipisicing elit, sed do eiusmod te}|
{2:mpor} |
{2:incididunt ut labore et dolore magn}|
+ {2:a aliqua.} |
{4:-- More --}^ |
]]}
feed('j')
screen:expect{grid=[[
- {2:k: [string "<VimL compiled string>"}|
- {2:]:1: Lorem ipsum dolor sit amet, co}|
- {2:nsectetur} |
+ {2:":lua"]:1: Lorem ipsum dolor sit am}|
+ {2:et, consectetur} |
{2:adipisicing elit, sed do eiusmod te}|
{2:mpor} |
{2:incididunt ut labore et dolore magn}|
{2:a aliqua.} |
+ {2:Ut enim ad minim veniam, quis nostr}|
{4:-- More --}^ |
]]}
-
end)
it('handles wrapped lines with page scroll', function()
feed(':lua error(_G.x)<cr>')
screen:expect{grid=[[
- {2:E5105: Error while calling lua chun}|
- {2:k: [string "<VimL compiled string>"}|
- {2:]:1: Lorem ipsum dolor sit amet, co}|
- {2:nsectetur} |
+ {2:E5108: Error executing lua [string }|
+ {2:":lua"]:1: Lorem ipsum dolor sit am}|
+ {2:et, consectetur} |
{2:adipisicing elit, sed do eiusmod te}|
{2:mpor} |
{2:incididunt ut labore et dolore magn}|
+ {2:a aliqua.} |
{4:-- More --}^ |
]]}
feed('d')
screen:expect{grid=[[
- {2:adipisicing elit, sed do eiusmod te}|
- {2:mpor} |
{2:incididunt ut labore et dolore magn}|
{2:a aliqua.} |
{2:Ut enim ad minim veniam, quis nostr}|
{2:ud xercitation} |
{2:ullamco laboris nisi ut} |
- {4:-- More --}^ |
+ {2:aliquip ex ea commodo consequat.} |
+ {4:Press ENTER or type command to cont}|
+ {4:inue}^ |
]]}
feed('u')
screen:expect{grid=[[
- {2:E5105: Error while calling lua chun}|
- {2:k: [string "<VimL compiled string>"}|
- {2:]:1: Lorem ipsum dolor sit amet, co}|
- {2:nsectetur} |
+ {2:E5108: Error executing lua [string }|
+ {2:":lua"]:1: Lorem ipsum dolor sit am}|
+ {2:et, consectetur} |
{2:adipisicing elit, sed do eiusmod te}|
{2:mpor} |
{2:incididunt ut labore et dolore magn}|
+ {2:a aliqua.} |
{4:-- More --}^ |
]]}
feed('d')
screen:expect{grid=[[
- {2:adipisicing elit, sed do eiusmod te}|
{2:mpor} |
{2:incididunt ut labore et dolore magn}|
{2:a aliqua.} |
{2:Ut enim ad minim veniam, quis nostr}|
{2:ud xercitation} |
{2:ullamco laboris nisi ut} |
+ {2:aliquip ex ea commodo consequat.} |
{4:-- More --}^ |
]]}
end)
@@ -1210,49 +1356,49 @@ aliquip ex ea commodo consequat.]])
feed(':lua error(_G.x)<cr>')
screen:expect{grid=[[
- {3:E5105: Error while calling lua chun}|
- {3:k: [string "<VimL compiled string>"}|
- {3:]:1: Lorem ipsum dolor sit amet, co}|
- {3:nsectetur}{5: }|
+ {3:E5108: Error executing lua [string }|
+ {3:":lua"]:1: Lorem ipsum dolor sit am}|
+ {3:et, consectetur}{5: }|
{3:adipisicing elit, sed do eiusmod te}|
{3:mpor}{5: }|
{3:incididunt ut labore et dolore magn}|
+ {3:a aliqua.}{5: }|
{6:-- More --}{5:^ }|
]]}
feed('j')
screen:expect{grid=[[
- {3:k: [string "<VimL compiled string>"}|
- {3:]:1: Lorem ipsum dolor sit amet, co}|
- {3:nsectetur}{5: }|
+ {3:":lua"]:1: Lorem ipsum dolor sit am}|
+ {3:et, consectetur}{5: }|
{3:adipisicing elit, sed do eiusmod te}|
{3:mpor}{5: }|
{3:incididunt ut labore et dolore magn}|
{3:a aliqua.}{5: }|
+ {3:Ut enim ad minim veniam, quis nostr}|
{6:-- More --}{5:^ }|
]]}
feed('k')
screen:expect{grid=[[
- {3:E5105: Error while calling lua chun}|
- {3:k: [string "<VimL compiled string>"}|
- {3:]:1: Lorem ipsum dolor sit amet, co}|
- {3:nsectetur}{5: }|
+ {3:E5108: Error executing lua [string }|
+ {3:":lua"]:1: Lorem ipsum dolor sit am}|
+ {3:et, consectetur}{5: }|
{3:adipisicing elit, sed do eiusmod te}|
{3:mpor}{5: }|
{3:incididunt ut labore et dolore magn}|
+ {3:a aliqua.}{5: }|
{6:-- More --}{5:^ }|
]]}
feed('j')
screen:expect{grid=[[
- {3:k: [string "<VimL compiled string>"}|
- {3:]:1: Lorem ipsum dolor sit amet, co}|
- {3:nsectetur}{5: }|
+ {3:":lua"]:1: Lorem ipsum dolor sit am}|
+ {3:et, consectetur}{5: }|
{3:adipisicing elit, sed do eiusmod te}|
{3:mpor}{5: }|
{3:incididunt ut labore et dolore magn}|
{3:a aliqua.}{5: }|
+ {3:Ut enim ad minim veniam, quis nostr}|
{6:-- More --}{5:^ }|
]]}
end)
@@ -1261,46 +1407,46 @@ aliquip ex ea commodo consequat.]])
command("hi MsgArea guisp=Yellow")
feed(':lua error(_G.x)<cr>')
screen:expect{grid=[[
- {3:E5105: Error while calling lua chun}|
- {3:k: [string "<VimL compiled string>"}|
- {3:]:1: Lorem ipsum dolor sit amet, co}|
- {3:nsectetur}{5: }|
+ {3:E5108: Error executing lua [string }|
+ {3:":lua"]:1: Lorem ipsum dolor sit am}|
+ {3:et, consectetur}{5: }|
{3:adipisicing elit, sed do eiusmod te}|
{3:mpor}{5: }|
{3:incididunt ut labore et dolore magn}|
+ {3:a aliqua.}{5: }|
{6:-- More --}{5:^ }|
]]}
feed('d')
screen:expect{grid=[[
- {3:adipisicing elit, sed do eiusmod te}|
- {3:mpor}{5: }|
{3:incididunt ut labore et dolore magn}|
{3:a aliqua.}{5: }|
{3:Ut enim ad minim veniam, quis nostr}|
{3:ud xercitation}{5: }|
{3:ullamco laboris nisi ut}{5: }|
- {6:-- More --}{5:^ }|
+ {3:aliquip ex ea commodo consequat.}{5: }|
+ {6:Press ENTER or type command to cont}|
+ {6:inue}{5:^ }|
]]}
feed('u')
screen:expect{grid=[[
- {3:E5105: Error while calling lua chun}|
- {3:k: [string "<VimL compiled string>"}|
- {3:]:1: Lorem ipsum dolor sit amet, co}|
- {3:nsectetur}{5: }|
+ {3:E5108: Error executing lua [string }|
+ {3:":lua"]:1: Lorem ipsum dolor sit am}|
+ {3:et, consectetur}{5: }|
{3:adipisicing elit, sed do eiusmod te}|
{3:mpor}{5: }|
{3:incididunt ut labore et dolore magn}|
+ {3:a aliqua.}{5: }|
{6:-- More --}{5:^ }|
]]}
feed('d')
screen:expect{grid=[[
- {3:adipisicing elit, sed do eiusmod te}|
{3:mpor}{5: }|
{3:incididunt ut labore et dolore magn}|
{3:a aliqua.}{5: }|
{3:Ut enim ad minim veniam, quis nostr}|
{3:ud xercitation}{5: }|
{3:ullamco laboris nisi ut}{5: }|
+ {3:aliquip ex ea commodo consequat.}{5: }|
{6:-- More --}{5:^ }|
]]}
end)
@@ -1437,23 +1583,23 @@ aliquip ex ea commodo consequat.]])
it('can be resized', function()
feed(':lua error(_G.x)<cr>')
screen:expect{grid=[[
- {2:E5105: Error while calling lua chun}|
- {2:k: [string "<VimL compiled string>"}|
- {2:]:1: Lorem ipsum dolor sit amet, co}|
- {2:nsectetur} |
+ {2:E5108: Error executing lua [string }|
+ {2:":lua"]:1: Lorem ipsum dolor sit am}|
+ {2:et, consectetur} |
{2:adipisicing elit, sed do eiusmod te}|
{2:mpor} |
{2:incididunt ut labore et dolore magn}|
+ {2:a aliqua.} |
{4:-- More --}^ |
]]}
-- responds to resize, but text is not reflown
screen:try_resize(45, 5)
screen:expect{grid=[[
- {2:nsectetur} |
{2:adipisicing elit, sed do eiusmod te} |
{2:mpor} |
{2:incididunt ut labore et dolore magn} |
+ {2:a aliqua.} |
{4:-- More --}^ |
]]}
@@ -1461,14 +1607,14 @@ aliquip ex ea commodo consequat.]])
-- text is not reflown; existing lines get cut
screen:try_resize(30, 12)
screen:expect{grid=[[
- {2:E5105: Error while calling lua}|
- {2:k: [string "<VimL compiled str}|
- {2:]:1: Lorem ipsum dolor sit ame}|
- {2:nsectetur} |
+ {2:E5108: Error executing lua [st}|
+ {2:":lua"]:1: Lorem ipsum dolor s}|
+ {2:et, consectetur} |
{2:adipisicing elit, sed do eiusm}|
{2:mpore} |
{2:incididunt ut labore et dolore}|
- {2: magn} |
+ {2:a aliqua.} |
+ |
|
|
|
@@ -1479,18 +1625,18 @@ aliquip ex ea commodo consequat.]])
-- wrapped at the new screen size.
feed('<cr>')
screen:expect{grid=[[
- {2:k: [string "<VimL compiled str}|
- {2:]:1: Lorem ipsum dolor sit ame}|
- {2:nsectetur} |
+ {2:et, consectetur} |
{2:adipisicing elit, sed do eiusm}|
{2:mpore} |
{2:incididunt ut labore et dolore}|
- {2: magna aliqua.} |
+ {2:a aliqua.} |
{2:Ut enim ad minim veniam, quis }|
{2:nostrud xercitation} |
{2:ullamco laboris nisi ut} |
{2:aliquip ex ea commodo consequa}|
- {4:-- More --}^ |
+ {2:t.} |
+ {4:Press ENTER or type command to}|
+ {4: continue}^ |
]]}
feed('q')
diff --git a/test/functional/ui/mode_spec.lua b/test/functional/ui/mode_spec.lua
index 200f6eecdb..9390f268b3 100644
--- a/test/functional/ui/mode_spec.lua
+++ b/test/functional/ui/mode_spec.lua
@@ -3,6 +3,7 @@ local Screen = require('test.functional.ui.screen')
local clear, feed, insert = helpers.clear, helpers.feed, helpers.insert
local command = helpers.command
+local retry = helpers.retry
describe('ui mode_change event', function()
local screen
@@ -61,30 +62,36 @@ describe('ui mode_change event', function()
|
]], mode="normal"}
+ local matchtime = 0
command("set showmatch")
- command("set matchtime=2") -- tenths of seconds
- feed('a(stuff')
- screen:expect{grid=[[
- word(stuff^ |
- {0:~ }|
- {0:~ }|
- {2:-- INSERT --} |
- ]], mode="insert"}
-
- feed(')')
- screen:expect{grid=[[
- word^(stuff) |
- {0:~ }|
- {0:~ }|
- {2:-- INSERT --} |
- ]], mode="showmatch"}
-
- screen:expect{grid=[[
- word(stuff)^ |
- {0:~ }|
- {0:~ }|
- {2:-- INSERT --} |
- ]], mode="insert"}
+ retry(nil, nil, function()
+ matchtime = matchtime + 1
+ local screen_timeout = 1000 * matchtime -- fail faster for retry.
+
+ command("set matchtime=" .. matchtime) -- tenths of seconds
+ feed('a(stuff')
+ screen:expect{grid=[[
+ word(stuff^ |
+ {0:~ }|
+ {0:~ }|
+ {2:-- INSERT --} |
+ ]], mode="insert", timeout=screen_timeout}
+
+ feed(')')
+ screen:expect{grid=[[
+ word^(stuff) |
+ {0:~ }|
+ {0:~ }|
+ {2:-- INSERT --} |
+ ]], mode="showmatch", timeout=screen_timeout}
+
+ screen:expect{grid=[[
+ word(stuff)^ |
+ {0:~ }|
+ {0:~ }|
+ {2:-- INSERT --} |
+ ]], mode="insert", timeout=screen_timeout}
+ end)
end)
it('works in replace mode', function()
diff --git a/test/functional/ui/mouse_spec.lua b/test/functional/ui/mouse_spec.lua
index 3bd6b81ff1..d857b57a31 100644
--- a/test/functional/ui/mouse_spec.lua
+++ b/test/functional/ui/mouse_spec.lua
@@ -12,7 +12,10 @@ describe('ui/mouse/input', function()
clear()
meths.set_option('mouse', 'a')
meths.set_option('list', true)
- meths.set_option('listchars', 'eol:$')
+ -- NB: this is weird, but mostly irrelevant to the test
+ -- So I didn't bother to change it
+ command('set listchars=eol:$')
+ command('setl listchars=nbsp:x')
screen = Screen.new(25, 5)
screen:attach()
screen:set_default_attr_ids({
@@ -26,6 +29,8 @@ describe('ui/mouse/input', function()
},
[4] = {reverse = true},
[5] = {bold = true, reverse = true},
+ [6] = {foreground = Screen.colors.Grey100, background = Screen.colors.Red},
+ [7] = {bold = true, foreground = Screen.colors.SeaGreen4},
})
command("set display-=msgsep")
feed('itesting<cr>mouse<cr>support and selection<esc>')
@@ -38,10 +43,6 @@ describe('ui/mouse/input', function()
]])
end)
- after_each(function()
- screen:detach()
- end)
-
it('single left click moves cursor', function()
feed('<LeftMouse><2,1>')
screen:expect([[
@@ -419,9 +420,9 @@ describe('ui/mouse/input', function()
meths.set_option('showtabline', 2)
screen:expect([[
{fill:test-test2 }|
+ testing |
mouse |
support and selectio^n |
- {0:~ }|
|
]])
meths.set_var('reply', {})
@@ -539,9 +540,9 @@ describe('ui/mouse/input', function()
feed_command('tabprevious') -- go to first tab
screen:expect([[
{sel: + foo }{tab: + bar }{fill: }{tab:X}|
+ testing |
mouse |
support and selectio^n |
- {0:~ }|
:tabprevious |
]])
feed('<LeftMouse><10,0><LeftRelease>') -- go to second tab
@@ -620,12 +621,12 @@ describe('ui/mouse/input', function()
meths.set_option('tags', './non-existent-tags-file')
feed('<C-LeftMouse><0,0>')
screen:expect([[
- E433: No tags file |
- E426: tag not found: test|
- ing |
- Press ENTER or type comma|
- nd to continue^ |
- ]],nil,true)
+ {6:E433: No tags file} |
+ {6:E426: tag not found: test}|
+ {6:ing} |
+ {7:Press ENTER or type comma}|
+ {7:nd to continue}^ |
+ ]])
feed('<cr>')
end)
@@ -814,7 +815,8 @@ describe('ui/mouse/input', function()
feed_command('set concealcursor=ni')
feed_command('set nowrap')
- feed_command('set shiftwidth=2 tabstop=4 list listchars=tab:>-')
+ feed_command('set shiftwidth=2 tabstop=4 list')
+ feed_command('setl listchars=tab:>-')
feed_command('syntax match NonText "\\*" conceal')
feed_command('syntax match NonText "cats" conceal cchar=X')
feed_command('syntax match NonText "x" conceal cchar=>')
diff --git a/test/functional/ui/multibyte_spec.lua b/test/functional/ui/multibyte_spec.lua
index 3e63353ad2..e6a79feadc 100644
--- a/test/functional/ui/multibyte_spec.lua
+++ b/test/functional/ui/multibyte_spec.lua
@@ -21,10 +21,6 @@ describe("multibyte rendering", function()
})
end)
- after_each(function()
- screen:detach()
- end)
-
it("works with composed char at start of line", function()
insert([[
̊
@@ -127,20 +123,20 @@ describe('multibyte rendering: statusline', function()
before_each(function()
clear()
screen = Screen.new(40, 4)
+ screen:set_default_attr_ids({
+ [1] = {bold = true, foreground = Screen.colors.Blue1},
+ [2] = {bold = true, reverse = true},
+ })
screen:attach()
command('set laststatus=2')
end)
- after_each(function()
- screen:detach()
- end)
-
it('last char shows (multibyte)', function()
command('set statusline=你好')
screen:expect([[
^ |
- ~ |
- 你好 |
+ {1:~ }|
+ {2:你好 }|
|
]])
end)
@@ -148,8 +144,8 @@ describe('multibyte rendering: statusline', function()
command('set statusline=abc')
screen:expect([[
^ |
- ~ |
- abc |
+ {1:~ }|
+ {2:abc }|
|
]])
end)
@@ -157,8 +153,8 @@ describe('multibyte rendering: statusline', function()
command('set statusline=Ÿ')
screen:expect([[
^ |
- ~ |
- <9f> |
+ {1:~ }|
+ {2:<9f> }|
|
]])
end)
@@ -167,8 +163,8 @@ describe('multibyte rendering: statusline', function()
-- o + U+1DF0 + U+20EF + U+0338 + U+20D0 + U+20E7 + U+20DD
screen:expect([[
^ |
- ~ |
- o̸⃯ᷰ⃐⃧⃝ |
+ {1:~ }|
+ {2:o̸⃯ᷰ⃐⃧⃝ }|
|
]])
end)
@@ -177,9 +173,19 @@ describe('multibyte rendering: statusline', function()
-- U+9F + U+1DF0 + U+20EF + U+0338 + U+20D0 + U+20E7 + U+20DD
screen:expect([[
^ |
- ~ |
- <9f><1df0><20ef><0338><20d0><20e7><20dd>|
+ {1:~ }|
+ {2:<9f><1df0><20ef><0338><20d0><20e7><20dd>}|
|
]])
end)
+
+ it('hidden group %( %) does not cause invalid unicode', function()
+ command("let &statusline = '%#StatColorHi2#%(✓%#StatColorHi2#%) Q≡'")
+ screen:expect{grid=[[
+ ^ |
+ {1:~ }|
+ {2: Q≡ }|
+ |
+ ]]}
+ end)
end)
diff --git a/test/functional/ui/multigrid_spec.lua b/test/functional/ui/multigrid_spec.lua
index 30a5b63d89..e4d1187dea 100644
--- a/test/functional/ui/multigrid_spec.lua
+++ b/test/functional/ui/multigrid_spec.lua
@@ -37,10 +37,6 @@ describe('ext_multigrid', function()
})
end)
- after_each(function()
- screen:detach()
- end)
-
it('default initial screen', function()
screen:expect{grid=[[
## grid 1
@@ -1966,4 +1962,191 @@ describe('ext_multigrid', function()
{1:~ }|
]]}
end)
+
+ it('has viewport information', function()
+ screen:try_resize(48, 8)
+ screen:expect{grid=[[
+ ## grid 1
+ [2:------------------------------------------------]|
+ [2:------------------------------------------------]|
+ [2:------------------------------------------------]|
+ [2:------------------------------------------------]|
+ [2:------------------------------------------------]|
+ [2:------------------------------------------------]|
+ {11:[No Name] }|
+ [3:------------------------------------------------]|
+ ## grid 2
+ ^ |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ ## grid 3
+ |
+ ]], win_viewport={
+ [2] = {win = { id = 1000 }, topline = 0, botline = 2, curline = 0, curcol = 0}
+ }}
+ insert([[
+ Lorem ipsum dolor sit amet, consectetur
+ adipisicing elit, sed do eiusmod tempor
+ incididunt ut labore et dolore magna aliqua.
+ Ut enim ad minim veniam, quis nostrud
+ exercitation ullamco laboris nisi ut aliquip ex
+ ea commodo consequat. Duis aute irure dolor in
+ reprehenderit in voluptate velit esse cillum
+ dolore eu fugiat nulla pariatur. Excepteur sint
+ occaecat cupidatat non proident, sunt in culpa
+ qui officia deserunt mollit anim id est
+ laborum.]])
+
+ screen:expect{grid=[[
+ ## grid 1
+ [2:------------------------------------------------]|
+ [2:------------------------------------------------]|
+ [2:------------------------------------------------]|
+ [2:------------------------------------------------]|
+ [2:------------------------------------------------]|
+ [2:------------------------------------------------]|
+ {11:[No Name] [+] }|
+ [3:------------------------------------------------]|
+ ## grid 2
+ ea commodo consequat. Duis aute irure dolor in |
+ reprehenderit in voluptate velit esse cillum |
+ dolore eu fugiat nulla pariatur. Excepteur sint |
+ occaecat cupidatat non proident, sunt in culpa |
+ qui officia deserunt mollit anim id est |
+ laborum^. |
+ ## grid 3
+ |
+ ]], win_viewport={
+ [2] = {win = {id = 1000}, topline = 5, botline = 11, curline = 10, curcol = 7},
+ }}
+
+
+ feed('<c-u>')
+ screen:expect{grid=[[
+ ## grid 1
+ [2:------------------------------------------------]|
+ [2:------------------------------------------------]|
+ [2:------------------------------------------------]|
+ [2:------------------------------------------------]|
+ [2:------------------------------------------------]|
+ [2:------------------------------------------------]|
+ {11:[No Name] [+] }|
+ [3:------------------------------------------------]|
+ ## grid 2
+ incididunt ut labore et dolore magna aliqua. |
+ Ut enim ad minim veniam, quis nostrud |
+ exercitation ullamco laboris nisi ut aliquip ex |
+ ea commodo consequat. Duis aute irure dolor in |
+ reprehenderit in voluptate velit esse cillum |
+ ^dolore eu fugiat nulla pariatur. Excepteur sint |
+ ## grid 3
+ |
+ ]], win_viewport={
+ [2] = {win = {id = 1000}, topline = 2, botline = 9, curline = 7, curcol = 0},
+ }}
+
+ command("split")
+ screen:expect{grid=[[
+ ## grid 1
+ [4:------------------------------------------------]|
+ [4:------------------------------------------------]|
+ [4:------------------------------------------------]|
+ {11:[No Name] [+] }|
+ [2:------------------------------------------------]|
+ [2:------------------------------------------------]|
+ {12:[No Name] [+] }|
+ [3:------------------------------------------------]|
+ ## grid 2
+ reprehenderit in voluptate velit esse cillum |
+ dolore eu fugiat nulla pariatur. Excepteur sint |
+ ## grid 3
+ |
+ ## grid 4
+ ea commodo consequat. Duis aute irure dolor in |
+ reprehenderit in voluptate velit esse cillum |
+ ^dolore eu fugiat nulla pariatur. Excepteur sint |
+ ]], win_viewport={
+ [2] = {win = {id = 1000}, topline = 6, botline = 9, curline = 7, curcol = 0},
+ [4] = {win = {id = 1001}, topline = 5, botline = 9, curline = 7, curcol = 0},
+ }}
+
+ feed("b")
+ screen:expect{grid=[[
+ ## grid 1
+ [4:------------------------------------------------]|
+ [4:------------------------------------------------]|
+ [4:------------------------------------------------]|
+ {11:[No Name] [+] }|
+ [2:------------------------------------------------]|
+ [2:------------------------------------------------]|
+ {12:[No Name] [+] }|
+ [3:------------------------------------------------]|
+ ## grid 2
+ reprehenderit in voluptate velit esse cillum |
+ dolore eu fugiat nulla pariatur. Excepteur sint |
+ ## grid 3
+ |
+ ## grid 4
+ ea commodo consequat. Duis aute irure dolor in |
+ reprehenderit in voluptate velit esse ^cillum |
+ dolore eu fugiat nulla pariatur. Excepteur sint |
+ ]], win_viewport={
+ [2] = {win = {id = 1000}, topline = 6, botline = 9, curline = 7, curcol = 0},
+ [4] = {win = {id = 1001}, topline = 5, botline = 9, curline = 6, curcol = 38},
+ }}
+
+ feed("2k")
+ screen:expect{grid=[[
+ ## grid 1
+ [4:------------------------------------------------]|
+ [4:------------------------------------------------]|
+ [4:------------------------------------------------]|
+ {11:[No Name] [+] }|
+ [2:------------------------------------------------]|
+ [2:------------------------------------------------]|
+ {12:[No Name] [+] }|
+ [3:------------------------------------------------]|
+ ## grid 2
+ reprehenderit in voluptate velit esse cillum |
+ dolore eu fugiat nulla pariatur. Excepteur sint |
+ ## grid 3
+ |
+ ## grid 4
+ exercitation ullamco laboris nisi ut a^liquip ex |
+ ea commodo consequat. Duis aute irure dolor in |
+ reprehenderit in voluptate velit esse cillum |
+ ]], win_viewport={
+ [2] = {win = {id = 1000}, topline = 6, botline = 9, curline = 7, curcol = 0},
+ [4] = {win = {id = 1001}, topline = 4, botline = 8, curline = 4, curcol = 38},
+ }}
+
+ -- handles non-current window
+ meths.win_set_cursor(1000, {1, 10})
+ screen:expect{grid=[[
+ ## grid 1
+ [4:------------------------------------------------]|
+ [4:------------------------------------------------]|
+ [4:------------------------------------------------]|
+ {11:[No Name] [+] }|
+ [2:------------------------------------------------]|
+ [2:------------------------------------------------]|
+ {12:[No Name] [+] }|
+ [3:------------------------------------------------]|
+ ## grid 2
+ Lorem ipsum dolor sit amet, consectetur |
+ adipisicing elit, sed do eiusmod tempor |
+ ## grid 3
+ |
+ ## grid 4
+ exercitation ullamco laboris nisi ut a^liquip ex |
+ ea commodo consequat. Duis aute irure dolor in |
+ reprehenderit in voluptate velit esse cillum |
+ ]], win_viewport={
+ [2] = {win = {id = 1000}, topline = 0, botline = 3, curline = 0, curcol = 10},
+ [4] = {win = {id = 1001}, topline = 4, botline = 8, curline = 4, curcol = 38},
+ }}
+ end)
end)
diff --git a/test/functional/ui/options_spec.lua b/test/functional/ui/options_spec.lua
index 93192934c7..9646c3fdad 100644
--- a/test/functional/ui/options_spec.lua
+++ b/test/functional/ui/options_spec.lua
@@ -5,7 +5,7 @@ local command = helpers.command
local eq = helpers.eq
local shallowcopy = helpers.shallowcopy
-describe('ui receives option updates', function()
+describe('UI receives option updates', function()
local screen
local function reset(opts, ...)
@@ -20,6 +20,8 @@ describe('ui receives option updates', function()
pumblend=0,
showtabline=1,
termguicolors=false,
+ ttimeout=true,
+ ttimeoutlen=50,
ext_cmdline=false,
ext_popupmenu=false,
ext_tabline=false,
@@ -40,10 +42,6 @@ describe('ui receives option updates', function()
return defaults
end
- after_each(function()
- screen:detach()
- end)
-
it("for defaults", function()
local expected = reset()
screen:expect(function()
@@ -51,6 +49,33 @@ describe('ui receives option updates', function()
end)
end)
+ it('on attach #11372', function()
+ clear()
+ local evs = {}
+ screen = Screen.new(20,5)
+ -- Override mouse_on/mouse_off handlers.
+ function screen:_handle_mouse_on()
+ table.insert(evs, 'mouse_on')
+ end
+ function screen:_handle_mouse_off()
+ table.insert(evs, 'mouse_off')
+ end
+ screen:attach()
+ screen:expect(function()
+ eq({'mouse_off'}, evs)
+ end)
+ command("set mouse=nvi")
+ screen:expect(function()
+ eq({'mouse_off','mouse_on'}, evs)
+ end)
+ screen:detach()
+ eq({'mouse_off','mouse_on'}, evs)
+ screen:attach()
+ screen:expect(function()
+ eq({'mouse_off','mouse_on','mouse_on'}, evs)
+ end)
+ end)
+
it("when setting options", function()
local expected = reset()
local defaults = shallowcopy(expected)
@@ -85,6 +110,18 @@ describe('ui receives option updates', function()
eq(expected, screen.options)
end)
+ command("set nottimeout")
+ expected.ttimeout = false
+ screen:expect(function()
+ eq(expected, screen.options)
+ end)
+
+ command("set ttimeoutlen=100")
+ expected.ttimeoutlen = 100
+ screen:expect(function()
+ eq(expected, screen.options)
+ end)
+
command("set all&")
screen:expect(function()
eq(defaults, screen.options)
diff --git a/test/functional/ui/output_spec.lua b/test/functional/ui/output_spec.lua
index c028f44b44..d7dde6345f 100644
--- a/test/functional/ui/output_spec.lua
+++ b/test/functional/ui/output_spec.lua
@@ -10,6 +10,8 @@ local iswin = helpers.iswin
local clear = helpers.clear
local command = helpers.command
local nvim_dir = helpers.nvim_dir
+local has_powershell = helpers.has_powershell
+local set_shell_powershell = helpers.set_shell_powershell
describe("shell command :!", function()
local screen
@@ -30,7 +32,6 @@ describe("shell command :!", function()
after_each(function()
child_session.feed_data("\3") -- Ctrl-C
- screen:detach()
end)
it("displays output without LF/EOF. #4646 #4569 #3772", function()
@@ -51,8 +52,7 @@ describe("shell command :!", function()
it("throttles shell-command output greater than ~10KB", function()
if 'openbsd' == helpers.uname() then
- pending('FIXME #10804', function() end)
- return
+ pending('FIXME #10804')
end
child_session.feed_data(":!"..nvim_dir.."/shell-test REP 30001 foo\n")
@@ -96,8 +96,7 @@ describe("shell command :!", function()
it('handles control codes', function()
if iswin() then
- pending('missing printf', function() end)
- return
+ pending('missing printf')
end
local screen = Screen.new(50, 4)
screen:attach()
@@ -230,4 +229,23 @@ describe("shell command :!", function()
]])
end)
end)
+ if has_powershell() then
+ it('powershell supports literal strings', function()
+ set_shell_powershell()
+ local screen = Screen.new(30, 4)
+ screen:attach()
+ feed_command([[!'Write-Output $a']])
+ screen:expect{any='\nWrite%-Output %$a', timeout=10000}
+ feed_command([[!$a = 1; Write-Output '$a']])
+ screen:expect{any='\n%$a', timeout=10000}
+ feed_command([[!"Write-Output $a"]])
+ screen:expect{any='\nWrite%-Output', timeout=10000}
+ feed_command([[!$a = 1; Write-Output "$a"]])
+ screen:expect{any='\n1', timeout=10000}
+ feed_command(iswin()
+ and [[!& 'C:\\Windows\\system32\\cmd.exe' /c 'echo $a']]
+ or [[!& '/bin/sh' -c 'echo ''$a''']])
+ screen:expect{any='\n%$a', timeout=10000}
+ end)
+ end
end)
diff --git a/test/functional/ui/popupmenu_spec.lua b/test/functional/ui/popupmenu_spec.lua
index ae2136f451..c1c5d1ce2e 100644
--- a/test/functional/ui/popupmenu_spec.lua
+++ b/test/functional/ui/popupmenu_spec.lua
@@ -8,7 +8,7 @@ local command = helpers.command
local funcs = helpers.funcs
local get_pathsep = helpers.get_pathsep
local eq = helpers.eq
-local matches = helpers.matches
+local pcall_err = helpers.pcall_err
describe('ui/ext_popupmenu', function()
local screen
@@ -382,7 +382,7 @@ describe('ui/ext_popupmenu', function()
end
describe('pum_set_height', function()
- it('can be set pum height', function()
+ it('can set pum height', function()
source_complete_month()
local month_expected = {
{'January', '', '', ''},
@@ -421,22 +421,79 @@ describe('ui/ext_popupmenu', function()
end)
it('an error occurs if set 0 or less', function()
- local ok, err, _
- ok, _ = pcall(meths.ui_pum_set_height, 1)
- eq(ok, true)
- ok, err = pcall(meths.ui_pum_set_height, 0)
- eq(ok, false)
- matches('.*: Expected pum height > 0', err)
+ meths.ui_pum_set_height(1)
+ eq('Expected pum height > 0',
+ pcall_err(meths.ui_pum_set_height, 0))
end)
it('an error occurs when ext_popupmenu is false', function()
- local ok, err, _
- ok, _ = pcall(meths.ui_pum_set_height, 1)
- eq(ok, true)
+ meths.ui_pum_set_height(1)
screen:set_option('ext_popupmenu', false)
- ok, err = pcall(meths.ui_pum_set_height, 1)
- eq(ok, false)
- matches('.*: It must support the ext_popupmenu option', err)
+ eq('It must support the ext_popupmenu option',
+ pcall_err(meths.ui_pum_set_height, 1))
+ end)
+ end)
+
+ describe('pum_set_bounds', function()
+ it('can set pum bounds', function()
+ source_complete_month()
+ local month_expected = {
+ {'January', '', '', ''},
+ {'February', '', '', ''},
+ {'March', '', '', ''},
+ {'April', '', '', ''},
+ {'May', '', '', ''},
+ {'June', '', '', ''},
+ {'July', '', '', ''},
+ {'August', '', '', ''},
+ {'September', '', '', ''},
+ {'October', '', '', ''},
+ {'November', '', '', ''},
+ {'December', '', '', ''},
+ }
+ local pum_height = 6
+ feed('o<C-r>=TestCompleteMonth()<CR>')
+ meths.ui_pum_set_height(pum_height)
+ -- set bounds w h r c
+ meths.ui_pum_set_bounds(10.5, 5.2, 6.3, 7.4)
+ feed('<PageDown>')
+ -- pos becomes pum_height-2 because it is subtracting 2 to keep some
+ -- context in ins_compl_key2count()
+ screen:expect{grid=[[
+ |
+ January^ |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {2:-- INSERT --} |
+ ]], popupmenu={
+ items=month_expected,
+ pos=pum_height-2,
+ anchor={1,1,0},
+ }}
+ end)
+
+ it('no error occurs if row or col set less than 0', function()
+ meths.ui_pum_set_bounds(1.0, 1.0, 0.0, 1.5)
+ meths.ui_pum_set_bounds(1.0, 1.0, -1.0, 0.0)
+ meths.ui_pum_set_bounds(1.0, 1.0, 0.0, -1.0)
+ end)
+
+ it('an error occurs if width or height set 0 or less', function()
+ meths.ui_pum_set_bounds(1.0, 1.0, 0.0, 1.5)
+ eq('Expected width > 0',
+ pcall_err(meths.ui_pum_set_bounds, 0.0, 1.0, 1.0, 0.0))
+ eq('Expected height > 0',
+ pcall_err(meths.ui_pum_set_bounds, 1.0, -1.0, 1.0, 0.0))
+ end)
+
+ it('an error occurs when ext_popupmenu is false', function()
+ meths.ui_pum_set_bounds(1.0, 1.0, 0.0, 1.5)
+ screen:set_option('ext_popupmenu', false)
+ eq('UI must support the ext_popupmenu option',
+ pcall_err(meths.ui_pum_set_bounds, 1.0, 1.0, 0.0, 1.5))
end)
end)
@@ -516,6 +573,7 @@ describe('ui/ext_popupmenu', function()
{1:~ }|
:sign ^ |
]])
+ eq(0, funcs.wildmenumode())
feed('<tab>')
screen:expect{grid=[[
@@ -530,6 +588,7 @@ describe('ui/ext_popupmenu', function()
{1:~ }|
:sign define^ |
]], popupmenu={items=wild_expected, pos=0, anchor={1, 9, 6}}}
+ eq(1, funcs.wildmenumode())
feed('<left>')
screen:expect{grid=[[
@@ -589,6 +648,7 @@ describe('ui/ext_popupmenu', function()
:sign unplace^ |
]], popupmenu={items=wild_expected, pos=5, anchor={1, 9, 6}}}
feed('<esc>')
+ eq(0, funcs.wildmenumode())
-- check positioning with multibyte char in pattern
command("e långfile1")
@@ -637,7 +697,7 @@ describe('builtin popupmenu', function()
})
end)
- it('works with preview-window above', function()
+ it('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>')
@@ -665,7 +725,7 @@ describe('builtin popupmenu', function()
]])
end)
- it('works with preview-window below', function()
+ it('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>')
@@ -693,7 +753,7 @@ describe('builtin popupmenu', function()
]])
end)
- it('works with preview-window above and tall and inverted', function()
+ it('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>')
@@ -723,7 +783,7 @@ describe('builtin popupmenu', function()
]])
end)
- it('works with preview-window above and short and inverted', function()
+ it('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>')
@@ -736,23 +796,23 @@ describe('builtin popupmenu', function()
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 } |
+ hh |
+ {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 }{s: } |
aa^ |
{4:[No Name] [+] }|
{2:-- }{5:match 1 of 10} |
]])
end)
- it('works with preview-window below and inverted', function()
+ it('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>')
@@ -781,7 +841,7 @@ describe('builtin popupmenu', function()
]])
end)
- it('works with vsplits', function()
+ it('with vsplits', function()
insert('aaa aab aac\n')
feed(':vsplit<cr>')
screen:expect([[
@@ -856,7 +916,7 @@ describe('builtin popupmenu', function()
]])
end)
- it('works with split and scroll', function()
+ it('with split and scroll', function()
screen:try_resize(60,14)
command("split")
command("set completeopt+=noinsert")
@@ -1153,10 +1213,10 @@ describe('builtin popupmenu', function()
funcs.complete(29, {'word', 'choice', 'text', 'thing'})
screen:expect([[
some long prefix before the ^ |
- {1:~ }{n: word }|
- {1:~ }{n: choice}|
- {1:~ }{n: text }|
- {1:~ }{n: thing }|
+ {n:word }{1: }|
+ {n:choice }{1: }|
+ {n:text }{1: }|
+ {n:thing }{1: }|
{1:~ }|
{1:~ }|
{1:~ }|
@@ -1201,10 +1261,10 @@ describe('builtin popupmenu', function()
feed('<c-p>')
screen:expect([[
some long prefix before the text|
- {1:^~ }{n: word }|
- {1:~ }{n: choice}|
- {1:~ }{s: text }|
- {1:~ }{n: thing }|
+ {n:^word }{1: }|
+ {n:choice }{1: }|
+ {s:text }{1: }|
+ {n:thing }{1: }|
{1:~ }|
{1:~ }|
{1:~ }|
@@ -1290,7 +1350,7 @@ describe('builtin popupmenu', function()
]])
end)
- it('behaves correcty with VimResized autocmd', function()
+ it('with VimResized autocmd', function()
feed('isome long prefix before the ')
command("set completeopt+=noinsert,noselect")
command("autocmd VimResized * redraw!")
@@ -1298,10 +1358,10 @@ describe('builtin popupmenu', function()
funcs.complete(29, {'word', 'choice', 'text', 'thing'})
screen:expect([[
some long prefix before the ^ |
- {1:~ }{n: word }|
- {1:~ }{n: choice}|
- {1:~ }{n: text }|
- {1:~ }{n: thing }|
+ {n:word }{1: }|
+ {n:choice }{1: }|
+ {n:text }{1: }|
+ {n:thing }{1: }|
{1:~ }|
{1:~ }|
{1:~ }|
@@ -1334,8 +1394,8 @@ describe('builtin popupmenu', function()
]])
end)
- it('works with rightleft window', function()
- command("set rl")
+ it('with rightleft window', function()
+ command("set rl wildoptions+=pum")
feed('isome rightleft ')
screen:expect([[
^ tfelthgir emos|
@@ -1432,9 +1492,58 @@ describe('builtin popupmenu', function()
{1: ~}|
{2:-- INSERT --} |
]])
+
+ -- not rightleft on the cmdline
+ feed('<esc>:sign ')
+ screen:expect{grid=[[
+ drow tfelthgir emos|
+ {1: ~}|
+ {1: ~}|
+ {1: ~}|
+ {1: ~}|
+ {1: ~}|
+ {1: ~}|
+ {1: ~}|
+ {1: ~}|
+ {1: ~}|
+ {1: ~}|
+ {1: ~}|
+ {1: ~}|
+ {1: ~}|
+ {1: ~}|
+ {1: ~}|
+ {1: ~}|
+ {1: ~}|
+ {1: ~}|
+ :sign ^ |
+ ]]}
+
+ feed('<tab>')
+ screen:expect{grid=[[
+ drow tfelthgir emos|
+ {1: ~}|
+ {1: ~}|
+ {1: ~}|
+ {1: ~}|
+ {1: ~}|
+ {1: ~}|
+ {1: ~}|
+ {1: ~}|
+ {1: ~}|
+ {1: ~}|
+ {1: ~}|
+ {1: ~}|
+ {1: }{s: define }{1: ~}|
+ {1: }{n: jump }{1: ~}|
+ {1: }{n: list }{1: ~}|
+ {1: }{n: place }{1: ~}|
+ {1: }{n: undefine }{1: ~}|
+ {1: }{n: unplace }{1: ~}|
+ :sign define^ |
+ ]]}
end)
- it('works with multiline messages', function()
+ it('with multiline messages', function()
screen:try_resize(40,8)
feed('ixx<cr>')
command('imap <f2> <cmd>echoerr "very"\\|echoerr "much"\\|echoerr "error"<cr>')
@@ -1488,20 +1597,20 @@ describe('builtin popupmenu', function()
command("split")
screen:expect([[
+ xx |
choice^ |
- {1:~ }|
{n:word }{1: }|
{s:choice }{4: }|
{n:text } |
- {n:thing }{1: }|
+ {n:thing } |
{3:[No Name] [+] }|
{2:-- INSERT --} |
]])
meths.input_mouse('wheel', 'down', '', 0, 6, 15)
screen:expect{grid=[[
+ xx |
choice^ |
- {1:~ }|
{n:word }{1: }|
{s:choice }{4: }|
{n:text } |
@@ -1511,7 +1620,7 @@ describe('builtin popupmenu', function()
]], unchanged=true}
end)
- it('works with kind, menu and abbr attributes', function()
+ it('with kind, menu and abbr attributes', function()
screen:try_resize(40,8)
feed('ixx ')
funcs.complete(4, {{word='wordey', kind= 'x', menu='extrainfo'}, 'thing', {word='secret', abbr='sneaky', menu='bar'}})
@@ -1563,7 +1672,7 @@ describe('builtin popupmenu', function()
]])
end)
- it('works with wildoptions=pum', function()
+ it('wildoptions=pum', function()
screen:try_resize(32,10)
command('set wildmenu')
command('set wildoptions=pum')
@@ -1735,7 +1844,7 @@ describe('builtin popupmenu', function()
]])
end)
- it('works with wildoptions=pum with scrolled mesages ', function()
+ it('wildoptions=pum with scrolled mesages ', function()
screen:try_resize(40,10)
command('set wildmenu')
command('set wildoptions=pum')
@@ -1783,6 +1892,39 @@ describe('builtin popupmenu', function()
]]}
end)
+ it('wildoptions=pum and wildmode=longest,full #11622', function()
+ screen:try_resize(30,8)
+ command('set wildmenu')
+ command('set wildoptions=pum')
+ command('set wildmode=longest,full')
+
+ feed(':sign u<tab>')
+ screen:expect{grid=[[
+ |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ :sign un^ |
+ ]]}
+ eq(0, funcs.wildmenumode())
+
+ feed('<tab>')
+ screen:expect{grid=[[
+ |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }{s: undefine }{1: }|
+ {1:~ }{n: unplace }{1: }|
+ :sign undefine^ |
+ ]]}
+ eq(1, funcs.wildmenumode())
+ end)
+
it("'pumblend' RGB-color", function()
screen:try_resize(60,14)
screen:set_default_attr_ids({
@@ -2016,4 +2158,42 @@ describe('builtin popupmenu', function()
{9:-- Keyword Local completion (^N^P) }{10:match 1 of 3} |
]])
end)
+
+ it("'pumheight'", function()
+ screen:try_resize(32,8)
+ feed('isome long prefix before the ')
+ command("set completeopt+=noinsert,noselect")
+ command("set linebreak")
+ command("set pumheight=2")
+ funcs.complete(29, {'word', 'choice', 'text', 'thing'})
+ screen:expect([[
+ some long prefix before the ^ |
+ {n:word }{c: }{1: }|
+ {n:choice }{s: }{1: }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {2:-- INSERT --} |
+ ]])
+ end)
+
+ it("'pumwidth'", function()
+ screen:try_resize(32,8)
+ feed('isome long prefix before the ')
+ command("set completeopt+=noinsert,noselect")
+ command("set linebreak")
+ command("set pumwidth=8")
+ funcs.complete(29, {'word', 'choice', 'text', 'thing'})
+ screen:expect([[
+ some long prefix before the ^ |
+ {n:word }{1: }|
+ {n:choice }{1: }|
+ {n:text }{1: }|
+ {n:thing }{1: }|
+ {1:~ }|
+ {1:~ }|
+ {2:-- INSERT --} |
+ ]])
+ end)
end)
diff --git a/test/functional/ui/screen.lua b/test/functional/ui/screen.lua
index 06a2ac3ca2..bf979e89f4 100644
--- a/test/functional/ui/screen.lua
+++ b/test/functional/ui/screen.lua
@@ -66,12 +66,12 @@
-- [1] = {reverse = true, bold = true},
-- [2] = {reverse = true}
-- })
--- screen:set_default_attr_ignore( {{}, {bold=true, foreground=NonText}} )
--
-- To help write screen tests, see Screen:snapshot_util().
-- To debug screen tests, see Screen:redraw_debug().
local helpers = require('test.functional.helpers')(nil)
+local busted = require('busted')
local deepcopy = helpers.deepcopy
local shallowcopy = helpers.shallowcopy
local concat_tables = helpers.concat_tables
@@ -158,6 +158,7 @@ function Screen.new(width, height)
wildmenu_items = nil,
wildmenu_selected = nil,
win_position = {},
+ win_viewport = {},
float_pos = {},
msg_grid = nil,
msg_grid_pos = nil,
@@ -169,12 +170,11 @@ function Screen.new(width, height)
ruler = {},
hl_groups = {},
_default_attr_ids = nil,
- _default_attr_ignore = nil,
_mouse_enabled = true,
_attrs = {},
- _hl_info = {},
+ _hl_info = {[0]={}},
_attr_table = {[0]={{},{}}},
- _clear_attrs = {},
+ _clear_attrs = nil,
_new_attrs = false,
_width = width,
_height = height,
@@ -202,12 +202,8 @@ function Screen:get_default_attr_ids()
return deepcopy(self._default_attr_ids)
end
-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
+function Screen:set_rgb_cterm(val)
+ self._rgb_cterm = val
end
function Screen:attach(options, session)
@@ -223,7 +219,7 @@ function Screen:attach(options, session)
self._session = session
self._options = options
- self._clear_attrs = (options.ext_linegrid and {{},{}}) or {}
+ self._clear_attrs = (not options.ext_linegrid) and {} or nil
self:_handle_resize(self._width, self._height)
self.uimeths.attach(self._width, self._height, options)
if self._options.rgb == nil then
@@ -259,13 +255,13 @@ end
-- canonical order of ext keys, used to generate asserts
local ext_keys = {
'popupmenu', 'cmdline', 'cmdline_block', 'wildmenu_items', 'wildmenu_pos',
- 'messages', 'showmode', 'showcmd', 'ruler', 'float_pos',
+ 'messages', 'showmode', 'showcmd', 'ruler', 'float_pos', 'win_viewport'
}
-- Asserts that the screen state eventually matches an expected state.
--
-- Can be called with positional args:
--- screen:expect(grid, [attr_ids, attr_ignore])
+-- screen:expect(grid, [attr_ids])
-- screen:expect(condition)
-- or keyword args (supports more options):
-- screen:expect{grid=[[...]], cmdline={...}, condition=function() ... end}
@@ -274,7 +270,7 @@ local ext_keys = {
-- grid: Expected screen state (string). Each line represents a screen
-- row. Last character of each row (typically "|") is stripped.
-- Common indentation is stripped.
--- Lines containing only "{IGNORE}|" are skipped.
+-- "{MATCH:x}|" lines are matched against Lua pattern `x`.
-- 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
@@ -282,8 +278,6 @@ local ext_keys = {
-- attributes in the final state are an error.
-- 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
@@ -318,13 +312,13 @@ local ext_keys = {
-- 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, ...)
+function Screen:expect(expected, attr_ids, ...)
local grid, condition = nil, nil
local expected_rows = {}
assert(next({...}) == nil, "invalid args to expect()")
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,
+ assert(not (attr_ids ~= nil))
+ local is_key = {grid=true, attr_ids=true, condition=true,
any=true, mode=true, unchanged=true, intermediate=true,
reset=true, timeout=true, request_cb=true, hl_groups=true}
for _, v in ipairs(ext_keys) do
@@ -337,14 +331,13 @@ function Screen:expect(expected, attr_ids, attr_ignore, ...)
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))
+ assert(not (attr_ids ~= nil))
condition = expected
expected = {}
else
@@ -361,10 +354,9 @@ function Screen:expect(expected, attr_ids, attr_ignore, ...)
end
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 {})
+ if self._options.ext_linegrid then
+ attr_state.id_to_index = self:linegrid_check_attrs(attr_state.ids or {})
end
self._new_attrs = false
self:_wait(function()
@@ -375,8 +367,8 @@ function Screen:expect(expected, attr_ids, attr_ignore, ...)
end
end
- if self._options.ext_hlstate and self._new_attrs then
- attr_state.id_to_index = self:hlstate_check_attrs(attr_state.ids or {})
+ if self._options.ext_linegrid and self._new_attrs then
+ attr_state.id_to_index = self:linegrid_check_attrs(attr_state.ids or {})
end
local actual_rows = self:render(not expected.any, attr_state)
@@ -399,9 +391,10 @@ function Screen:expect(expected, attr_ids, attr_ignore, ...)
err_msg = "Expected screen height " .. #expected_rows
.. ' differs from actual height ' .. #actual_rows .. '.'
end
- for i = 1, #expected_rows do
- msg_expected_rows[i] = expected_rows[i]
- if expected_rows[i] ~= actual_rows[i] and expected_rows[i] ~= "{IGNORE}|" then
+ for i, row in ipairs(expected_rows) do
+ msg_expected_rows[i] = row
+ local m = (row ~= actual_rows[i] and row:match('{MATCH:(.*)}') or nil)
+ if row ~= actual_rows[i] and (not m or not actual_rows[i]:match(m)) then
msg_expected_rows[i] = '*' .. msg_expected_rows[i]
if i <= #actual_rows then
actual_rows[i] = '*' .. actual_rows[i]
@@ -429,6 +422,9 @@ screen:redraw_debug() to show all intermediate screen states. ]])
if expected.mode ~= nil then
extstate.mode = self.mode
end
+ if expected.win_viewport == nil then
+ extstate.win_viewport = nil
+ end
-- Convert assertion errors into invalid screen state descriptions.
for _, k in ipairs(concat_tables(ext_keys, {'mode'})) do
@@ -584,7 +580,7 @@ asynchronous (feed(), nvim_input()) and synchronous API calls.
if err then
- assert(false, err)
+ busted.fail(err, 3)
elseif did_warn then
local tb = debug.traceback()
local index = string.find(tb, '\n%s*%[C]')
@@ -614,17 +610,12 @@ function Screen:_redraw(updates)
for i = 2, #update do
local handler_name = '_handle_'..method
local handler = self[handler_name]
- if handler ~= nil then
- local status, res = pcall(handler, self, unpack(update[i]))
- if not status then
- error(handler_name..' failed'
- ..'\n payload: '..inspect(update)
- ..'\n error: '..tostring(res))
- end
- else
- assert(self._on_event,
- "Add Screen:"..handler_name.." or call Screen:set_on_event_handler")
- self._on_event(method, update[i])
+ assert(handler ~= nil, "missing handler: Screen:"..handler_name)
+ local status, res = pcall(handler, self, unpack(update[i]))
+ if not status then
+ error(handler_name..' failed'
+ ..'\n payload: '..inspect(update)
+ ..'\n error: '..tostring(res))
end
end
if k == #updates and method == "flush" then
@@ -634,10 +625,6 @@ function Screen:_redraw(updates)
return did_flush
end
-function Screen:set_on_event_handler(callback)
- self._on_event = callback
-end
-
function Screen:_handle_resize(width, height)
self:_handle_grid_resize(1, width, height)
self._scroll_region = {
@@ -743,6 +730,7 @@ function Screen:_handle_grid_destroy(grid)
self._grids[grid] = nil
if self._options.ext_multigrid then
self.win_position[grid] = nil
+ self.win_viewport[grid] = nil
end
end
@@ -763,14 +751,24 @@ function Screen:_handle_grid_cursor_goto(grid, row, col)
end
function Screen:_handle_win_pos(grid, win, startrow, startcol, width, height)
- self.win_position[grid] = {
- win = win,
- startrow = startrow,
- startcol = startcol,
- width = width,
- height = height
- }
- self.float_pos[grid] = nil
+ self.win_position[grid] = {
+ win = win,
+ startrow = startrow,
+ startcol = startcol,
+ width = width,
+ height = height
+ }
+ self.float_pos[grid] = nil
+end
+
+function Screen:_handle_win_viewport(grid, win, topline, botline, curline, curcol)
+ self.win_viewport[grid] = {
+ win = win,
+ topline = topline,
+ botline = botline,
+ curline = curline,
+ curcol = curcol
+ }
end
function Screen:_handle_win_float_pos(grid, ...)
@@ -898,19 +896,16 @@ function Screen:_handle_grid_line(grid, row, col, items)
assert(self._options.ext_linegrid)
local line = self._grids[grid].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
@@ -1070,6 +1065,7 @@ function Screen:_clear_row_section(grid, rownum, startcol, stopcol, invalid)
for i = startcol, stopcol do
row[i].text = (invalid and '�' or ' ')
row[i].attrs = self._clear_attrs
+ row[i].hl_id = 0
end
end
@@ -1100,11 +1096,7 @@ function Screen:_row_repr(gridnr, rownr, attr_state, cursor)
end
if not did_window then
- 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)
+ local attr_id = self:_get_attr_id(attr_state, row[i].attrs, row[i].hl_id)
if current_attr_id and attr_id ~= current_attr_id then
-- close current attribute bracket
table.insert(rv, '}')
@@ -1153,6 +1145,8 @@ function Screen:_extstate_repr(attr_state)
messages[i] = {kind=entry[1], content=self:_chunks_repr(entry[2], attr_state)}
end
+ local win_viewport = (next(self.win_viewport) and self.win_viewport) or nil
+
return {
popupmenu=self.popupmenu,
cmdline=cmdline,
@@ -1164,7 +1158,8 @@ function Screen:_extstate_repr(attr_state)
showcmd=self:_chunks_repr(self.showcmd, attr_state),
ruler=self:_chunks_repr(self.ruler, attr_state),
msg_history=msg_history,
- float_pos=self.float_pos
+ float_pos=self.float_pos,
+ win_viewport=win_viewport,
}
end
@@ -1239,10 +1234,6 @@ function Screen:render(headers, attr_state, preview)
return rv
end
-local remove_all_metatables = function(item, path)
- if path[#path] ~= inspect.METATABLE then return item end
-end
-
-- Returns the current screen state in the form of a screen:expect()
-- keyword-args map.
function Screen:get_snapshot(attrs, ignore)
@@ -1261,8 +1252,8 @@ function Screen:get_snapshot(attrs, ignore)
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)
+ if self._options.ext_linegrid then
+ attr_state.id_to_index = self:linegrid_check_attrs(attr_state.ids)
end
local lines = self:render(true, attr_state, true)
@@ -1292,6 +1283,26 @@ function Screen:get_snapshot(attrs, ignore)
return kwargs, ext_state, attr_state
end
+local function fmt_ext_state(name, state)
+ if name == "win_viewport" then
+ local str = "{\n"
+ for k,v in pairs(state) do
+ str = (str.." ["..k.."] = {win = {id = "..v.win.id.."}, topline = "
+ ..v.topline..", botline = "..v.botline..", curline = "..v.curline
+ ..", curcol = "..v.curcol.."},\n")
+ end
+ return str .. "}"
+ else
+ -- TODO(bfredl): improve formatting of more states
+ local function remove_all_metatables(item, path)
+ if path[#path] ~= inspect.METATABLE then
+ return item
+ end
+ end
+ return inspect(state,{process=remove_all_metatables})
+ end
+end
+
function Screen:print_snapshot(attrs, ignore)
local kwargs, ext_state, attr_state = self:get_snapshot(attrs, ignore)
local attrstr = ""
@@ -1299,8 +1310,8 @@ function Screen:print_snapshot(attrs, ignore)
local attrstrs = {}
for i, a in pairs(attr_state.ids) do
local dict
- if self._options.ext_hlstate then
- dict = self:_pprint_hlstate(a)
+ if self._options.ext_linegrid then
+ dict = self:_pprint_hlitem(a)
else
dict = "{"..self:_pprint_attrs(a).."}"
end
@@ -1314,9 +1325,8 @@ function Screen:print_snapshot(attrs, ignore)
print(kwargs.grid)
io.stdout:write( "]]"..attrstr)
for _, k in ipairs(ext_keys) do
- if ext_state[k] ~= nil then
- -- TODO(bfredl): improve formatting
- io.stdout:write(", "..k.."="..inspect(ext_state[k],{process=remove_all_metatables}))
+ if ext_state[k] ~= nil and not (k == "win_viewport" and not self.options.ext_multigrid) then
+ io.stdout:write(", "..k.."="..fmt_ext_state(k, ext_state[k]))
end
end
print("}\n")
@@ -1328,37 +1338,41 @@ function Screen:_insert_hl_id(attr_state, hl_id)
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
+ local info = nil
+ if self._options.ext_hlstate then
+ 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
end
local entry = self._attr_table[hl_id]
local attrval
- if self._hlstate_cterm then
+ if self._rgb_cterm then
attrval = {entry[1], entry[2], info} -- unpack() doesn't work
- else
+ elseif self._options.ext_hlstate then
attrval = {entry[1], info}
+ else
+ attrval = self._options.rgb and entry[1] or entry[2]
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)
+function Screen:linegrid_check_attrs(attrs)
local id_to_index = {}
- for i = 1,#self._attr_table do
+ for i, def_attr in pairs(self._attr_table) do
local iinfo = self._hl_info[i]
local matchinfo = {}
if #iinfo > 1 then
@@ -1370,13 +1384,17 @@ function Screen:hlstate_check_attrs(attrs)
end
for k,v in pairs(attrs) do
local attr, info, attr_rgb, attr_cterm
- if self._hlstate_cterm then
+ if self._rgb_cterm then
attr_rgb, attr_cterm, info = unpack(v)
attr = {attr_rgb, attr_cterm}
- else
+ info = info or {}
+ elseif self._options.ext_hlstate then
attr, info = unpack(v)
+ else
+ attr = v
+ info = {}
end
- if self:_equal_attr_def(attr, self._attr_table[i]) then
+ if self:_equal_attr_def(attr, def_attr) then
if #info == #matchinfo then
local match = false
if #info == 1 then
@@ -1397,24 +1415,32 @@ function Screen:hlstate_check_attrs(attrs)
end
end
end
+ if self:_equal_attr_def(self._rgb_cterm and {{}, {}} or {}, def_attr) and #self._hl_info[i] == 0 then
+ id_to_index[i] = ""
+ end
end
return id_to_index
end
-function Screen:_pprint_hlstate(item)
+function Screen:_pprint_hlitem(item)
-- print(inspect(item))
- local attrdict = "{"..self:_pprint_attrs(item[1]).."}, "
+ local multi = self._rgb_cterm or self._options.ext_hlstate
+ local cterm = (not self._rgb_cterm and not self._options.rgb)
+ local attrdict = "{"..self:_pprint_attrs(multi and item[1] or item, cterm).."}"
local attrdict2, hlinfo
- if self._hlstate_cterm then
- attrdict2 = "{"..self:_pprint_attrs(item[2]).."}, "
+ local descdict = ""
+ if self._rgb_cterm then
+ attrdict2 = ", {"..self:_pprint_attrs(item[2], true).."}"
hlinfo = item[3]
else
attrdict2 = ""
hlinfo = item[2]
end
- local descdict = "{"..self:_pprint_hlinfo(hlinfo).."}"
- return "{"..attrdict..attrdict2..descdict.."}"
+ if self._options.ext_hlstate then
+ descdict = ", {"..self:_pprint_hlinfo(hlinfo).."}"
+ end
+ return (multi and "{" or "")..attrdict..attrdict2..descdict..(multi and "}" or "")
end
function Screen:_pprint_hlinfo(states)
@@ -1434,13 +1460,15 @@ function Screen:_pprint_hlinfo(states)
end
-function Screen:_pprint_attrs(attrs)
+function Screen:_pprint_attrs(attrs, cterm)
local items = {}
for f, v in pairs(attrs) do
local desc = tostring(v)
if f == "foreground" or f == "background" or f == "special" then
if Screen.colornames[v] ~= nil then
desc = "Screen.colors."..Screen.colornames[v]
+ elseif cterm then
+ desc = tostring(v)
else
desc = string.format("tonumber('0x%06x')",v)
end
@@ -1464,9 +1492,11 @@ function Screen:_get_attr_id(attr_state, attrs, hl_id)
return
end
- if self._options.ext_hlstate then
+ if self._options.ext_linegrid then
local id = attr_state.id_to_index[hl_id]
- if id ~= nil or hl_id == 0 then
+ if id == "" then -- sentinel for empty it
+ return nil
+ elseif id ~= nil then
return id
end
if attr_state.mutable then
@@ -1476,9 +1506,7 @@ function Screen:_get_attr_id(attr_state, attrs, hl_id)
end
return "UNEXPECTED "..self:_pprint_attrs(self._attr_table[hl_id][1])
else
- if self:_equal_attrs(attrs, {}) or
- attr_state.ignore == true or
- self:_attr_index(attr_state.ignore, attrs) ~= nil then
+ if self:_equal_attrs(attrs, {}) then
-- ignore this attrs
return nil
end
@@ -1497,10 +1525,12 @@ function Screen:_get_attr_id(attr_state, attrs, hl_id)
end
function Screen:_equal_attr_def(a, b)
- if self._hlstate_cterm then
+ if self._rgb_cterm then
return self:_equal_attrs(a[1],b[1]) and self:_equal_attrs(a[2],b[2])
- else
+ elseif self._options.rgb then
return self:_equal_attrs(a,b[1])
+ else
+ return self:_equal_attrs(a,b[2])
end
end
@@ -1510,7 +1540,8 @@ function Screen:_equal_attrs(a, b)
a.italic == b.italic and a.reverse == b.reverse and
a.foreground == b.foreground and a.background == b.background and
a.special == b.special and a.blend == b.blend and
- a.strikethrough == b.strikethrough
+ a.strikethrough == b.strikethrough and
+ a.fg_indexed == b.fg_indexed and a.bg_indexed == b.bg_indexed
end
function Screen:_equal_info(a, b)
diff --git a/test/functional/ui/screen_basic_spec.lua b/test/functional/ui/screen_basic_spec.lua
index 46f0b5060c..ff9f30d0a1 100644
--- a/test/functional/ui/screen_basic_spec.lua
+++ b/test/functional/ui/screen_basic_spec.lua
@@ -915,6 +915,7 @@ local function screen_tests(linegrid)
-- Regression test for #8357
it('does not have artifacts after temporary chars in insert mode', function()
+ command('set timeoutlen=10000')
command('inoremap jk <esc>')
feed('ifooj')
screen:expect([[
@@ -986,7 +987,7 @@ describe('Screen default colors', function()
it('can be set to light', function()
startup(true, false)
screen:expect{condition=function()
- eq({rgb_bg=Screen.colors.White, rgb_fg=0, rgb_sp=Screen.colors.Red,
+ eq({rgb_fg=Screen.colors.White, rgb_bg=0, rgb_sp=Screen.colors.Red,
cterm_bg=0, cterm_fg=0}, screen.default_colors)
end}
end)
diff --git a/test/functional/ui/searchhl_spec.lua b/test/functional/ui/searchhl_spec.lua
index 486de02a09..635ce7392b 100644
--- a/test/functional/ui/searchhl_spec.lua
+++ b/test/functional/ui/searchhl_spec.lua
@@ -442,7 +442,7 @@ describe('search highlighting', function()
feed_command("call matchadd('MyGroup', 'special')")
feed_command("call matchadd('MyGroup2', 'text', 0)")
- -- searchhl and matchadd matches are exclusive, only the higest priority
+ -- searchhl and matchadd matches are exclusive, only the highest priority
-- is used (and matches with lower priorities are not combined)
feed_command("/ial te")
screen:expect([[
diff --git a/test/functional/ui/sign_spec.lua b/test/functional/ui/sign_spec.lua
index 68e675b8e5..0ed62b21b2 100644
--- a/test/functional/ui/sign_spec.lua
+++ b/test/functional/ui/sign_spec.lua
@@ -26,10 +26,6 @@ describe('Signs', function()
} )
end)
- after_each(function()
- screen:detach()
- end)
-
describe(':sign place', function()
it('allows signs with combining characters', function()
feed('ia<cr>b<cr><esc>')
diff --git a/test/functional/ui/spell_spec.lua b/test/functional/ui/spell_spec.lua
index 913f1b9bed..2c6e586665 100644
--- a/test/functional/ui/spell_spec.lua
+++ b/test/functional/ui/spell_spec.lua
@@ -4,8 +4,9 @@ local helpers = require('test.functional.helpers')(after_each)
local Screen = require('test.functional.ui.screen')
local clear = helpers.clear
local feed = helpers.feed
-local feed_command = helpers.feed_command
local insert = helpers.insert
+local uname = helpers.uname
+local command = helpers.command
describe("'spell'", function()
local screen
@@ -16,16 +17,14 @@ describe("'spell'", function()
screen:attach()
screen:set_default_attr_ids( {
[0] = {bold=true, foreground=Screen.colors.Blue},
- [1] = {special = Screen.colors.Red, undercurl = true}
+ [1] = {special = Screen.colors.Red, undercurl = true},
+ [2] = {special = Screen.colors.Blue1, undercurl = true},
})
end)
- after_each(function()
- screen:detach()
- end)
-
it('joins long lines #7937', function()
- feed_command('set spell')
+ if uname() == 'openbsd' then pending('FIXME #12104', function() end) return end
+ command('set spell')
insert([[
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod
tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam,
@@ -46,4 +45,26 @@ describe("'spell'", function()
|
]])
end)
+
+ it('has correct highlight at start of line', function()
+ insert([[
+ "This is some text without any spell errors. Everything",
+ "should just be black, nothing wrong here.",
+ "",
+ "This line has a sepll error. and missing caps.",
+ "And and this is the the duplication.",
+ "with missing caps here.",
+ ]])
+ command('set spell spelllang=en_nz')
+ screen:expect([[
+ "This is some text without any spell errors. Everything", |
+ "should just be black, nothing wrong here.", |
+ "", |
+ "This line has a {1:sepll} error. {2:and} missing caps.", |
+ "{1:And and} this is {1:the the} duplication.", |
+ "with missing caps here.", |
+ ^ |
+ |
+ ]])
+ end)
end)
diff --git a/test/functional/ui/syntax_conceal_spec.lua b/test/functional/ui/syntax_conceal_spec.lua
index 00e94ef94b..d1af0e955c 100644
--- a/test/functional/ui/syntax_conceal_spec.lua
+++ b/test/functional/ui/syntax_conceal_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 eq = helpers.eq
local insert = helpers.insert
describe('Screen', function()
@@ -17,13 +18,10 @@ describe('Screen', function()
[3] = {reverse = true},
[4] = {bold = true},
[5] = {background = Screen.colors.Yellow},
+ [6] = {background = Screen.colors.LightGrey},
} )
end)
- after_each(function()
- screen:detach()
- end)
-
describe("match and conceal", function()
before_each(function()
@@ -823,5 +821,96 @@ describe('Screen', function()
]])
end)
end)
+
+ it('redraws properly with concealcursor in visual mode', function()
+ command('set concealcursor=v conceallevel=2')
+
+ feed('10Ofoo barf bar barf eggs<esc>')
+ feed(':3<cr>o a<Esc>ggV')
+ screen:expect{grid=[[
+ ^f{6:oo }{1:b}{6: bar }{1:b}{6: eggs} |
+ foo {1:b} bar {1:b} eggs |
+ foo {1:b} bar {1:b} eggs |
+ a |
+ foo {1:b} bar {1:b} eggs |
+ foo {1:b} bar {1:b} eggs |
+ foo {1:b} bar {1:b} eggs |
+ foo {1:b} bar {1:b} eggs |
+ foo {1:b} bar {1:b} eggs |
+ {4:-- VISUAL LINE --} |
+ ]]}
+ feed(string.rep('j', 15))
+ screen:expect{grid=[[
+ {6:foo }{1:b}{6: bar }{1:b}{6: eggs} |
+ {6:foo }{1:b}{6: bar }{1:b}{6: eggs} |
+ {6:foo }{1:b}{6: bar }{1:b}{6: eggs} |
+ {6:foo }{1:b}{6: bar }{1:b}{6: eggs} |
+ {6:foo }{1:b}{6: bar }{1:b}{6: eggs} |
+ {6:foo }{1:b}{6: bar }{1:b}{6: eggs} |
+ {6:foo }{1:b}{6: bar }{1:b}{6: eggs} |
+ {6:foo }{1:b}{6: bar }{1:b}{6: eggs} |
+ ^f{6:oo }{1:b}{6: bar }{1:b}{6: eggs} |
+ {4:-- VISUAL LINE --} |
+ ]]}
+ feed(string.rep('k', 15))
+ screen:expect{grid=[[
+ ^f{6:oo }{1:b}{6: bar }{1:b}{6: eggs} |
+ foo {1:b} bar {1:b} eggs |
+ foo {1:b} bar {1:b} eggs |
+ a |
+ foo {1:b} bar {1:b} eggs |
+ foo {1:b} bar {1:b} eggs |
+ foo {1:b} bar {1:b} eggs |
+ foo {1:b} bar {1:b} eggs |
+ foo {1:b} bar {1:b} eggs |
+ {4:-- VISUAL LINE --} |
+ ]]}
+ end)
+ end)
+
+ it('redraws not too much with conceallevel=1', function()
+ command('set conceallevel=1')
+ command('set redrawdebug+=nodelta')
+
+ insert([[
+ aaa
+ bbb
+ ccc
+ ]])
+ screen:expect{grid=[[
+ aaa |
+ bbb |
+ ccc |
+ ^ |
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ |
+ ]]}
+
+ -- XXX: hack to get notifications, and check only a single line is
+ -- updated. Could use next_msg() also.
+ local orig_handle_grid_line = screen._handle_grid_line
+ local grid_lines = {}
+ function screen._handle_grid_line(self, grid, row, col, items)
+ table.insert(grid_lines, {row, col, items})
+ orig_handle_grid_line(self, grid, row, col, items)
+ end
+ feed('k')
+ screen:expect{grid=[[
+ aaa |
+ bbb |
+ ^ccc |
+ |
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ |
+ ]]}
+ eq(grid_lines, {{2, 0, {{'c', 0, 3}}}})
end)
end)
diff --git a/test/functional/ui/tabline_spec.lua b/test/functional/ui/tabline_spec.lua
index dcab9f7ef4..23aae81745 100644
--- a/test/functional/ui/tabline_spec.lua
+++ b/test/functional/ui/tabline_spec.lua
@@ -10,15 +10,9 @@ describe('ui/ext_tabline', function()
clear()
screen = Screen.new(25, 5)
screen:attach({rgb=true, ext_tabline=true})
- screen:set_on_event_handler(function(name, data)
- if name == "tabline_update" then
- event_curtab, event_tabs = unpack(data)
- end
- end)
- end)
-
- after_each(function()
- screen:detach()
+ function screen:_handle_tabline_update(curtab, tabs)
+ event_curtab, event_tabs = curtab, tabs
+ end
end)
it('publishes UI events', function()
diff --git a/test/functional/ui/wildmode_spec.lua b/test/functional/ui/wildmode_spec.lua
index f3fa711fb1..99ebc4971e 100644
--- a/test/functional/ui/wildmode_spec.lua
+++ b/test/functional/ui/wildmode_spec.lua
@@ -16,6 +16,44 @@ describe("'wildmenu'", function()
screen:attach()
end)
+ it('C-E to cancel wildmenu completion restore original input', function()
+ feed(':sign <tab>')
+ screen:expect([[
+ |
+ ~ |
+ ~ |
+ define jump list > |
+ :sign define^ |
+ ]])
+ feed('<C-E>')
+ screen:expect([[
+ |
+ ~ |
+ ~ |
+ ~ |
+ :sign ^ |
+ ]])
+ end)
+
+ it('C-Y to apply selection and end wildmenu completion', function()
+ feed(':sign <tab>')
+ screen:expect([[
+ |
+ ~ |
+ ~ |
+ define jump list > |
+ :sign define^ |
+ ]])
+ feed('<tab><C-Y>')
+ screen:expect([[
+ |
+ ~ |
+ ~ |
+ ~ |
+ :sign jump^ |
+ ]])
+ end)
+
it(':sign <tab> shows wildmenu completions', function()
command('set wildmenu wildmode=full')
feed(':sign <tab>')
@@ -221,6 +259,106 @@ describe("'wildmenu'", function()
]])
end)
+ it('wildmode=longest,list', function()
+ -- Need more than 5 rows, else tabline is covered and will be redrawn.
+ screen:try_resize(25, 7)
+
+ command('set wildmenu wildmode=longest,list')
+
+ -- give wildmode-longest something to expand to
+ feed(':sign u<tab>')
+ screen:expect([[
+ |
+ ~ |
+ ~ |
+ ~ |
+ ~ |
+ ~ |
+ :sign un^ |
+ ]])
+ feed('<tab>') -- trigger wildmode list
+ screen:expect([[
+ |
+ ~ |
+ ~ |
+ |
+ :sign un |
+ undefine unplace |
+ :sign un^ |
+ ]])
+ feed('<Esc>')
+ screen:expect([[
+ ^ |
+ ~ |
+ ~ |
+ ~ |
+ ~ |
+ ~ |
+ |
+ ]])
+
+ -- give wildmode-longest something it cannot expand, use list
+ feed(':sign un<tab>')
+ screen:expect([[
+ |
+ ~ |
+ ~ |
+ |
+ :sign un |
+ undefine unplace |
+ :sign un^ |
+ ]])
+ feed('<tab>')
+ screen:expect_unchanged()
+ feed('<Esc>')
+ screen:expect([[
+ ^ |
+ ~ |
+ ~ |
+ ~ |
+ ~ |
+ ~ |
+ |
+ ]])
+ end)
+
+ it('wildmode=list,longest', function()
+ -- Need more than 5 rows, else tabline is covered and will be redrawn.
+ screen:try_resize(25, 7)
+
+ command('set wildmenu wildmode=list,longest')
+ feed(':sign u<tab>')
+ screen:expect([[
+ |
+ ~ |
+ ~ |
+ |
+ :sign u |
+ undefine unplace |
+ :sign u^ |
+ ]])
+ feed('<tab>') -- trigger wildmode longest
+ screen:expect([[
+ |
+ ~ |
+ ~ |
+ |
+ :sign u |
+ undefine unplace |
+ :sign un^ |
+ ]])
+ feed('<Esc>')
+ screen:expect([[
+ ^ |
+ ~ |
+ ~ |
+ ~ |
+ ~ |
+ ~ |
+ |
+ ]])
+ end)
+
it('multiple <C-D> renders correctly', function()
screen:try_resize(25, 7)
diff --git a/test/helpers.lua b/test/helpers.lua
index ebc0a7d811..40b93d9935 100644
--- a/test/helpers.lua
+++ b/test/helpers.lua
@@ -55,17 +55,32 @@ local check_logs_useless_lines = {
['See README_MISSING_SYSCALL_OR_IOCTL for guidance']=3,
}
-function module.eq(expected, actual, context)
- return assert.are.same(expected, actual, context)
+--- Invokes `fn` and includes the tail of `logfile` in the error message if it
+--- fails.
+---
+--@param logfile Log file, defaults to $NVIM_LOG_FILE or '.nvimlog'
+--@param fn Function to invoke
+--@param ... Function arguments
+local function dumplog(logfile, fn, ...)
+ -- module.validate({
+ -- logfile={logfile,'s',true},
+ -- fn={fn,'f',false},
+ -- })
+ local status, rv = pcall(fn, ...)
+ if status == false then
+ logfile = logfile or os.getenv('NVIM_LOG_FILE') or '.nvimlog'
+ local logtail = module.read_nvim_log(logfile)
+ error(string.format('%s\n%s', rv, logtail))
+ end
end
-function module.neq(expected, actual, context)
- return assert.are_not.same(expected, actual, context)
+function module.eq(expected, actual, context, logfile)
+ return dumplog(logfile, assert.are.same, expected, actual, context)
end
-function module.ok(res, msg)
- return assert.is_true(res, msg)
+function module.neq(expected, actual, context, logfile)
+ return dumplog(logfile, assert.are_not.same, expected, actual, context)
end
-function module.near(actual, expected, tolerance)
- return assert.is.near(actual, expected, tolerance)
+function module.ok(res, msg, logfile)
+ return dumplog(logfile, assert.is_true, res, msg)
end
function module.matches(pat, actual)
if nil ~= string.match(actual, pat) then
@@ -74,7 +89,24 @@ function module.matches(pat, actual)
error(string.format('Pattern does not match.\nPattern:\n%s\nActual:\n%s', pat, actual))
end
--- Invokes `fn` and returns the error string, or raises an error if `fn` succeeds.
+--- Asserts that `pat` matches one or more lines in the tail of $NVIM_LOG_FILE.
+---
+--@param pat (string) Lua pattern to search for in the log file.
+--@param logfile (string, default=$NVIM_LOG_FILE) full path to log file.
+function module.assert_log(pat, logfile)
+ logfile = logfile or os.getenv('NVIM_LOG_FILE') or '.nvimlog'
+ local nrlines = 10
+ local lines = module.read_file_list(logfile, -nrlines) or {}
+ for _,line in ipairs(lines) do
+ if line:match(pat) then return end
+ end
+ local logtail = module.read_nvim_log(logfile)
+ error(string.format('Pattern %q not found in log (last %d lines): %s:\n%s',
+ pat, nrlines, logfile, logtail))
+end
+
+-- Invokes `fn` and returns the error string (may truncate full paths), or
+-- raises an error if `fn` succeeds.
--
-- Usage:
-- -- Match exact string.
@@ -88,7 +120,20 @@ function module.pcall_err(fn, ...)
if status == true then
error('expected failure, but got success')
end
+ -- From this:
+ -- /home/foo/neovim/runtime/lua/vim/shared.lua:186: Expected string, got number
+ -- to this:
+ -- Expected string, got number
local errmsg = tostring(rv):gsub('^[^:]+:%d+: ', '')
+ -- From this:
+ -- Error executing lua: /very/long/foo.lua:186: Expected string, got number
+ -- to this:
+ -- Error executing lua: .../foo.lua:186: Expected string, got number
+ errmsg = errmsg:gsub([[lua: [a-zA-Z]?:?[^:]-[/\]([^:/\]+):%d+: ]], 'lua: .../%1: ')
+ -- Compiled modules will not have a path and will just be a name like
+ -- shared.lua:186, so strip the number.
+ errmsg = errmsg:gsub([[lua: ([^:/\ ]+):%d+: ]], 'lua: .../%1: ')
+ -- ^ Windows drive-letter (C:)
return errmsg
end
@@ -245,24 +290,6 @@ module.tmpname = (function()
end)
end)()
-function module.map(func, tab)
- local rettab = {}
- for k, v in pairs(tab) do
- rettab[k] = func(v)
- end
- return rettab
-end
-
-function module.filter(filter_func, tab)
- local rettab = {}
- for _, entry in pairs(tab) do
- if filter_func(entry) then
- table.insert(rettab, entry)
- end
- end
- return rettab
-end
-
function module.hasenv(name)
local env = os.getenv(name)
if env and env ~= '' then
@@ -715,17 +742,18 @@ end
function module.isCI(name)
local any = (name == nil)
- assert(any or name == 'appveyor' or name == 'quickbuild' or name == 'travis')
+ assert(any or name == 'appveyor' or name == 'travis' or name == 'sourcehut')
local av = ((any or name == 'appveyor') and nil ~= os.getenv('APPVEYOR'))
local tr = ((any or name == 'travis') and nil ~= os.getenv('TRAVIS'))
- local qb = ((any or name == 'quickbuild') and nil ~= lfs.attributes('/usr/home/quickbuild'))
- return tr or av or qb
+ local sh = ((any or name == 'sourcehut') and nil ~= os.getenv('SOURCEHUT'))
+ return tr or av or sh
+
end
--- Gets the contents of $NVIM_LOG_FILE for printing to the build log.
+-- Gets the (tail) contents of `logfile`.
-- Also moves the file to "${NVIM_LOG_FILE}.displayed" on CI environments.
-function module.read_nvim_log()
- local logfile = os.getenv('NVIM_LOG_FILE') or '.nvimlog'
+function module.read_nvim_log(logfile, ci_rename)
+ logfile = logfile or os.getenv('NVIM_LOG_FILE') or '.nvimlog'
local is_ci = module.isCI()
local keep = is_ci and 999 or 10
local lines = module.read_file_list(logfile, -keep) or {}
@@ -736,7 +764,7 @@ function module.read_nvim_log()
log = log..line..'\n'
end
log = log..('-'):rep(78)..'\n'
- if is_ci then
+ if is_ci and ci_rename then
os.rename(logfile, logfile .. '.displayed')
end
return log
diff --git a/test/unit/eval/helpers.lua b/test/unit/eval/helpers.lua
index 3d1c42c3a0..b600f01ab2 100644
--- a/test/unit/eval/helpers.lua
+++ b/test/unit/eval/helpers.lua
@@ -136,11 +136,15 @@ local function typvalt2lua_tab_init()
return
end
typvalt2lua_tab = {
+ [tonumber(eval.VAR_BOOL)] = function(t)
+ return ({
+ [tonumber(eval.kBoolVarFalse)] = false,
+ [tonumber(eval.kBoolVarTrue)] = true,
+ })[tonumber(t.vval.v_bool)]
+ end,
[tonumber(eval.VAR_SPECIAL)] = function(t)
return ({
- [tonumber(eval.kSpecialVarFalse)] = false,
[tonumber(eval.kSpecialVarNull)] = nil_value,
- [tonumber(eval.kSpecialVarTrue)] = true,
})[tonumber(t.vval.v_special)]
end,
[tonumber(eval.VAR_NUMBER)] = function(t)
@@ -349,8 +353,8 @@ lua2typvalt = function(l, processed)
[null_list] = {'VAR_LIST', {v_list=ffi.cast('list_T*', nil)}},
[null_dict] = {'VAR_DICT', {v_dict=ffi.cast('dict_T*', nil)}},
[nil_value] = {'VAR_SPECIAL', {v_special=eval.kSpecialVarNull}},
- [true] = {'VAR_SPECIAL', {v_special=eval.kSpecialVarTrue}},
- [false] = {'VAR_SPECIAL', {v_special=eval.kSpecialVarFalse}},
+ [true] = {'VAR_BOOL', {v_bool=eval.kBoolVarTrue}},
+ [false] = {'VAR_BOOL', {v_bool=eval.kBoolVarFalse}},
}
for k, v in pairs(special_vals) do
@@ -406,7 +410,7 @@ end
local alloc_logging_helpers = {
list = function(l) return {func='calloc', args={1, ffi.sizeof('list_T')}, ret=void(l)} end,
li = function(li) return {func='malloc', args={ffi.sizeof('listitem_T')}, ret=void(li)} end,
- dict = function(d) return {func='malloc', args={ffi.sizeof('dict_T')}, ret=void(d)} end,
+ dict = function(d) return {func='calloc', args={1, ffi.sizeof('dict_T')}, ret=void(d)} end,
di = function(di, size)
size = alloc_len(size, function() return di.di_key end)
return {func='malloc', args={ffi.offsetof('dictitem_T', 'di_key') + size + 1}, ret=void(di)}
diff --git a/test/unit/eval/typval_spec.lua b/test/unit/eval/typval_spec.lua
index 4535d6a0b2..7c03005529 100644
--- a/test/unit/eval/typval_spec.lua
+++ b/test/unit/eval/typval_spec.lua
@@ -14,7 +14,7 @@ local cimport = helpers.cimport
local to_cstr = helpers.to_cstr
local alloc_log_new = helpers.alloc_log_new
local concat_tables = helpers.concat_tables
-local map = helpers.map
+local map = helpers.tbl_map
local a = eval_helpers.alloc_logging_helpers
local int = eval_helpers.int
@@ -48,8 +48,7 @@ local lib = cimport('./src/nvim/eval/typval.h', './src/nvim/memory.h',
local function vimconv_alloc()
return ffi.gc(
- ffi.cast('vimconv_T*', lib.xcalloc(1, ffi.sizeof('vimconv_T'))),
- function(vc)
+ ffi.cast('vimconv_T*', lib.xcalloc(1, ffi.sizeof('vimconv_T'))), function(vc)
lib.convert_setup(vc, nil, nil)
lib.xfree(vc)
end)
@@ -1235,13 +1234,13 @@ describe('typval.c', function()
local l = list()
local l2 = list()
- -- NULL lists are not equal to empty lists
- eq(false, lib.tv_list_equal(l, nil, true, false))
- eq(false, lib.tv_list_equal(nil, l, false, false))
- eq(false, lib.tv_list_equal(nil, l, false, true))
- eq(false, lib.tv_list_equal(l, nil, true, true))
+ -- NULL lists are equal to empty lists
+ eq(true, lib.tv_list_equal(l, nil, true, false))
+ eq(true, lib.tv_list_equal(nil, l, false, false))
+ eq(true, lib.tv_list_equal(nil, l, false, true))
+ eq(true, lib.tv_list_equal(l, nil, true, true))
- -- Yet NULL lists are equal themselves
+ -- NULL lists are equal themselves
eq(true, lib.tv_list_equal(nil, nil, true, false))
eq(true, lib.tv_list_equal(nil, nil, false, false))
eq(true, lib.tv_list_equal(nil, nil, false, true))
@@ -2026,6 +2025,26 @@ describe('typval.c', function()
alloc_log:check({})
end)
end)
+ describe('float()', function()
+ itp('works', function()
+ local d = dict({test=10})
+ alloc_log:clear()
+ eq({test=10}, dct2tbl(d))
+ eq(OK, lib.tv_dict_add_float(d, 'testt', 3, 1.5))
+ local dis = dict_items(d)
+ alloc_log:check({a.di(dis.tes, 'tes')})
+ eq({test=10, tes=1.5}, dct2tbl(d))
+ eq(FAIL, check_emsg(function() return lib.tv_dict_add_float(d, 'testt', 3, 1.5) end,
+ 'E685: Internal error: hash_add()'))
+ alloc_log:clear()
+ lib.emsg_skip = lib.emsg_skip + 1
+ eq(FAIL, check_emsg(function() return lib.tv_dict_add_float(d, 'testt', 3, 1.5) end,
+ nil))
+ lib.emsg_skip = lib.emsg_skip - 1
+ alloc_log:clear_tmp_allocs()
+ alloc_log:check({})
+ end)
+ end)
describe('str()', function()
itp('works', function()
local d = dict({test=10})
@@ -2629,13 +2648,13 @@ describe('typval.c', function()
local l2 = lua2typvalt(empty_list)
local nl = lua2typvalt(null_list)
- -- NULL lists are not equal to empty lists
- eq(false, lib.tv_equal(l, nl, true, false))
- eq(false, lib.tv_equal(nl, l, false, false))
- eq(false, lib.tv_equal(nl, l, false, true))
- eq(false, lib.tv_equal(l, nl, true, true))
+ -- NULL lists are equal to empty lists
+ eq(true, lib.tv_equal(l, nl, true, false))
+ eq(true, lib.tv_equal(nl, l, false, false))
+ eq(true, lib.tv_equal(nl, l, false, true))
+ eq(true, lib.tv_equal(l, nl, true, true))
- -- Yet NULL lists are equal themselves
+ -- NULL lists are equal themselves
eq(true, lib.tv_equal(nl, nl, true, false))
eq(true, lib.tv_equal(nl, nl, false, false))
eq(true, lib.tv_equal(nl, nl, false, true))
@@ -2818,6 +2837,7 @@ describe('typval.c', function()
{lib.VAR_FUNC, 'E729: using Funcref as a String'},
{lib.VAR_LIST, 'E730: using List as a String'},
{lib.VAR_DICT, 'E731: using Dictionary as a String'},
+ {lib.VAR_BOOL, nil},
{lib.VAR_SPECIAL, nil},
{lib.VAR_UNKNOWN, 'E908: using an invalid value as a String'},
}) do
@@ -2848,8 +2868,8 @@ describe('typval.c', function()
{lib.VAR_LIST, {v_list=NULL}, 'E745: Using a List as a Number', 0},
{lib.VAR_DICT, {v_dict=NULL}, 'E728: Using a Dictionary as a Number', 0},
{lib.VAR_SPECIAL, {v_special=lib.kSpecialVarNull}, nil, 0},
- {lib.VAR_SPECIAL, {v_special=lib.kSpecialVarTrue}, nil, 1},
- {lib.VAR_SPECIAL, {v_special=lib.kSpecialVarFalse}, nil, 0},
+ {lib.VAR_BOOL, {v_bool=lib.kBoolVarTrue}, nil, 1},
+ {lib.VAR_BOOL, {v_bool=lib.kBoolVarFalse}, nil, 0},
{lib.VAR_UNKNOWN, nil, 'E685: Internal error: tv_get_number(UNKNOWN)', 0},
}) do
-- Using to_cstr, cannot free with tv_clear
@@ -2877,8 +2897,8 @@ describe('typval.c', function()
{lib.VAR_LIST, {v_list=NULL}, 'E745: Using a List as a Number', 0},
{lib.VAR_DICT, {v_dict=NULL}, 'E728: Using a Dictionary as a Number', 0},
{lib.VAR_SPECIAL, {v_special=lib.kSpecialVarNull}, nil, 0},
- {lib.VAR_SPECIAL, {v_special=lib.kSpecialVarTrue}, nil, 1},
- {lib.VAR_SPECIAL, {v_special=lib.kSpecialVarFalse}, nil, 0},
+ {lib.VAR_BOOL, {v_bool=lib.kBoolVarTrue}, nil, 1},
+ {lib.VAR_BOOL, {v_bool=lib.kBoolVarFalse}, nil, 0},
{lib.VAR_UNKNOWN, nil, 'E685: Internal error: tv_get_number(UNKNOWN)', 0},
}) do
-- Using to_cstr, cannot free with tv_clear
@@ -2911,8 +2931,8 @@ describe('typval.c', function()
{lib.VAR_LIST, {v_list=NULL}, 'E745: Using a List as a Number', -1},
{lib.VAR_DICT, {v_dict=NULL}, 'E728: Using a Dictionary as a Number', -1},
{lib.VAR_SPECIAL, {v_special=lib.kSpecialVarNull}, nil, 0},
- {lib.VAR_SPECIAL, {v_special=lib.kSpecialVarTrue}, nil, 1},
- {lib.VAR_SPECIAL, {v_special=lib.kSpecialVarFalse}, nil, 0},
+ {lib.VAR_BOOL, {v_bool=lib.kBoolVarTrue}, nil, 1},
+ {lib.VAR_BOOL, {v_bool=lib.kBoolVarFalse}, nil, 0},
{lib.VAR_UNKNOWN, nil, 'E685: Internal error: tv_get_number(UNKNOWN)', -1},
}) do
lib.curwin.w_cursor.lnum = 46
@@ -2941,8 +2961,8 @@ describe('typval.c', function()
{lib.VAR_LIST, {v_list=NULL}, 'E893: Using a List as a Float', 0},
{lib.VAR_DICT, {v_dict=NULL}, 'E894: Using a Dictionary as a Float', 0},
{lib.VAR_SPECIAL, {v_special=lib.kSpecialVarNull}, 'E907: Using a special value as a Float', 0},
- {lib.VAR_SPECIAL, {v_special=lib.kSpecialVarTrue}, 'E907: Using a special value as a Float', 0},
- {lib.VAR_SPECIAL, {v_special=lib.kSpecialVarFalse}, 'E907: Using a special value as a Float', 0},
+ {lib.VAR_BOOL, {v_bool=lib.kBoolVarTrue}, 'E362: Using a boolean value as a Float', 0},
+ {lib.VAR_BOOL, {v_bool=lib.kBoolVarFalse}, 'E362: Using a boolean value as a Float', 0},
{lib.VAR_UNKNOWN, nil, 'E685: Internal error: tv_get_float(UNKNOWN)', 0},
}) do
-- Using to_cstr, cannot free with tv_clear
@@ -2973,8 +2993,8 @@ describe('typval.c', function()
{lib.VAR_LIST, {v_list=NULL}, 'E730: using List as a String', ''},
{lib.VAR_DICT, {v_dict=NULL}, 'E731: using Dictionary as a String', ''},
{lib.VAR_SPECIAL, {v_special=lib.kSpecialVarNull}, nil, 'null'},
- {lib.VAR_SPECIAL, {v_special=lib.kSpecialVarTrue}, nil, 'true'},
- {lib.VAR_SPECIAL, {v_special=lib.kSpecialVarFalse}, nil, 'false'},
+ {lib.VAR_BOOL, {v_bool=lib.kBoolVarTrue}, nil, 'true'},
+ {lib.VAR_BOOL, {v_bool=lib.kBoolVarFalse}, nil, 'false'},
{lib.VAR_UNKNOWN, nil, 'E908: using an invalid value as a String', ''},
}) do
-- Using to_cstr in place of Neovim allocated string, cannot
@@ -2985,7 +3005,8 @@ describe('typval.c', function()
local ret = v[4]
eq(ret, check_emsg(function()
local res = lib.tv_get_string(tv)
- if tv.v_type == lib.VAR_NUMBER or tv.v_type == lib.VAR_SPECIAL then
+ if tv.v_type == lib.VAR_NUMBER or tv.v_type == lib.VAR_SPECIAL
+ or tv.v_type == lib.VAR_BOOL then
eq(buf, res)
else
neq(buf, res)
@@ -3016,8 +3037,8 @@ describe('typval.c', function()
{lib.VAR_LIST, {v_list=NULL}, 'E730: using List as a String', nil},
{lib.VAR_DICT, {v_dict=NULL}, 'E731: using Dictionary as a String', nil},
{lib.VAR_SPECIAL, {v_special=lib.kSpecialVarNull}, nil, 'null'},
- {lib.VAR_SPECIAL, {v_special=lib.kSpecialVarTrue}, nil, 'true'},
- {lib.VAR_SPECIAL, {v_special=lib.kSpecialVarFalse}, nil, 'false'},
+ {lib.VAR_BOOL, {v_bool=lib.kBoolVarTrue}, nil, 'true'},
+ {lib.VAR_BOOL, {v_bool=lib.kBoolVarFalse}, nil, 'false'},
{lib.VAR_UNKNOWN, nil, 'E908: using an invalid value as a String', nil},
}) do
-- Using to_cstr, cannot free with tv_clear
@@ -3027,7 +3048,8 @@ describe('typval.c', function()
local ret = v[4]
eq(ret, check_emsg(function()
local res = lib.tv_get_string_chk(tv)
- if tv.v_type == lib.VAR_NUMBER or tv.v_type == lib.VAR_SPECIAL then
+ if tv.v_type == lib.VAR_NUMBER or tv.v_type == lib.VAR_SPECIAL
+ or tv.v_type == lib.VAR_BOOL then
eq(buf, res)
else
neq(buf, res)
@@ -3057,8 +3079,8 @@ describe('typval.c', function()
{lib.VAR_LIST, {v_list=NULL}, 'E730: using List as a String', ''},
{lib.VAR_DICT, {v_dict=NULL}, 'E731: using Dictionary as a String', ''},
{lib.VAR_SPECIAL, {v_special=lib.kSpecialVarNull}, nil, 'null'},
- {lib.VAR_SPECIAL, {v_special=lib.kSpecialVarTrue}, nil, 'true'},
- {lib.VAR_SPECIAL, {v_special=lib.kSpecialVarFalse}, nil, 'false'},
+ {lib.VAR_BOOL, {v_bool=lib.kBoolVarTrue}, nil, 'true'},
+ {lib.VAR_BOOL, {v_bool=lib.kBoolVarFalse}, nil, 'false'},
{lib.VAR_UNKNOWN, nil, 'E908: using an invalid value as a String', ''},
}) do
-- Using to_cstr, cannot free with tv_clear
@@ -3069,7 +3091,8 @@ describe('typval.c', function()
eq(ret, check_emsg(function()
local buf = ffi.new('char[?]', lib.NUMBUFLEN, {0})
local res = lib.tv_get_string_buf(tv, buf)
- if tv.v_type == lib.VAR_NUMBER or tv.v_type == lib.VAR_SPECIAL then
+ if tv.v_type == lib.VAR_NUMBER or tv.v_type == lib.VAR_SPECIAL
+ or tv.v_type == lib.VAR_BOOL then
eq(buf, res)
else
neq(buf, res)
@@ -3099,8 +3122,8 @@ describe('typval.c', function()
{lib.VAR_LIST, {v_list=NULL}, 'E730: using List as a String', nil},
{lib.VAR_DICT, {v_dict=NULL}, 'E731: using Dictionary as a String', nil},
{lib.VAR_SPECIAL, {v_special=lib.kSpecialVarNull}, nil, 'null'},
- {lib.VAR_SPECIAL, {v_special=lib.kSpecialVarTrue}, nil, 'true'},
- {lib.VAR_SPECIAL, {v_special=lib.kSpecialVarFalse}, nil, 'false'},
+ {lib.VAR_BOOL, {v_bool=lib.kBoolVarTrue}, nil, 'true'},
+ {lib.VAR_BOOL, {v_bool=lib.kBoolVarFalse}, nil, 'false'},
{lib.VAR_UNKNOWN, nil, 'E908: using an invalid value as a String', nil},
}) do
-- Using to_cstr, cannot free with tv_clear
@@ -3111,7 +3134,8 @@ describe('typval.c', function()
eq(ret, check_emsg(function()
local buf = ffi.new('char[?]', lib.NUMBUFLEN, {0})
local res = lib.tv_get_string_buf_chk(tv, buf)
- if tv.v_type == lib.VAR_NUMBER or tv.v_type == lib.VAR_SPECIAL then
+ if tv.v_type == lib.VAR_NUMBER or tv.v_type == lib.VAR_SPECIAL
+ or tv.v_type == lib.VAR_BOOL then
eq(buf, res)
else
neq(buf, res)
diff --git a/test/unit/helpers.lua b/test/unit/helpers.lua
index 24dbc65bd0..465b553693 100644
--- a/test/unit/helpers.lua
+++ b/test/unit/helpers.lua
@@ -13,7 +13,7 @@ local syscall = nil
local check_cores = global_helpers.check_cores
local dedent = global_helpers.dedent
local neq = global_helpers.neq
-local map = global_helpers.map
+local map = global_helpers.tbl_map
local eq = global_helpers.eq
local trim = global_helpers.trim
@@ -96,8 +96,8 @@ local init = only_separate(function()
c.func(unpack(c.args))
end
libnvim.time_init()
- libnvim.early_init()
libnvim.event_init()
+ libnvim.early_init(nil)
if child_calls_mod then
for _, c in ipairs(child_calls_mod) do
c.func(unpack(c.args))
@@ -545,7 +545,7 @@ local tracehelp = dedent([[
local function child_sethook(wr)
local trace_level = os.getenv('NVIM_TEST_TRACE_LEVEL')
if not trace_level or trace_level == '' then
- trace_level = 1
+ trace_level = 0
else
trace_level = tonumber(trace_level)
end
@@ -708,7 +708,7 @@ local function check_child_err(rd)
local eres = sc.read(rd, 2)
if eres ~= '$\n' then
if #trace == 0 then
- err = '\nTest crashed, no trace available\n'
+ err = '\nTest crashed, no trace available (check NVIM_TEST_TRACE_LEVEL)\n'
else
err = '\nTest crashed, trace:\n' .. tracehelp
for i = 1, #trace do
diff --git a/test/unit/marktree_spec.lua b/test/unit/marktree_spec.lua
new file mode 100644
index 0000000000..56acc0f93e
--- /dev/null
+++ b/test/unit/marktree_spec.lua
@@ -0,0 +1,190 @@
+local helpers = require("test.unit.helpers")(after_each)
+local itp = helpers.gen_itp(it)
+
+local ffi = helpers.ffi
+local eq = helpers.eq
+local ok = helpers.ok
+
+local lib = helpers.cimport("./src/nvim/marktree.h")
+
+local function tablelength(t)
+ local count = 0
+ for _ in pairs(t) do count = count + 1 end
+ return count
+end
+
+local function pos_leq(a, b)
+ return a[1] < b[1] or (a[1] == b[1] and a[2] <= b[2])
+end
+
+-- Checks that shadow and tree is consistent, and optionally
+-- return the order
+local function shadoworder(tree, shadow, iter, giveorder)
+ ok(iter ~= nil)
+ local status = lib.marktree_itr_first(tree, iter)
+ local count = 0
+ local pos2id, id2pos = {}, {}
+ local last
+ if not status and next(shadow) == nil then
+ return pos2id, id2pos
+ end
+ repeat
+ local mark = lib.marktree_itr_current(iter)
+ local id = tonumber(mark.id)
+ local spos = shadow[id]
+ if (mark.row ~= spos[1] or mark.col ~= spos[2]) then
+ error("invalid pos for "..id..":("..mark.row..", "..mark.col..") instead of ("..spos[1]..", "..spos[2]..")")
+ end
+ if mark.right_gravity ~= spos[3] then
+ error("invalid gravity for "..id..":("..mark.row..", "..mark.col..")")
+ end
+ if count > 0 then
+ if not pos_leq(last, spos) then
+ error("DISORDER")
+ end
+ end
+ count = count + 1
+ last = spos
+ if giveorder then
+ pos2id[count] = id
+ id2pos[id] = count
+ end
+ until not lib.marktree_itr_next(tree, iter)
+ local shadowlen = tablelength(shadow)
+ if shadowlen ~= count then
+ error("missed some keys? (shadow "..shadowlen..", tree "..count..")")
+ end
+ return id2pos, pos2id
+end
+
+local function shadowsplice(shadow, start, old_extent, new_extent)
+ local old_end = {start[1] + old_extent[1],
+ (old_extent[1] == 0 and start[2] or 0) + old_extent[2]}
+ local new_end = {start[1] + new_extent[1],
+ (new_extent[1] == 0 and start[2] or 0) + new_extent[2]}
+ local delta = {new_end[1] - old_end[1], new_end[2] - old_end[2]}
+ for _, pos in pairs(shadow) do
+ if pos_leq(start, pos) then
+ if pos_leq(pos, old_end) then
+ -- delete region
+ if pos[3] then -- right gravity
+ pos[1], pos[2] = new_end[1], new_end[2]
+ else
+ pos[1], pos[2] = start[1], start[2]
+ end
+ else
+ if pos[1] == old_end[1] then
+ pos[2] = pos[2] + delta[2]
+ end
+ pos[1] = pos[1] + delta[1]
+ end
+ end
+ end
+end
+
+local function dosplice(tree, shadow, start, old_extent, new_extent)
+ lib.marktree_splice(tree, start[1], start[2], old_extent[1], old_extent[2], new_extent[1], new_extent[2])
+ shadowsplice(shadow, start, old_extent, new_extent)
+end
+
+describe('marktree', function()
+ itp('works', function()
+ local tree = ffi.new("MarkTree[1]") -- zero initialized by luajit
+ local shadow = {}
+ local iter = ffi.new("MarkTreeIter[1]")
+ local iter2 = ffi.new("MarkTreeIter[1]")
+
+ for i = 1,100 do
+ for j = 1,100 do
+ local gravitate = (i%2) > 0
+ local id = tonumber(lib.marktree_put(tree, j, i, gravitate))
+ ok(id > 0)
+ eq(nil, shadow[id])
+ shadow[id] = {j,i,gravitate}
+ end
+ -- checking every insert is too slow, but this is ok
+ lib.marktree_check(tree)
+ end
+
+ -- ss = lib.mt_inspect_rec(tree)
+ -- io.stdout:write(ffi.string(ss))
+ -- io.stdout:flush()
+
+ local id2pos, pos2id = shadoworder(tree, shadow, iter)
+ eq({}, pos2id) -- not set if not requested
+ eq({}, id2pos)
+
+ for i,ipos in pairs(shadow) do
+ local pos = lib.marktree_lookup(tree, i, iter)
+ eq(ipos[1], pos.row)
+ eq(ipos[2], pos.col)
+ local k = lib.marktree_itr_current(iter)
+ eq(ipos[1], k.row)
+ eq(ipos[2], k.col, ipos[1])
+ lib.marktree_itr_next(tree, iter)
+ -- TODO(bfredl): use id2pos to check neighbour?
+ -- local k2 = lib.marktree_itr_current(iter)
+ end
+
+ for i,ipos in pairs(shadow) do
+ lib.marktree_itr_get(tree, ipos[1], ipos[2], iter)
+ local k = lib.marktree_itr_current(iter)
+ eq(i, tonumber(k.id))
+ eq(ipos[1], k.row)
+ eq(ipos[2], k.col)
+ end
+
+ ok(lib.marktree_itr_first(tree, iter))
+ local del = lib.marktree_itr_current(iter)
+
+ lib.marktree_del_itr(tree, iter, false)
+ shadow[tonumber(del.id)] = nil
+ shadoworder(tree, shadow, iter)
+
+ for _, ci in ipairs({0,-1,1,-2,2,-10,10}) do
+ for i = 1,100 do
+ lib.marktree_itr_get(tree, i, 50+ci, iter)
+ local k = lib.marktree_itr_current(iter)
+ local id = tonumber(k.id)
+ eq(shadow[id][1], k.row)
+ eq(shadow[id][2], k.col)
+ lib.marktree_del_itr(tree, iter, false)
+ shadow[id] = nil
+ end
+ lib.marktree_check(tree)
+ shadoworder(tree, shadow, iter)
+ end
+
+ -- NB: this is quite rudimentary. We rely on
+ -- functional tests exercising splicing quite a bit
+ lib.marktree_check(tree)
+ dosplice(tree, shadow, {2,2}, {0,5}, {1, 2})
+ lib.marktree_check(tree)
+ shadoworder(tree, shadow, iter)
+ dosplice(tree, shadow, {30,2}, {30,5}, {1, 2})
+ lib.marktree_check(tree)
+ shadoworder(tree, shadow, iter)
+
+ dosplice(tree, shadow, {5,3}, {0,2}, {0, 5})
+ shadoworder(tree, shadow, iter)
+ lib.marktree_check(tree)
+
+ -- build then burn (HOORAY! HOORAY!)
+ while next(shadow) do
+ lib.marktree_itr_first(tree, iter)
+ -- delete every other key for fun and profit
+ while true do
+ local k = lib.marktree_itr_current(iter)
+ lib.marktree_del_itr(tree, iter, false)
+ ok(shadow[tonumber(k.id)] ~= nil)
+ shadow[tonumber(k.id)] = nil
+ local stat = lib.marktree_itr_next(tree, iter)
+ if not stat then
+ break
+ end
+ end
+ lib.marktree_check(tree)
+ shadoworder(tree, shadow, iter2)
+ end
+ end)
+end)
diff --git a/test/unit/mbyte_spec.lua b/test/unit/mbyte_spec.lua
index d27f52923a..fdb1bceab0 100644
--- a/test/unit/mbyte_spec.lua
+++ b/test/unit/mbyte_spec.lua
@@ -8,11 +8,6 @@ local mbyte = helpers.cimport("./src/nvim/mbyte.h")
local charset = helpers.cimport('./src/nvim/charset.h')
describe('mbyte', function()
- if helpers.isCI('quickbuild') then
- pending("crashes on quickbuild", function() end)
- return
- end
-
-- Array for composing characters
local intp = ffi.typeof('int[?]')
local function to_intp()
diff --git a/test/unit/os/env_spec.lua b/test/unit/os/env_spec.lua
index c543551607..ad05b134e0 100644
--- a/test/unit/os/env_spec.lua
+++ b/test/unit/os/env_spec.lua
@@ -121,7 +121,7 @@ describe('env.c', function()
local name = 'NVIM_UNIT_TEST_GETENV_1N'
local value = 'NVIM_UNIT_TEST_GETENV_1V'
eq(NULL, os_getenv(name))
- -- Use os_setenv because Lua dosen't have setenv.
+ -- Use os_setenv because Lua doesn't have setenv.
os_setenv(name, value, 1)
eq(value, os_getenv(name))
@@ -172,7 +172,7 @@ describe('env.c', function()
i = i + 1
name = cimp.os_getenvname_at_index(i)
end
- eq(true, (table.getn(names)) > 0)
+ eq(true, #names > 0)
eq(true, found_name)
end)
diff --git a/test/unit/os/fs_spec.lua b/test/unit/os/fs_spec.lua
index 526a09e845..7fd71cb1ae 100644
--- a/test/unit/os/fs_spec.lua
+++ b/test/unit/os/fs_spec.lua
@@ -110,7 +110,7 @@ describe('fs.c', function()
describe('os_chdir', function()
itp('fails with path="~"', function()
- eq(false, os_isdir('~')) -- sanity check: no literal "~" directory.
+ eq(false, os_isdir('~'), 'sanity check: no literal "~" directory')
local length = 4096
local expected_cwd = cstr(length, '')
local cwd = cstr(length, '')
diff --git a/test/unit/path_spec.lua b/test/unit/path_spec.lua
index e8ce660ce1..356c4997fa 100644
--- a/test/unit/path_spec.lua
+++ b/test/unit/path_spec.lua
@@ -66,10 +66,10 @@ describe('path.c', function()
end)
describe('path_full_compare', function()
- local function path_full_compare(s1, s2, cn)
+ local function path_full_compare(s1, s2, cn, ee)
s1 = to_cstr(s1)
s2 = to_cstr(s2)
- return cimp.path_full_compare(s1, s2, cn or 0)
+ return cimp.path_full_compare(s1, s2, cn or 0, ee or 1)
end
local f1 = 'f1.o'
@@ -456,7 +456,7 @@ describe('path.c', function()
end)
itp('fails and uses filename when the path is relative to HOME', function()
- eq(false, cimp.os_isdir('~')) -- sanity check: no literal "~" directory.
+ eq(false, cimp.os_isdir('~'), 'sanity check: no literal "~" directory')
local absolute_path = '~/home.file'
local buflen = string.len(absolute_path) + 1
local do_expand = 1
diff --git a/test/unit/preprocess.lua b/test/unit/preprocess.lua
index 1073855a7d..3786bc2122 100644
--- a/test/unit/preprocess.lua
+++ b/test/unit/preprocess.lua
@@ -89,6 +89,10 @@ local Gcc = {
get_defines_extra_flags = {'-std=c99', '-dM', '-E'},
get_declarations_extra_flags = {'-std=c99', '-P', '-E'},
}
+if ffi.abi("32bit") then
+ table.insert(Gcc.get_defines_extra_flags, '-m32')
+ table.insert(Gcc.get_declarations_extra_flags, '-m32')
+end
function Gcc:define(name, args, val)
local define = '-D' .. name
diff --git a/test/unit/tui_spec.lua b/test/unit/tui_spec.lua
new file mode 100644
index 0000000000..e6b5c889d7
--- /dev/null
+++ b/test/unit/tui_spec.lua
@@ -0,0 +1,136 @@
+local helpers = require("test.unit.helpers")(after_each)
+local cimport = helpers.cimport
+local eq = helpers.eq
+local ffi = helpers.ffi
+local itp = helpers.gen_itp(it)
+local to_cstr = helpers.to_cstr
+
+local cinput = cimport("./src/nvim/tui/input.h")
+local rbuffer = cimport("./test/unit/fixtures/rbuffer.h")
+local globals = cimport("./src/nvim/globals.h")
+local multiqueue = cimport("./test/unit/fixtures/multiqueue.h")
+
+itp('handle_background_color', function()
+ local handle_background_color = cinput.ut_handle_background_color
+ local term_input = ffi.new('TermInput', {})
+ local events = globals.main_loop.thread_events
+
+ -- Short-circuit when not waiting for response.
+ term_input.waiting_for_bg_response = 0
+ eq(false, handle_background_color(term_input))
+
+ local capacity = 100
+ local rbuf = ffi.gc(rbuffer.rbuffer_new(capacity), rbuffer.rbuffer_free)
+ term_input.read_stream.buffer = rbuf
+
+ local function assert_bg(colorspace, color, bg)
+ local term_response = '\027]11;'..colorspace..':'..color..'\007'
+ rbuffer.rbuffer_write(rbuf, to_cstr(term_response), #term_response)
+
+ term_input.waiting_for_bg_response = 1
+ eq(true, handle_background_color(term_input))
+ eq(0, term_input.waiting_for_bg_response)
+ eq(1, multiqueue.multiqueue_size(events))
+
+ local event = multiqueue.multiqueue_get(events)
+ local bg_event = ffi.cast("Event*", event.argv[1])
+ eq(bg, ffi.string(bg_event.argv[0]))
+
+ -- Buffer has been consumed.
+ eq(0, rbuf.size)
+ end
+
+ assert_bg('rgb', '0000/0000/0000', 'dark')
+ assert_bg('rgb', 'ffff/ffff/ffff', 'light')
+ assert_bg('rgb', '000/000/000', 'dark')
+ assert_bg('rgb', 'fff/fff/fff', 'light')
+ assert_bg('rgb', '00/00/00', 'dark')
+ assert_bg('rgb', 'ff/ff/ff', 'light')
+ assert_bg('rgb', '0/0/0', 'dark')
+ assert_bg('rgb', 'f/f/f', 'light')
+
+ assert_bg('rgb', 'f/0/0', 'dark')
+ assert_bg('rgb', '0/f/0', 'light')
+ assert_bg('rgb', '0/0/f', 'dark')
+
+ assert_bg('rgb', '1/1/1', 'dark')
+ assert_bg('rgb', '2/2/2', 'dark')
+ assert_bg('rgb', '3/3/3', 'dark')
+ assert_bg('rgb', '4/4/4', 'dark')
+ assert_bg('rgb', '5/5/5', 'dark')
+ assert_bg('rgb', '6/6/6', 'dark')
+ assert_bg('rgb', '7/7/7', 'dark')
+ assert_bg('rgb', '8/8/8', 'light')
+ assert_bg('rgb', '9/9/9', 'light')
+ assert_bg('rgb', 'a/a/a', 'light')
+ assert_bg('rgb', 'b/b/b', 'light')
+ assert_bg('rgb', 'c/c/c', 'light')
+ assert_bg('rgb', 'd/d/d', 'light')
+ assert_bg('rgb', 'e/e/e', 'light')
+
+ assert_bg('rgb', '0/e/0', 'light')
+ assert_bg('rgb', '0/d/0', 'light')
+ assert_bg('rgb', '0/c/0', 'dark')
+ assert_bg('rgb', '0/b/0', 'dark')
+
+ assert_bg('rgb', 'f/0/f', 'dark')
+ assert_bg('rgb', 'f/1/f', 'dark')
+ assert_bg('rgb', 'f/2/f', 'dark')
+ assert_bg('rgb', 'f/3/f', 'light')
+ assert_bg('rgb', 'f/4/f', 'light')
+
+ assert_bg('rgba', '0000/0000/0000/0000', 'dark')
+ assert_bg('rgba', '0000/0000/0000/ffff', 'dark')
+ assert_bg('rgba', 'ffff/ffff/ffff/0000', 'light')
+ assert_bg('rgba', 'ffff/ffff/ffff/ffff', 'light')
+ assert_bg('rgba', '000/000/000/000', 'dark')
+ assert_bg('rgba', '000/000/000/fff', 'dark')
+ assert_bg('rgba', 'fff/fff/fff/000', 'light')
+ assert_bg('rgba', 'fff/fff/fff/fff', 'light')
+ assert_bg('rgba', '00/00/00/00', 'dark')
+ assert_bg('rgba', '00/00/00/ff', 'dark')
+ assert_bg('rgba', 'ff/ff/ff/00', 'light')
+ assert_bg('rgba', 'ff/ff/ff/ff', 'light')
+ assert_bg('rgba', '0/0/0/0', 'dark')
+ assert_bg('rgba', '0/0/0/f', 'dark')
+ assert_bg('rgba', 'f/f/f/0', 'light')
+ assert_bg('rgba', 'f/f/f/f', 'light')
+
+
+ -- Incomplete sequence: not necessarily correct behavior, but tests it.
+ local term_response = '\027]11;rgba:f/f/f/f' -- missing '\007
+ rbuffer.rbuffer_write(rbuf, to_cstr(term_response), #term_response)
+
+ term_input.waiting_for_bg_response = 1
+ eq(false, handle_background_color(term_input))
+ eq(0, term_input.waiting_for_bg_response)
+
+ eq(0, multiqueue.multiqueue_size(events))
+ eq(0, rbuf.size)
+
+
+ -- Does nothing when not at start of buffer.
+ term_response = '123\027]11;rgba:f/f/f/f\007456'
+ rbuffer.rbuffer_write(rbuf, to_cstr(term_response), #term_response)
+
+ term_input.waiting_for_bg_response = 3
+ eq(false, handle_background_color(term_input))
+ eq(2, term_input.waiting_for_bg_response)
+
+ eq(0, multiqueue.multiqueue_size(events))
+ eq(#term_response, rbuf.size)
+ rbuffer.rbuffer_consumed(rbuf, #term_response)
+
+
+ -- Keeps trailing buffer.
+ term_response = '\027]11;rgba:f/f/f/f\007456'
+ rbuffer.rbuffer_write(rbuf, to_cstr(term_response), #term_response)
+
+ term_input.waiting_for_bg_response = 1
+ eq(true, handle_background_color(term_input))
+ eq(0, term_input.waiting_for_bg_response)
+
+ eq(1, multiqueue.multiqueue_size(events))
+ eq(3, rbuf.size)
+ rbuffer.rbuffer_consumed(rbuf, rbuf.size)
+end)