aboutsummaryrefslogtreecommitdiff
path: root/test/functional/api
diff options
context:
space:
mode:
authorJosh Rahm <joshuarahm@gmail.com>2023-11-29 22:39:54 +0000
committerJosh Rahm <joshuarahm@gmail.com>2023-11-29 22:39:54 +0000
commit21cb7d04c387e4198ca8098a884c78b56ffcf4c2 (patch)
tree84fe5690df1551f0bb2bdfe1a13aacd29ebc1de7 /test/functional/api
parentd9c904f85a23a496df4eb6be42aa43f007b22d50 (diff)
parent4a8bf24ac690004aedf5540fa440e788459e5e34 (diff)
downloadrneovim-colorcolchar.tar.gz
rneovim-colorcolchar.tar.bz2
rneovim-colorcolchar.zip
Merge remote-tracking branch 'upstream/master' into colorcolcharcolorcolchar
Diffstat (limited to 'test/functional/api')
-rw-r--r--test/functional/api/autocmd_spec.lua191
-rw-r--r--test/functional/api/buffer_spec.lua1276
-rw-r--r--test/functional/api/buffer_updates_spec.lua4
-rw-r--r--test/functional/api/command_spec.lua16
-rw-r--r--test/functional/api/extmark_spec.lua240
-rw-r--r--test/functional/api/highlight_spec.lua375
-rw-r--r--test/functional/api/keymap_spec.lua214
-rw-r--r--test/functional/api/proc_spec.lua12
-rw-r--r--test/functional/api/rpc_fixture.lua7
-rw-r--r--test/functional/api/server_notifications_spec.lua3
-rw-r--r--test/functional/api/server_requests_spec.lua19
-rw-r--r--test/functional/api/ui_spec.lua84
-rw-r--r--test/functional/api/version_spec.lua2
-rw-r--r--test/functional/api/vim_spec.lua876
-rw-r--r--test/functional/api/window_spec.lua354
15 files changed, 3206 insertions, 467 deletions
diff --git a/test/functional/api/autocmd_spec.lua b/test/functional/api/autocmd_spec.lua
index 22a1311ee9..fd46a1dcfa 100644
--- a/test/functional/api/autocmd_spec.lua
+++ b/test/functional/api/autocmd_spec.lua
@@ -14,14 +14,40 @@ before_each(clear)
describe('autocmd api', function()
describe('nvim_create_autocmd', function()
- it('"command" and "callback" are mutually exclusive', function()
- local rv = pcall_err(meths.create_autocmd, "BufReadPost", {
- pattern = "*.py,*.pyi",
+ it('validation', function()
+ eq("Cannot use both 'callback' and 'command'", pcall_err(meths.create_autocmd, 'BufReadPost', {
+ pattern = '*.py,*.pyi',
command = "echo 'Should Have Errored",
- callback = "NotAllowed",
- })
-
- eq("specify either 'callback' or 'command', not both", rv)
+ callback = 'NotAllowed',
+ }))
+ eq("Cannot use both 'pattern' and 'buffer' for the same autocmd", pcall_err(meths.create_autocmd, 'FileType', {
+ command = 'let g:called = g:called + 1',
+ buffer = 0,
+ pattern = '*.py',
+ }))
+ eq("Required: 'event'", pcall_err(meths.create_autocmd, {}, {
+ command = 'ls',
+ }))
+ eq("Required: 'command' or 'callback'", pcall_err(meths.create_autocmd, 'FileType', {
+ }))
+ eq("Invalid 'desc': expected String, got Integer", pcall_err(meths.create_autocmd, 'FileType', {
+ command = 'ls',
+ desc = 42,
+ }))
+ eq("Invalid 'callback': expected Lua function or Vim function name, got Integer", pcall_err(meths.create_autocmd, 'FileType', {
+ callback = 0,
+ }))
+ eq("Invalid 'event' item: expected String, got Array", pcall_err(meths.create_autocmd,
+ {'FileType', {}}, {}))
+ eq("Invalid 'group': 0", pcall_err(meths.create_autocmd, 'FileType', {
+ group = 0,
+ command = 'ls',
+ }))
+
+ eq("Invalid 'event': 'foo'", pcall_err(meths.create_autocmd, 'foo', { command = '' }))
+ eq("Invalid 'event': 'VimEnter '", pcall_err(meths.create_autocmd, 'VimEnter ', { command = '' }))
+ eq("Invalid 'event': 'VimEnter foo'", pcall_err(meths.create_autocmd, 'VimEnter foo', { command = '' }))
+ eq("Invalid 'event': 'BufAdd,BufDelete'", pcall_err(meths.create_autocmd, 'BufAdd,BufDelete', { command = '' }))
end)
it('doesnt leak when you use ++once', function()
@@ -59,18 +85,8 @@ describe('autocmd api', function()
eq(1, meths.get_var('called'))
end)
- it('does not allow passing buffer and patterns', function()
- local rv = pcall_err(meths.create_autocmd, "Filetype", {
- command = "let g:called = g:called + 1",
- buffer = 0,
- pattern = "*.py",
- })
-
- eq("cannot pass both: 'pattern' and 'buffer' for the same autocmd", rv)
- end)
-
it('does not allow passing invalid buffers', function()
- local ok, msg = pcall(meths.create_autocmd, "Filetype", {
+ local ok, msg = pcall(meths.create_autocmd, 'FileType', {
command = "let g:called = g:called + 1",
buffer = -1,
})
@@ -209,7 +225,7 @@ describe('autocmd api', function()
local aus = meths.get_autocmds({ event = 'User', pattern = 'Test' })
local first = aus[1]
- eq(first.id, 1)
+ eq(true, first.id > 0)
meths.set_var("some_condition", true)
meths.exec_autocmds("User", {pattern = "Test"})
@@ -217,23 +233,28 @@ describe('autocmd api', function()
end)
it('receives an args table', function()
- local res = exec_lua [[
- local group_id = vim.api.nvim_create_augroup("TestGroup", {})
- local autocmd_id = vim.api.nvim_create_autocmd("User", {
+ local group_id = meths.create_augroup("TestGroup", {})
+ -- Having an existing autocmd calling expand("<afile>") shouldn't change args #18964
+ meths.create_autocmd('User', {
+ group = 'TestGroup',
+ pattern = 'Te*',
+ command = 'call expand("<afile>")',
+ })
+
+ local autocmd_id = exec_lua [[
+ return vim.api.nvim_create_autocmd("User", {
group = "TestGroup",
pattern = "Te*",
callback = function(args)
vim.g.autocmd_args = args
end,
})
-
- return {group_id, autocmd_id}
]]
meths.exec_autocmds("User", {pattern = "Test pattern"})
eq({
- id = res[2],
- group = res[1],
+ id = autocmd_id,
+ group = group_id,
event = "User",
match = "Test pattern",
file = "Test pattern",
@@ -241,27 +262,24 @@ describe('autocmd api', function()
}, meths.get_var("autocmd_args"))
-- Test without a group
- res = exec_lua [[
- local autocmd_id = vim.api.nvim_create_autocmd("User", {
+ autocmd_id = exec_lua [[
+ return vim.api.nvim_create_autocmd("User", {
pattern = "*",
callback = function(args)
vim.g.autocmd_args = args
end,
})
-
- return {autocmd_id}
]]
meths.exec_autocmds("User", {pattern = "some_pat"})
eq({
- id = res[1],
+ id = autocmd_id,
group = nil,
event = "User",
match = "some_pat",
file = "some_pat",
buf = 1,
}, meths.get_var("autocmd_args"))
-
end)
it('can receive arbitrary data', function()
@@ -294,8 +312,32 @@ describe('autocmd api', function()
end)
describe('nvim_get_autocmds', function()
+ it('validation', function()
+ eq("Invalid 'group': 9997999", pcall_err(meths.get_autocmds, {
+ group = 9997999,
+ }))
+ eq("Invalid 'group': 'bogus'", pcall_err(meths.get_autocmds, {
+ group = 'bogus',
+ }))
+ eq("Invalid 'group': 0", pcall_err(meths.get_autocmds, {
+ group = 0,
+ }))
+ eq("Invalid 'group': expected String or Integer, got Array", pcall_err(meths.get_autocmds, {
+ group = {},
+ }))
+ eq("Invalid 'buffer': expected Integer or Array, got Boolean", pcall_err(meths.get_autocmds, {
+ buffer = true,
+ }))
+ eq("Invalid 'event': expected String or Array", pcall_err(meths.get_autocmds, {
+ event = true,
+ }))
+ eq("Invalid 'pattern': expected String or Array, got Boolean", pcall_err(meths.get_autocmds, {
+ pattern = true,
+ }))
+ end)
+
describe('events', function()
- it('should return one autocmd when there is only one for an event', function()
+ it('returns one autocmd when there is only one for an event', function()
command [[au! InsertEnter]]
command [[au InsertEnter * :echo "1"]]
@@ -303,7 +345,7 @@ describe('autocmd api', function()
eq(1, #aus)
end)
- it('should return two autocmds when there are two for an event', function()
+ it('returns two autocmds when there are two for an event', function()
command [[au! InsertEnter]]
command [[au InsertEnter * :echo "1"]]
command [[au InsertEnter * :echo "2"]]
@@ -312,7 +354,7 @@ describe('autocmd api', function()
eq(2, #aus)
end)
- it('should return the same thing if you use string or list', function()
+ it('returns the same thing if you use string or list', function()
command [[au! InsertEnter]]
command [[au InsertEnter * :echo "1"]]
command [[au InsertEnter * :echo "2"]]
@@ -322,7 +364,7 @@ describe('autocmd api', function()
eq(string_aus, array_aus)
end)
- it('should return two autocmds when there are two for an event', function()
+ it('returns two autocmds when there are two for an event', function()
command [[au! InsertEnter]]
command [[au! InsertLeave]]
command [[au InsertEnter * :echo "1"]]
@@ -332,7 +374,7 @@ describe('autocmd api', function()
eq(2, #aus)
end)
- it('should return different IDs for different autocmds', function()
+ it('returns different IDs for different autocmds', function()
command [[au! InsertEnter]]
command [[au! InsertLeave]]
command [[au InsertEnter * :echo "1"]]
@@ -356,7 +398,7 @@ describe('autocmd api', function()
eq(first, new_aus[1])
end)
- it('should return event name', function()
+ it('returns event name', function()
command [[au! InsertEnter]]
command [[au InsertEnter * :echo "1"]]
@@ -364,7 +406,7 @@ describe('autocmd api', function()
eq({ { buflocal = false, command = ':echo "1"', event = "InsertEnter", once = false, pattern = "*" } }, aus)
end)
- it('should work with buffer numbers', function()
+ it('works with buffer numbers', function()
command [[new]]
command [[au! InsertEnter]]
command [[au InsertEnter <buffer=1> :echo "1"]]
@@ -407,8 +449,8 @@ describe('autocmd api', function()
pattern = "<buffer=2>",
}}, aus)
- eq("Invalid value for 'buffer': must be an integer or array of integers", pcall_err(meths.get_autocmds, { event = "InsertEnter", buffer = "foo" }))
- eq("Invalid value for 'buffer': must be an integer", pcall_err(meths.get_autocmds, { event = "InsertEnter", buffer = { "foo", 42 } }))
+ eq("Invalid 'buffer': expected Integer or Array, got String", pcall_err(meths.get_autocmds, { event = "InsertEnter", buffer = "foo" }))
+ eq("Invalid 'buffer': expected Integer, got String", pcall_err(meths.get_autocmds, { event = "InsertEnter", buffer = { "foo", 42 } }))
eq("Invalid buffer id: 42", pcall_err(meths.get_autocmds, { event = "InsertEnter", buffer = { 42 } }))
local bufs = {}
@@ -416,10 +458,10 @@ describe('autocmd api', function()
table.insert(bufs, meths.create_buf(true, false))
end
- eq("Too many buffers. Please limit yourself to 256 or fewer", pcall_err(meths.get_autocmds, { event = "InsertEnter", buffer = bufs }))
+ eq("Too many buffers (maximum of 256)", pcall_err(meths.get_autocmds, { event = "InsertEnter", buffer = bufs }))
end)
- it('should return autocmds when group is specified by id', function()
+ it('returns autocmds when group is specified by id', function()
local auid = meths.create_augroup("nvim_test_augroup", { clear = true })
meths.create_autocmd("FileType", { group = auid, command = 'echo "1"' })
meths.create_autocmd("FileType", { group = auid, command = 'echo "2"' })
@@ -431,7 +473,7 @@ describe('autocmd api', function()
eq(0, #aus2)
end)
- it('should return autocmds when group is specified by name', function()
+ it('returns autocmds when group is specified by name', function()
local auname = "nvim_test_augroup"
meths.create_augroup(auname, { clear = true })
meths.create_autocmd("FileType", { group = auname, command = 'echo "1"' })
@@ -531,7 +573,7 @@ describe('autocmd api', function()
command [[augroup END]]
end)
- it('should return all groups if no group is specified', function()
+ it('returns all groups if no group is specified', function()
local aus = meths.get_autocmds { event = "InsertEnter" }
if #aus ~= 4 then
eq({}, aus)
@@ -540,7 +582,7 @@ describe('autocmd api', function()
eq(4, #aus)
end)
- it('should return only the group specified', function()
+ it('returns only the group specified', function()
local aus = meths.get_autocmds {
event = "InsertEnter",
group = "GroupOne",
@@ -551,7 +593,7 @@ describe('autocmd api', function()
eq("GroupOne", aus[1].group_name)
end)
- it('should return only the group specified, multiple values', function()
+ it('returns only the group specified, multiple values', function()
local aus = meths.get_autocmds {
event = "InsertEnter",
group = "GroupTwo",
@@ -578,7 +620,7 @@ describe('autocmd api', function()
]], {}))
eq(false, success)
- matches('invalid augroup: NotDefined', code)
+ matches("Invalid 'group': 'NotDefined'", code)
end)
it('raises error for undefined augroup id', function()
@@ -596,7 +638,7 @@ describe('autocmd api', function()
]], {}))
eq(false, success)
- matches('invalid augroup: 1', code)
+ matches("Invalid 'group': 1", code)
end)
it('raises error for invalid group type', function()
@@ -611,7 +653,7 @@ describe('autocmd api', function()
]], {}))
eq(false, success)
- matches("'group' must be a string or an integer", code)
+ matches("Invalid 'group': expected String or Integer, got Boolean", code)
end)
it('raises error for invalid pattern array', function()
@@ -625,7 +667,7 @@ describe('autocmd api', function()
]], {}))
eq(false, success)
- matches("All entries in 'pattern' must be strings", code)
+ matches("Invalid 'pattern' item: expected String, got Array", code)
end)
end)
@@ -640,7 +682,7 @@ describe('autocmd api', function()
command [[au InsertEnter <buffer> :echo "Buffer"]]
end)
- it('should should return for literal match', function()
+ it('returns for literal match', function()
local aus = meths.get_autocmds {
event = "InsertEnter",
pattern = "*"
@@ -650,7 +692,7 @@ describe('autocmd api', function()
eq([[:echo "No Group"]], aus[1].command)
end)
- it('should return for multiple matches', function()
+ it('returns for multiple matches', function()
-- vim.api.nvim_get_autocmds
local aus = meths.get_autocmds {
event = "InsertEnter",
@@ -687,6 +729,26 @@ describe('autocmd api', function()
end)
describe('nvim_exec_autocmds', function()
+ it('validation', function()
+ eq("Invalid 'group': 9997999", pcall_err(meths.exec_autocmds, 'FileType', {
+ group = 9997999,
+ }))
+ eq("Invalid 'group': 'bogus'", pcall_err(meths.exec_autocmds, 'FileType', {
+ group = 'bogus',
+ }))
+ eq("Invalid 'group': expected String or Integer, got Array", pcall_err(meths.exec_autocmds, 'FileType', {
+ group = {},
+ }))
+ eq("Invalid 'group': 0", pcall_err(meths.exec_autocmds, 'FileType', {
+ group = 0,
+ }))
+ eq("Invalid 'buffer': expected Buffer, got Array", pcall_err(meths.exec_autocmds, 'FileType', {
+ buffer = {},
+ }))
+ eq("Invalid 'event' item: expected String, got Array", pcall_err(meths.exec_autocmds,
+ {'FileType', {}}, {}))
+ end)
+
it("can trigger builtin autocmds", function()
meths.set_var("autocmd_executed", false)
@@ -1004,6 +1066,12 @@ describe('autocmd api', function()
eq(false, exec_lua[[return pcall(vim.api.nvim_del_augroup_by_id, -12342)]])
eq('Vim:E367: No such group: "--Deleted--"', pcall_err(meths.del_augroup_by_id, -12312))
+
+ eq(false, exec_lua[[return pcall(vim.api.nvim_del_augroup_by_id, 0)]])
+ eq('Vim:E367: No such group: "[NULL]"', pcall_err(meths.del_augroup_by_id, 0))
+
+ eq(false, exec_lua[[return pcall(vim.api.nvim_del_augroup_by_id, 12342)]])
+ eq('Vim:E367: No such group: "[NULL]"', pcall_err(meths.del_augroup_by_id, 12312))
end)
it('groups work with once', function()
@@ -1036,7 +1104,7 @@ describe('autocmd api', function()
local augroup = "WillBeDeleted"
meths.create_augroup(augroup, { clear = true })
- meths.create_autocmd({"Filetype"}, {
+ meths.create_autocmd({"FileType"}, {
pattern = "*",
command = "echo 'does not matter'",
})
@@ -1055,7 +1123,7 @@ describe('autocmd api', function()
meths.set_var("value_set", false)
meths.create_augroup(augroup, { clear = true })
- meths.create_autocmd("Filetype", {
+ meths.create_autocmd("FileType", {
pattern = "<buffer>",
command = "let g:value_set = v:true",
})
@@ -1171,6 +1239,17 @@ describe('autocmd api', function()
end)
describe('nvim_clear_autocmds', function()
+ it('validation', function()
+ eq("Cannot use both 'pattern' and 'buffer'", pcall_err(meths.clear_autocmds, {
+ pattern = '*',
+ buffer = 42,
+ }))
+ eq("Invalid 'event' item: expected String, got Array", pcall_err(meths.clear_autocmds, {
+ event = {'FileType', {}}
+ }))
+ eq("Invalid 'group': 0", pcall_err(meths.clear_autocmds, {group = 0}))
+ end)
+
it('should clear based on event + pattern', function()
command('autocmd InsertEnter *.py :echo "Python can be cool sometimes"')
command('autocmd InsertEnter *.txt :echo "Text Files Are Cool"')
@@ -1254,7 +1333,7 @@ describe('autocmd api', function()
local without_group = meths.get_autocmds(search)
eq(2, #without_group)
- -- Doest clear with passing group.
+ -- Doesn't clear with passing group.
meths.clear_autocmds { buffer = 0, group = search.group }
local with_group = meths.get_autocmds(search)
eq(1, #with_group)
diff --git a/test/functional/api/buffer_spec.lua b/test/functional/api/buffer_spec.lua
index 6b13729994..6ed9aa574a 100644
--- a/test/functional/api/buffer_spec.lua
+++ b/test/functional/api/buffer_spec.lua
@@ -16,6 +16,7 @@ local command = helpers.command
local bufmeths = helpers.bufmeths
local feed = helpers.feed
local pcall_err = helpers.pcall_err
+local assert_alive = helpers.assert_alive
describe('api/buf', function()
before_each(clear)
@@ -41,6 +42,14 @@ describe('api/buf', function()
eq(1, curbuf_depr('line_count'))
end)
+ it("doesn't crash just after set undolevels=1 #24894", function()
+ local buf = meths.create_buf(false, true)
+ meths.buf_set_option(buf, 'undolevels', -1)
+ meths.buf_set_lines(buf, 0, 1, false, { })
+
+ assert_alive()
+ end)
+
it('cursor position is maintained after lines are inserted #9961', function()
-- replace the buffer contents with these three lines.
request('nvim_buf_set_lines', 0, 0, -1, 1, {"line1", "line2", "line3", "line4"})
@@ -76,6 +85,38 @@ describe('api/buf', function()
eq({4, 2}, curwin('get_cursor'))
end)
+ it('cursor position is maintained in non-current window', function()
+ meths.buf_set_lines(0, 0, -1, 1, {"line1", "line2", "line3", "line4"})
+ meths.win_set_cursor(0, {3, 2})
+ local win = meths.get_current_win()
+ local buf = meths.get_current_buf()
+
+ command('new')
+
+ meths.buf_set_lines(buf, 1, 2, 1, {"line5", "line6"})
+ eq({"line1", "line5", "line6", "line3", "line4"}, meths.buf_get_lines(buf, 0, -1, true))
+ eq({4, 2}, meths.win_get_cursor(win))
+ end)
+
+ it('cursor position is maintained in TWO non-current windows', function()
+ meths.buf_set_lines(0, 0, -1, 1, {"line1", "line2", "line3", "line4"})
+ meths.win_set_cursor(0, {3, 2})
+ local win = meths.get_current_win()
+ local buf = meths.get_current_buf()
+
+ command('split')
+ meths.win_set_cursor(0, {4, 2})
+ local win2 = meths.get_current_win()
+
+ -- set current window to third one with another buffer
+ command("new")
+
+ meths.buf_set_lines(buf, 1, 2, 1, {"line5", "line6"})
+ eq({"line1", "line5", "line6", "line3", "line4"}, meths.buf_get_lines(buf, 0, -1, true))
+ eq({4, 2}, meths.win_get_cursor(win))
+ eq({5, 2}, meths.win_get_cursor(win2))
+ end)
+
it('line_count has defined behaviour for unloaded buffers', function()
-- we'll need to know our bufnr for when it gets unloaded
local bufnr = curbuf('get_number')
@@ -105,6 +146,285 @@ describe('api/buf', function()
-- it's impossible to get out-of-bounds errors for an unloaded buffer
eq({}, buffer('get_lines', bufnr, 8888, 9999, 1))
end)
+
+ describe('handles topline', function()
+ local screen
+ before_each(function()
+ screen = Screen.new(20, 12)
+ screen:set_default_attr_ids {
+ [1] = {bold = true, foreground = Screen.colors.Blue1};
+ [2] = {reverse = true, bold = true};
+ [3] = {reverse = true};
+ }
+ screen:attach()
+ meths.buf_set_lines(0, 0, -1, 1, {"aaa", "bbb", "ccc", "ddd", "www", "xxx", "yyy", "zzz"})
+ meths.set_option_value('modified', false, {})
+ end)
+
+ it('of current window', function()
+ local win = meths.get_current_win()
+ local buf = meths.get_current_buf()
+
+ command('new | wincmd w')
+ meths.win_set_cursor(win, {8,0})
+
+ screen:expect{grid=[[
+ |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {3:[No Name] }|
+ www |
+ xxx |
+ yyy |
+ ^zzz |
+ {2:[No Name] }|
+ |
+ ]]}
+
+ meths.buf_set_lines(buf, 0, 2, true, {"aaabbb"})
+ screen:expect{grid=[[
+ |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {3:[No Name] }|
+ www |
+ xxx |
+ yyy |
+ ^zzz |
+ {2:[No Name] [+] }|
+ |
+ ]]}
+
+ -- replacing topline keeps it the topline
+ meths.buf_set_lines(buf, 3, 4, true, {"wwweeee"})
+ screen:expect{grid=[[
+ |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {3:[No Name] }|
+ wwweeee |
+ xxx |
+ yyy |
+ ^zzz |
+ {2:[No Name] [+] }|
+ |
+ ]]}
+
+ -- inserting just before topline does not scroll up if cursor would be moved
+ meths.buf_set_lines(buf, 3, 3, true, {"mmm"})
+ screen:expect{grid=[[
+ |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {3:[No Name] }|
+ wwweeee |
+ xxx |
+ yyy |
+ ^zzz |
+ {2:[No Name] [+] }|
+ |
+ ]], unchanged=true}
+
+ meths.win_set_cursor(0, {7, 0})
+ screen:expect{grid=[[
+ |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {3:[No Name] }|
+ wwweeee |
+ xxx |
+ ^yyy |
+ zzz |
+ {2:[No Name] [+] }|
+ |
+ ]]}
+
+ meths.buf_set_lines(buf, 4, 4, true, {"mmmeeeee"})
+ screen:expect{grid=[[
+ |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {3:[No Name] }|
+ mmmeeeee |
+ wwweeee |
+ xxx |
+ ^yyy |
+ {2:[No Name] [+] }|
+ |
+ ]]}
+ end)
+
+ it('of non-current window', function()
+ local win = meths.get_current_win()
+ local buf = meths.get_current_buf()
+
+ command('new')
+ meths.win_set_cursor(win, {8,0})
+
+ screen:expect{grid=[[
+ ^ |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {2:[No Name] }|
+ www |
+ xxx |
+ yyy |
+ zzz |
+ {3:[No Name] }|
+ |
+ ]]}
+
+ meths.buf_set_lines(buf, 0, 2, true, {"aaabbb"})
+ screen:expect{grid=[[
+ ^ |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {2:[No Name] }|
+ www |
+ xxx |
+ yyy |
+ zzz |
+ {3:[No Name] [+] }|
+ |
+ ]]}
+
+ -- replacing topline keeps it the topline
+ meths.buf_set_lines(buf, 3, 4, true, {"wwweeee"})
+ screen:expect{grid=[[
+ ^ |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {2:[No Name] }|
+ wwweeee |
+ xxx |
+ yyy |
+ zzz |
+ {3:[No Name] [+] }|
+ |
+ ]]}
+
+ -- inserting just before topline scrolls up
+ meths.buf_set_lines(buf, 3, 3, true, {"mmm"})
+ screen:expect{grid=[[
+ ^ |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {2:[No Name] }|
+ mmm |
+ wwweeee |
+ xxx |
+ yyy |
+ {3:[No Name] [+] }|
+ |
+ ]]}
+ end)
+
+ it('of split windows with same buffer', function()
+ local win = meths.get_current_win()
+ local buf = meths.get_current_buf()
+
+ command('split')
+ meths.win_set_cursor(win, {8,0})
+ meths.win_set_cursor(0, {1,0})
+
+ screen:expect{grid=[[
+ ^aaa |
+ bbb |
+ ccc |
+ ddd |
+ www |
+ {2:[No Name] }|
+ www |
+ xxx |
+ yyy |
+ zzz |
+ {3:[No Name] }|
+ |
+ ]]}
+ meths.buf_set_lines(buf, 0, 2, true, {"aaabbb"})
+
+ screen:expect{grid=[[
+ ^aaabbb |
+ ccc |
+ ddd |
+ www |
+ xxx |
+ {2:[No Name] [+] }|
+ www |
+ xxx |
+ yyy |
+ zzz |
+ {3:[No Name] [+] }|
+ |
+ ]]}
+
+ -- replacing topline keeps it the topline
+ meths.buf_set_lines(buf, 3, 4, true, {"wwweeee"})
+ screen:expect{grid=[[
+ ^aaabbb |
+ ccc |
+ ddd |
+ wwweeee |
+ xxx |
+ {2:[No Name] [+] }|
+ wwweeee |
+ xxx |
+ yyy |
+ zzz |
+ {3:[No Name] [+] }|
+ |
+ ]]}
+
+ -- inserting just before topline scrolls up
+ meths.buf_set_lines(buf, 3, 3, true, {"mmm"})
+ screen:expect{grid=[[
+ ^aaabbb |
+ ccc |
+ ddd |
+ mmm |
+ wwweeee |
+ {2:[No Name] [+] }|
+ mmm |
+ wwweeee |
+ xxx |
+ yyy |
+ {3:[No Name] [+] }|
+ |
+ ]]}
+ end)
+ end)
+
+ it('handles clearing out non-current buffer #24911', function()
+ local buf = meths.get_current_buf()
+ meths.buf_set_lines(buf, 0, -1, true, {"aaa", "bbb", "ccc"})
+ command("new")
+
+ meths.buf_set_lines(0, 0, -1, true, {"xxx", "yyy", "zzz"})
+
+ meths.buf_set_lines(buf, 0, -1, true, {})
+ eq({"xxx", "yyy", "zzz"}, meths.buf_get_lines(0, 0, -1, true))
+ eq({''}, meths.buf_get_lines(buf, 0, -1, true))
+ end)
end)
describe('deprecated: {get,set,del}_line', function()
@@ -193,7 +513,7 @@ describe('api/buf', function()
it('fails correctly when input is not valid', function()
eq(1, api.curbufmeths.get_number())
- eq([[String cannot contain newlines]],
+ eq([['replacement string' item contains newlines]],
pcall_err(bufmeths.set_lines, 1, 1, 2, false, {'b\na'}))
end)
@@ -393,6 +713,21 @@ describe('api/buf', function()
feed('<c-w>p')
eq(3, funcs.winnr())
end)
+
+ it('set_lines on unloaded buffer #8659 #22670', function()
+ local bufnr = curbuf('get_number')
+ meths.buf_set_lines(bufnr, 0, -1, false, {'a', 'b', 'c'})
+ meths.buf_set_name(bufnr, 'set_lines')
+ finally(function()
+ os.remove('set_lines')
+ end)
+ command('write!')
+ command('new')
+ command('bunload! '..bufnr)
+ local new_bufnr = funcs.bufnr('set_lines', true)
+ meths.buf_set_lines(new_bufnr, 0, -1, false, {})
+ eq({''}, meths.buf_get_lines(new_bufnr, 0, -1, false))
+ end)
end)
describe('nvim_buf_set_text', function()
@@ -437,6 +772,10 @@ describe('api/buf', function()
-- can append to a line
set_text(1, 4, -1, 4, {' and', 'more'})
eq({'goodbye bar', 'text and', 'more'}, get_lines(0, 3, true))
+
+ -- can use negative column numbers
+ set_text(0, -5, 0, -1, {'!'})
+ eq({'goodbye!'}, get_lines(0, 1, true))
end)
it('works with undo', function()
@@ -480,6 +819,754 @@ describe('api/buf', function()
eq({1, 9}, curwin('get_cursor'))
end)
+ it('updates the cursor position in non-current window', function()
+ insert([[
+ hello world!]])
+
+ -- position the cursor on `!`
+ meths.win_set_cursor(0, {1, 11})
+
+ local win = meths.get_current_win()
+ local buf = meths.get_current_buf()
+
+ command("new")
+
+ -- replace 'world' with 'foo'
+ meths.buf_set_text(buf, 0, 6, 0, 11, {'foo'})
+ eq({'hello foo!'}, meths.buf_get_lines(buf, 0, -1, true))
+ -- cursor should be moved left by two columns (replacement is shorter by 2 chars)
+ eq({1, 9}, meths.win_get_cursor(win))
+ end)
+
+ it('updates the cursor position in TWO non-current windows', function()
+ insert([[
+ hello world!]])
+
+ -- position the cursor on `!`
+ meths.win_set_cursor(0, {1, 11})
+ local win = meths.get_current_win()
+ local buf = meths.get_current_buf()
+
+ command("split")
+ local win2 = meths.get_current_win()
+ -- position the cursor on `w`
+ meths.win_set_cursor(0, {1, 6})
+
+ command("new")
+
+ -- replace 'hello' with 'foo'
+ meths.buf_set_text(buf, 0, 0, 0, 5, {'foo'})
+ eq({'foo world!'}, meths.buf_get_lines(buf, 0, -1, true))
+
+ -- both cursors should be moved left by two columns (replacement is shorter by 2 chars)
+ eq({1, 9}, meths.win_get_cursor(win))
+ eq({1, 4}, meths.win_get_cursor(win2))
+ end)
+
+ describe('when text is being added right at cursor position #22526', function()
+ it('updates the cursor position in NORMAL mode', function()
+ insert([[
+ abcd]])
+
+ -- position the cursor on 'c'
+ curwin('set_cursor', {1, 2})
+ -- add 'xxx' before 'c'
+ set_text(0, 2, 0, 2, {'xxx'})
+ eq({'abxxxcd'}, get_lines(0, -1, true))
+ -- cursor should be on 'c'
+ eq({1, 5}, curwin('get_cursor'))
+ end)
+
+ it('updates the cursor position only in non-current window when in INSERT mode', function()
+ insert([[
+ abcd]])
+
+ -- position the cursor on 'c'
+ curwin('set_cursor', {1, 2})
+ -- open vertical split
+ feed('<c-w>v')
+ -- get into INSERT mode to treat cursor
+ -- as being after 'b', not on 'c'
+ feed('i')
+ -- add 'xxx' between 'b' and 'c'
+ set_text(0, 2, 0, 2, {'xxx'})
+ eq({'abxxxcd'}, get_lines(0, -1, true))
+ -- in the current window cursor should stay after 'b'
+ eq({1, 2}, curwin('get_cursor'))
+ -- quit INSERT mode
+ feed('<esc>')
+ -- close current window
+ feed('<c-w>c')
+ -- in another window cursor should be on 'c'
+ eq({1, 5}, curwin('get_cursor'))
+ end)
+ end)
+
+ describe('when text is being deleted right at cursor position', function()
+ it('leaves cursor at the same position in NORMAL mode', function()
+ insert([[
+ abcd]])
+
+ -- position the cursor on 'b'
+ curwin('set_cursor', {1, 1})
+ -- delete 'b'
+ set_text(0, 1, 0, 2, {})
+ eq({'acd'}, get_lines(0, -1, true))
+ -- cursor is now on 'c'
+ eq({1, 1}, curwin('get_cursor'))
+ end)
+
+ it('leaves cursor at the same position in INSERT mode in current and non-current window', function()
+ insert([[
+ abcd]])
+
+ -- position the cursor on 'b'
+ curwin('set_cursor', {1, 1})
+ -- open vertical split
+ feed('<c-w>v')
+ -- get into INSERT mode to treat cursor
+ -- as being after 'a', not on 'b'
+ feed('i')
+ -- delete 'b'
+ set_text(0, 1, 0, 2, {})
+ eq({'acd'}, get_lines(0, -1, true))
+ -- cursor in the current window should stay after 'a'
+ eq({1, 1}, curwin('get_cursor'))
+ -- quit INSERT mode
+ feed('<esc>')
+ -- close current window
+ feed('<c-w>c')
+ -- cursor in non-current window should stay on 'c'
+ eq({1, 1}, curwin('get_cursor'))
+ end)
+ end)
+
+ describe('when cursor is inside replaced row range', function()
+ it('keeps cursor at the same position if cursor is at start_row, but before start_col', function()
+ insert([[
+ This should be first
+ then there is a line we do not want
+ and finally the last one]])
+
+ -- position the cursor on ' ' before 'first'
+ curwin('set_cursor', {1, 14})
+
+ set_text(0, 15, 2, 11, {
+ 'the line we do not want',
+ 'but hopefully',
+ })
+
+ eq({
+ 'This should be the line we do not want',
+ 'but hopefully the last one',
+ }, get_lines(0, -1, true))
+ -- cursor should stay at the same position
+ eq({1, 14}, curwin('get_cursor'))
+ end)
+
+ it('keeps cursor at the same position if cursor is at start_row and column is still valid', function()
+ insert([[
+ This should be first
+ then there is a line we do not want
+ and finally the last one]])
+
+ -- position the cursor on 'f' in 'first'
+ curwin('set_cursor', {1, 15})
+
+ set_text(0, 15, 2, 11, {
+ 'the line we do not want',
+ 'but hopefully',
+ })
+
+ eq({
+ 'This should be the line we do not want',
+ 'but hopefully the last one',
+ }, get_lines(0, -1, true))
+ -- cursor should stay at the same position
+ eq({1, 15}, curwin('get_cursor'))
+ end)
+
+ it('adjusts cursor column to keep it valid if start_row got smaller', function()
+ insert([[
+ This should be first
+ then there is a line we do not want
+ and finally the last one]])
+
+ -- position the cursor on 't' in 'first'
+ curwin('set_cursor', {1, 19})
+
+ local cursor = exec_lua([[
+ vim.api.nvim_buf_set_text(0, 0, 15, 2, 24, {'last'})
+ return vim.api.nvim_win_get_cursor(0)
+ ]])
+
+ eq({ 'This should be last' }, get_lines(0, -1, true))
+ -- cursor should end up on 't' in 'last'
+ eq({1, 18}, curwin('get_cursor'))
+ -- immediate call to nvim_win_get_cursor should have returned the same position
+ eq({1, 18}, cursor)
+ end)
+
+ it('adjusts cursor column to keep it valid if start_row got smaller in INSERT mode', function()
+ insert([[
+ This should be first
+ then there is a line we do not want
+ and finally the last one]])
+
+ -- position the cursor on 't' in 'first'
+ curwin('set_cursor', {1, 19})
+ -- enter INSERT mode to treat cursor as being after 't'
+ feed('a')
+
+ local cursor = exec_lua([[
+ vim.api.nvim_buf_set_text(0, 0, 15, 2, 24, {'last'})
+ return vim.api.nvim_win_get_cursor(0)
+ ]])
+
+ eq({ 'This should be last' }, get_lines(0, -1, true))
+ -- cursor should end up after 't' in 'last'
+ eq({1, 19}, curwin('get_cursor'))
+ -- immediate call to nvim_win_get_cursor should have returned the same position
+ eq({1, 19}, cursor)
+ end)
+
+ it('adjusts cursor column to keep it valid in a row after start_row if it got smaller', function()
+ insert([[
+ This should be first
+ then there is a line we do not want
+ and finally the last one]])
+
+ -- position the cursor on 'w' in 'want'
+ curwin('set_cursor', {2, 31})
+
+ local cursor = exec_lua([[
+ vim.api.nvim_buf_set_text(0, 0, 15, 2, 11, {
+ '1',
+ 'then 2',
+ 'and then',
+ })
+ return vim.api.nvim_win_get_cursor(0)
+ ]])
+
+ eq({
+ 'This should be 1',
+ 'then 2',
+ 'and then the last one',
+ }, get_lines(0, -1, true))
+ -- cursor column should end up at the end of a row
+ eq({2, 5}, curwin('get_cursor'))
+ -- immediate call to nvim_win_get_cursor should have returned the same position
+ eq({2, 5}, cursor)
+ end)
+
+ it('adjusts cursor column to keep it valid in a row after start_row if it got smaller in INSERT mode', function()
+ insert([[
+ This should be first
+ then there is a line we do not want
+ and finally the last one]])
+
+ -- position the cursor on 'w' in 'want'
+ curwin('set_cursor', {2, 31})
+ -- enter INSERT mode
+ feed('a')
+
+ local cursor = exec_lua([[
+ vim.api.nvim_buf_set_text(0, 0, 15, 2, 11, {
+ '1',
+ 'then 2',
+ 'and then',
+ })
+ return vim.api.nvim_win_get_cursor(0)
+ ]])
+
+ eq({
+ 'This should be 1',
+ 'then 2',
+ 'and then the last one',
+ }, get_lines(0, -1, true))
+ -- cursor column should end up at the end of a row
+ eq({2, 6}, curwin('get_cursor'))
+ -- immediate call to nvim_win_get_cursor should have returned the same position
+ eq({2, 6}, cursor)
+ end)
+
+ it('adjusts cursor line and column to keep it inside replacement range', function()
+ insert([[
+ This should be first
+ then there is a line we do not want
+ and finally the last one]])
+
+ -- position the cursor on 'n' in 'finally'
+ curwin('set_cursor', {3, 6})
+
+ local cursor = exec_lua([[
+ vim.api.nvim_buf_set_text(0, 0, 15, 2, 11, {
+ 'the line we do not want',
+ 'but hopefully',
+ })
+ return vim.api.nvim_win_get_cursor(0)
+ ]])
+
+ eq({
+ 'This should be the line we do not want',
+ 'but hopefully the last one',
+ }, get_lines(0, -1, true))
+ -- cursor should end up on 'y' in 'hopefully'
+ -- to stay in the range, because it got smaller
+ eq({2, 12}, curwin('get_cursor'))
+ -- immediate call to nvim_win_get_cursor should have returned the same position
+ eq({2, 12}, cursor)
+ end)
+
+ it('adjusts cursor line and column if replacement is empty', function()
+ insert([[
+ This should be first
+ then there is a line we do not want
+ and finally the last one]])
+
+ -- position the cursor on 'r' in 'there'
+ curwin('set_cursor', {2, 8})
+
+ local cursor = exec_lua([[
+ vim.api.nvim_buf_set_text(0, 0, 15, 2, 12, {})
+ return vim.api.nvim_win_get_cursor(0)
+ ]])
+
+ eq({ 'This should be the last one' }, get_lines(0, -1, true))
+ -- cursor should end up on the next column after deleted range
+ eq({1, 15}, curwin('get_cursor'))
+ -- immediate call to nvim_win_get_cursor should have returned the same position
+ eq({1, 15}, cursor)
+ end)
+
+ it('adjusts cursor line and column if replacement is empty and start_col == 0', function()
+ insert([[
+ This should be first
+ then there is a line we do not want
+ and finally the last one]])
+
+ -- position the cursor on 'r' in 'there'
+ curwin('set_cursor', {2, 8})
+
+ local cursor = exec_lua([[
+ vim.api.nvim_buf_set_text(0, 0, 0, 2, 4, {})
+ return vim.api.nvim_win_get_cursor(0)
+ ]])
+
+ eq({ 'finally the last one' }, get_lines(0, -1, true))
+ -- cursor should end up in column 0
+ eq({1, 0}, curwin('get_cursor'))
+ -- immediate call to nvim_win_get_cursor should have returned the same position
+ eq({1, 0}, cursor)
+ end)
+
+ it('adjusts cursor column if replacement ends at cursor row, after cursor column', function()
+ insert([[
+ This should be first
+ then there is a line we do not want
+ and finally the last one]])
+
+ -- position the cursor on 'y' in 'finally'
+ curwin('set_cursor', {3, 10})
+ set_text(0, 15, 2, 11, { '1', 'this 2', 'and then' })
+
+ eq({
+ 'This should be 1',
+ 'this 2',
+ 'and then the last one',
+ }, get_lines(0, -1, true))
+ -- cursor should end up on 'n' in 'then'
+ eq({3, 7}, curwin('get_cursor'))
+ end)
+
+ it('adjusts cursor column if replacement ends at cursor row, at cursor column in INSERT mode', function()
+ insert([[
+ This should be first
+ then there is a line we do not want
+ and finally the last one]])
+
+ -- position the cursor on 'y' at 'finally'
+ curwin('set_cursor', {3, 10})
+ -- enter INSERT mode to treat cursor as being between 'l' and 'y'
+ feed('i')
+ set_text(0, 15, 2, 11, { '1', 'this 2', 'and then' })
+
+ eq({
+ 'This should be 1',
+ 'this 2',
+ 'and then the last one',
+ }, get_lines(0, -1, true))
+ -- cursor should end up after 'n' in 'then'
+ eq({3, 8}, curwin('get_cursor'))
+ end)
+
+ it('adjusts cursor column if replacement is inside of a single line', function()
+ insert([[
+ This should be first
+ then there is a line we do not want
+ and finally the last one]])
+
+ -- position the cursor on 'y' in 'finally'
+ curwin('set_cursor', {3, 10})
+ set_text(2, 4, 2, 11, { 'then' })
+
+ eq({
+ 'This should be first',
+ 'then there is a line we do not want',
+ 'and then the last one',
+ }, get_lines(0, -1, true))
+ -- cursor should end up on 'n' in 'then'
+ eq({3, 7}, curwin('get_cursor'))
+ end)
+
+ it('does not move cursor column after end of a line', function()
+ insert([[
+ This should be the only line here
+ !!!]])
+
+ -- position cursor on the last '1'
+ curwin('set_cursor', {2, 2})
+
+ local cursor = exec_lua([[
+ vim.api.nvim_buf_set_text(0, 0, 33, 1, 3, {})
+ return vim.api.nvim_win_get_cursor(0)
+ ]])
+
+ eq({ 'This should be the only line here' }, get_lines(0, -1, true))
+ -- cursor should end up on '!'
+ eq({1, 32}, curwin('get_cursor'))
+ -- immediate call to nvim_win_get_cursor should have returned the same position
+ eq({1, 32}, cursor)
+ end)
+
+ it('does not move cursor column before start of a line', function()
+ insert('\n!!!')
+
+ -- position cursor on the last '1'
+ curwin('set_cursor', {2, 2})
+
+ local cursor = exec_lua([[
+ vim.api.nvim_buf_set_text(0, 0, 0, 1, 3, {})
+ return vim.api.nvim_win_get_cursor(0)
+ ]])
+
+ eq({ '' }, get_lines(0, -1, true))
+ -- cursor should end up on '!'
+ eq({1, 0}, curwin('get_cursor'))
+ -- immediate call to nvim_win_get_cursor should have returned the same position
+ eq({1, 0}, cursor)
+ end)
+
+ describe('with virtualedit', function()
+ it('adjusts cursor line and column to keep it inside replacement range if cursor is not after eol', function()
+ insert([[
+ This should be first
+ then there is a line we do not want
+ and finally the last one]])
+
+ -- position cursor on 't' in 'want'
+ curwin('set_cursor', {2, 34})
+ -- turn on virtualedit
+ command('set virtualedit=all')
+
+ local cursor = exec_lua([[
+ vim.api.nvim_buf_set_text(0, 0, 15, 2, 11, {
+ 'the line we do not want',
+ 'but hopefully',
+ })
+ return vim.api.nvim_win_get_cursor(0)
+ ]])
+
+ eq({
+ 'This should be the line we do not want',
+ 'but hopefully the last one',
+ }, get_lines(0, -1, true))
+ -- cursor should end up on 'y' in 'hopefully'
+ -- to stay in the range
+ eq({2, 12}, curwin('get_cursor'))
+ -- immediate call to nvim_win_get_cursor should have returned the same position
+ eq({2, 12}, cursor)
+ -- coladd should be 0
+ eq(0, exec_lua([[
+ return vim.fn.winsaveview().coladd
+ ]]))
+ end)
+
+ it('does not change cursor screen column when cursor is after eol and row got shorter', function()
+ insert([[
+ This should be first
+ then there is a line we do not want
+ and finally the last one]])
+
+ -- position cursor on 't' in 'want'
+ curwin('set_cursor', {2, 34})
+ -- turn on virtualedit
+ command('set virtualedit=all')
+ -- move cursor after eol
+ exec_lua([[
+ vim.fn.winrestview({ coladd = 5 })
+ ]])
+
+ local cursor = exec_lua([[
+ vim.api.nvim_buf_set_text(0, 0, 15, 2, 11, {
+ 'the line we do not want',
+ 'but hopefully',
+ })
+ return vim.api.nvim_win_get_cursor(0)
+ ]])
+
+ eq({
+ 'This should be the line we do not want',
+ 'but hopefully the last one',
+ }, get_lines(0, -1, true))
+ -- cursor should end up at eol of a new row
+ eq({2, 26}, curwin('get_cursor'))
+ -- immediate call to nvim_win_get_cursor should have returned the same position
+ eq({2, 26}, cursor)
+ -- coladd should be increased so that cursor stays in the same screen column
+ eq(13, exec_lua([[
+ return vim.fn.winsaveview().coladd
+ ]]))
+ end)
+
+ it('does not change cursor screen column when cursor is after eol and row got longer', function()
+ insert([[
+ This should be first
+ then there is a line we do not want
+ and finally the last one]])
+
+ -- position cursor on 't' in 'first'
+ curwin('set_cursor', {1, 19})
+ -- turn on virtualedit
+ command('set virtualedit=all')
+ -- move cursor after eol
+ exec_lua([[
+ vim.fn.winrestview({ coladd = 21 })
+ ]])
+
+ local cursor = exec_lua([[
+ vim.api.nvim_buf_set_text(0, 0, 15, 2, 11, {
+ 'the line we do not want',
+ 'but hopefully',
+ })
+ return vim.api.nvim_win_get_cursor(0)
+ ]])
+
+ eq({
+ 'This should be the line we do not want',
+ 'but hopefully the last one',
+ }, get_lines(0, -1, true))
+ -- cursor should end up at eol of a new row
+ eq({1, 38}, curwin('get_cursor'))
+ -- immediate call to nvim_win_get_cursor should have returned the same position
+ eq({1, 38}, cursor)
+ -- coladd should be increased so that cursor stays in the same screen column
+ eq(2, exec_lua([[
+ return vim.fn.winsaveview().coladd
+ ]]))
+ end)
+
+ it('does not change cursor screen column when cursor is after eol and row extended past cursor column', function()
+ insert([[
+ This should be first
+ then there is a line we do not want
+ and finally the last one]])
+
+ -- position cursor on 't' in 'first'
+ curwin('set_cursor', {1, 19})
+ -- turn on virtualedit
+ command('set virtualedit=all')
+ -- move cursor after eol just a bit
+ exec_lua([[
+ vim.fn.winrestview({ coladd = 3 })
+ ]])
+
+ local cursor = exec_lua([[
+ vim.api.nvim_buf_set_text(0, 0, 15, 2, 11, {
+ 'the line we do not want',
+ 'but hopefully',
+ })
+ return vim.api.nvim_win_get_cursor(0)
+ ]])
+
+ eq({
+ 'This should be the line we do not want',
+ 'but hopefully the last one',
+ }, get_lines(0, -1, true))
+ -- cursor should stay at the same screen column
+ eq({1, 22}, curwin('get_cursor'))
+ -- immediate call to nvim_win_get_cursor should have returned the same position
+ eq({1, 22}, cursor)
+ -- coladd should become 0
+ eq(0, exec_lua([[
+ return vim.fn.winsaveview().coladd
+ ]]))
+ end)
+
+ it('does not change cursor screen column when cursor is after eol and row range decreased', function()
+ insert([[
+ This should be first
+ then there is a line we do not want
+ and one more
+ and finally the last one]])
+
+ -- position cursor on 'e' in 'more'
+ curwin('set_cursor', {3, 11})
+ -- turn on virtualedit
+ command('set virtualedit=all')
+ -- move cursor after eol
+ exec_lua([[
+ vim.fn.winrestview({ coladd = 28 })
+ ]])
+
+ local cursor = exec_lua([[
+ vim.api.nvim_buf_set_text(0, 0, 15, 3, 11, {
+ 'the line we do not want',
+ 'but hopefully',
+ })
+ return vim.api.nvim_win_get_cursor(0)
+ ]])
+
+ eq({
+ 'This should be the line we do not want',
+ 'but hopefully the last one',
+ }, get_lines(0, -1, true))
+ -- cursor should end up at eol of a new row
+ eq({2, 26}, curwin('get_cursor'))
+ -- immediate call to nvim_win_get_cursor should have returned the same position
+ eq({2, 26}, cursor)
+ -- coladd should be increased so that cursor stays in the same screen column
+ eq(13, exec_lua([[
+ return vim.fn.winsaveview().coladd
+ ]]))
+ end)
+ end)
+ end)
+
+ describe('when cursor is at end_row and after end_col', function()
+ it('adjusts cursor column when only a newline is added or deleted', function()
+ insert([[
+ first line
+ second
+ line]])
+
+ -- position the cursor on 'i'
+ curwin('set_cursor', {3, 2})
+ set_text(1, 6, 2, 0, {})
+ eq({'first line', 'second line'}, get_lines(0, -1, true))
+ -- cursor should stay on 'i'
+ eq({2, 8}, curwin('get_cursor'))
+
+ -- add a newline back
+ set_text(1, 6, 1, 6, {'', ''})
+ eq({'first line', 'second', ' line'}, get_lines(0, -1, true))
+ -- cursor should return back to the original position
+ eq({3, 2}, curwin('get_cursor'))
+ end)
+
+ it('adjusts cursor column if the range is not bound to either start or end of a line', function()
+ insert([[
+ This should be first
+ then there is a line we do not want
+ and finally the last one]])
+
+ -- position the cursor on 'h' in 'the'
+ curwin('set_cursor', {3, 13})
+ set_text(0, 14, 2, 11, {})
+ eq({'This should be the last one'}, get_lines(0, -1, true))
+ -- cursor should stay on 'h'
+ eq({1, 16}, curwin('get_cursor'))
+ -- add deleted lines back
+ set_text(0, 14, 0, 14, {
+ ' first',
+ 'then there is a line we do not want',
+ 'and finally',
+ })
+ eq({
+ 'This should be first',
+ 'then there is a line we do not want',
+ 'and finally the last one',
+ }, get_lines(0, -1, true))
+ -- cursor should return back to the original position
+ eq({3, 13}, curwin('get_cursor'))
+ end)
+
+ it('adjusts cursor column if replacing lines in range, not just deleting and adding', function()
+ insert([[
+ This should be first
+ then there is a line we do not want
+ and finally the last one]])
+
+ -- position the cursor on 's' in 'last'
+ curwin('set_cursor', {3, 18})
+ set_text(0, 15, 2, 11, {
+ 'the line we do not want',
+ 'but hopefully',
+ })
+
+ eq({
+ 'This should be the line we do not want',
+ 'but hopefully the last one',
+ }, get_lines(0, -1, true))
+ -- cursor should stay on 's'
+ eq({2, 20}, curwin('get_cursor'))
+
+ set_text(0, 15, 1, 13, {
+ 'first',
+ 'then there is a line we do not want',
+ 'and finally',
+ })
+
+ eq({
+ 'This should be first',
+ 'then there is a line we do not want',
+ 'and finally the last one',
+ }, get_lines(0, -1, true))
+ -- cursor should return back to the original position
+ eq({3, 18}, curwin('get_cursor'))
+ end)
+
+ it('does not move cursor column after end of a line', function()
+ insert([[
+ This should be the only line here
+ ]])
+
+ -- position cursor at the empty line
+ curwin('set_cursor', {2, 0})
+
+ local cursor = exec_lua([[
+ vim.api.nvim_buf_set_text(0, 0, 33, 1, 0, {'!'})
+ return vim.api.nvim_win_get_cursor(0)
+ ]])
+
+ eq({ 'This should be the only line here!' }, get_lines(0, -1, true))
+ -- cursor should end up on '!'
+ eq({1, 33}, curwin('get_cursor'))
+ -- immediate call to nvim_win_get_cursor should have returned the same position
+ eq({1, 33}, cursor)
+ end)
+
+ it('does not move cursor column before start of a line', function()
+ insert('\n')
+
+ eq({ '', '' }, get_lines(0, -1, true))
+
+ -- position cursor on the last '1'
+ curwin('set_cursor', {2, 2})
+
+ local cursor = exec_lua([[
+ vim.api.nvim_buf_set_text(0, 0, 0, 1, 0, {''})
+ return vim.api.nvim_win_get_cursor(0)
+ ]])
+
+ eq({ '' }, get_lines(0, -1, true))
+ -- cursor should end up on '!'
+ eq({1, 0}, curwin('get_cursor'))
+ -- immediate call to nvim_win_get_cursor should have returned the same position
+ eq({1, 0}, cursor)
+ end)
+ end)
+
it('can handle NULs', function()
set_text(0, 0, 0, 0, {'ab\0cd'})
eq('ab\0cd', curbuf_depr('get_line', 0))
@@ -553,20 +1640,20 @@ describe('api/buf', function()
insert([[
hello foo!
text]])
- eq('start_row out of bounds', pcall_err(set_text, 2, 0, 3, 0, {}))
- eq('start_row out of bounds', pcall_err(set_text, -3, 0, 0, 0, {}))
- eq('end_row out of bounds', pcall_err(set_text, 0, 0, 2, 0, {}))
- eq('end_row out of bounds', pcall_err(set_text, 0, 0, -3, 0, {}))
- eq('start_col out of bounds', pcall_err(set_text, 1, 5, 1, 5, {}))
- eq('end_col out of bounds', pcall_err(set_text, 1, 0, 1, 5, {}))
+ eq("Invalid 'start_row': out of range", pcall_err(set_text, 2, 0, 3, 0, {}))
+ eq("Invalid 'start_row': out of range", pcall_err(set_text, -3, 0, 0, 0, {}))
+ eq("Invalid 'end_row': out of range", pcall_err(set_text, 0, 0, 2, 0, {}))
+ eq("Invalid 'end_row': out of range", pcall_err(set_text, 0, 0, -3, 0, {}))
+ eq("Invalid 'start_col': out of range", pcall_err(set_text, 1, 5, 1, 5, {}))
+ eq("Invalid 'end_col': out of range", pcall_err(set_text, 1, 0, 1, 5, {}))
end)
it('errors when start is greater than end', function()
insert([[
hello foo!
text]])
- eq('start is higher than end', pcall_err(set_text, 1, 0, 0, 0, {}))
- eq('start is higher than end', pcall_err(set_text, 0, 1, 0, 0, {}))
+ eq("'start' is higher than 'end'", pcall_err(set_text, 1, 0, 0, 0, {}))
+ eq("'start' is higher than 'end'", pcall_err(set_text, 0, 1, 0, 0, {}))
end)
it('no heap-use-after-free when called consecutively #19643', function()
@@ -579,6 +1666,139 @@ describe('api/buf', function()
]])
eq({'one', 'two'}, get_lines(0, 2, true))
end)
+
+ describe('handles topline', function()
+ local screen
+ before_each(function()
+ screen = Screen.new(20, 12)
+ screen:set_default_attr_ids {
+ [1] = {bold = true, foreground = Screen.colors.Blue1};
+ [2] = {reverse = true, bold = true};
+ [3] = {reverse = true};
+ }
+ screen:attach()
+ meths.buf_set_lines(0, 0, -1, 1, {"aaa", "bbb", "ccc", "ddd", "www", "xxx", "yyy", "zzz"})
+ meths.set_option_value('modified', false, {})
+ end)
+
+ it('of current window', function()
+ local win = meths.get_current_win()
+ local buf = meths.get_current_buf()
+
+ command('new | wincmd w')
+ meths.win_set_cursor(win, {8,0})
+
+ screen:expect{grid=[[
+ |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {3:[No Name] }|
+ www |
+ xxx |
+ yyy |
+ ^zzz |
+ {2:[No Name] }|
+ |
+ ]]}
+ meths.buf_set_text(buf, 0,3, 1,0, {"X"})
+
+ screen:expect{grid=[[
+ |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {3:[No Name] }|
+ www |
+ xxx |
+ yyy |
+ ^zzz |
+ {2:[No Name] [+] }|
+ |
+ ]]}
+ end)
+
+ it('of non-current window', function()
+ local win = meths.get_current_win()
+ local buf = meths.get_current_buf()
+
+ command('new')
+ meths.win_set_cursor(win, {8,0})
+
+ screen:expect{grid=[[
+ ^ |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {2:[No Name] }|
+ www |
+ xxx |
+ yyy |
+ zzz |
+ {3:[No Name] }|
+ |
+ ]]}
+
+ meths.buf_set_text(buf, 0,3, 1,0, {"X"})
+ screen:expect{grid=[[
+ ^ |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {2:[No Name] }|
+ www |
+ xxx |
+ yyy |
+ zzz |
+ {3:[No Name] [+] }|
+ |
+ ]]}
+ end)
+
+ it('of split windows with same buffer', function()
+ local win = meths.get_current_win()
+ local buf = meths.get_current_buf()
+
+ command('split')
+ meths.win_set_cursor(win, {8,0})
+ meths.win_set_cursor(0, {1,1})
+
+ screen:expect{grid=[[
+ a^aa |
+ bbb |
+ ccc |
+ ddd |
+ www |
+ {2:[No Name] }|
+ www |
+ xxx |
+ yyy |
+ zzz |
+ {3:[No Name] }|
+ |
+ ]]}
+ meths.buf_set_text(buf, 0,3, 1,0, {"X"})
+
+ screen:expect{grid=[[
+ a^aaXbbb |
+ ccc |
+ ddd |
+ www |
+ xxx |
+ {2:[No Name] [+] }|
+ www |
+ xxx |
+ yyy |
+ zzz |
+ {3:[No Name] [+] }|
+ |
+ ]]}
+ end)
+ end)
end)
describe_lua_and_rpc('nvim_buf_get_text', function(api)
@@ -611,7 +1831,7 @@ describe('api/buf', function()
end)
it('errors when start is greater than end', function()
- eq('start is higher than end', pcall_err(get_text, 1, 0, 0, 0, {}))
+ eq("'start' is higher than 'end'", pcall_err(get_text, 1, 0, 0, 0, {}))
eq('start_col must be less than end_col', pcall_err(get_text, 0, 1, 0, 0, {}))
end)
end)
@@ -630,19 +1850,19 @@ describe('api/buf', function()
eq('Index out of bounds', pcall_err(get_offset, 6))
eq('Index out of bounds', pcall_err(get_offset, -1))
- curbufmeths.set_option('eol', false)
- curbufmeths.set_option('fixeol', false)
+ meths.set_option_value('eol', false, {})
+ meths.set_option_value('fixeol', false, {})
eq(28, get_offset(5))
-- fileformat is ignored
- curbufmeths.set_option('fileformat', 'dos')
+ meths.set_option_value('fileformat', 'dos', {})
eq(0, get_offset(0))
eq(6, get_offset(1))
eq(15, get_offset(2))
eq(16, get_offset(3))
eq(24, get_offset(4))
eq(28, get_offset(5))
- curbufmeths.set_option('eol', true)
+ meths.set_option_value('eol', true, {})
eq(29, get_offset(5))
command("set hidden")
@@ -650,6 +1870,18 @@ describe('api/buf', function()
eq(6, bufmeths.get_offset(1,1))
command("bunload! 1")
eq(-1, bufmeths.get_offset(1,1))
+ eq(-1, bufmeths.get_offset(1,0))
+ end)
+
+ it('works in empty buffer', function()
+ eq(0, get_offset(0))
+ eq(1, get_offset(1))
+ end)
+
+ it('works in buffer with one line inserted', function()
+ feed('itext')
+ eq(0, get_offset(0))
+ eq(5, get_offset(1))
end)
end)
@@ -697,23 +1929,23 @@ describe('api/buf', function()
end)
end)
- describe('nvim_buf_get_option, nvim_buf_set_option', function()
+ describe('nvim_get_option_value, nvim_set_option_value', function()
it('works', function()
- eq(8, curbuf('get_option', 'shiftwidth'))
- curbuf('set_option', 'shiftwidth', 4)
- eq(4, curbuf('get_option', 'shiftwidth'))
+ eq(8, nvim('get_option_value', 'shiftwidth', {}))
+ nvim('set_option_value', 'shiftwidth', 4, {})
+ eq(4, nvim('get_option_value', 'shiftwidth', {}))
-- global-local option
- curbuf('set_option', 'define', 'test')
- eq('test', curbuf('get_option', 'define'))
+ nvim('set_option_value', 'define', 'test', {buf = 0})
+ eq('test', nvim('get_option_value', 'define', {buf = 0}))
-- Doesn't change the global value
- eq([[^\s*#\s*define]], nvim('get_option', 'define'))
+ eq("", nvim('get_option_value', 'define', {scope='global'}))
end)
it('returns values for unset local options', function()
-- 'undolevels' is only set to its "unset" value when a new buffer is
-- created
command('enew')
- eq(-123456, curbuf('get_option', 'undolevels'))
+ eq(-123456, nvim('get_option_value', 'undolevels', {buf=0}))
end)
end)
diff --git a/test/functional/api/buffer_updates_spec.lua b/test/functional/api/buffer_updates_spec.lua
index d5f06c8f1f..80e29c1ff2 100644
--- a/test/functional/api/buffer_updates_spec.lua
+++ b/test/functional/api/buffer_updates_spec.lua
@@ -75,7 +75,7 @@ local function reopenwithfolds(b)
local tick = reopen(b, origlines)
-- use markers for folds, make all folds open by default
- command('setlocal foldmethod=marker foldlevel=20')
+ command('setlocal foldmethod=marker foldlevel=20 commentstring=/*%s*/')
-- add a fold
command('2,4fold')
@@ -762,7 +762,7 @@ describe('API: buffer events:', function()
it('returns a proper error on nonempty options dict', function()
clear()
local b = editoriginal(false)
- eq("unexpected key: builtin", pcall_err(buffer, 'attach', b, false, {builtin="asfd"}))
+ eq("Invalid 'opts' key: 'builtin'", pcall_err(buffer, 'attach', b, false, {builtin="asfd"}))
end)
it('nvim_buf_attach returns response after delay #8634', function()
diff --git a/test/functional/api/command_spec.lua b/test/functional/api/command_spec.lua
index d0fb26edc7..1ddb289ded 100644
--- a/test/functional/api/command_spec.lua
+++ b/test/functional/api/command_spec.lua
@@ -24,7 +24,7 @@ describe('nvim_get_commands', function()
eq({}, meths.get_commands({builtin=false}))
end)
- it('validates input', function()
+ it('validation', function()
eq('builtin=true not implemented', pcall_err(meths.get_commands,
{builtin=true}))
eq("Invalid key: 'foo'", pcall_err(meths.get_commands,
@@ -543,23 +543,19 @@ describe('nvim_create_user_command', function()
end)
it('does not allow invalid command names', function()
- matches("'name' must begin with an uppercase letter", pcall_err(exec_lua, [[
+ eq("Invalid command name (must start with uppercase): 'test'", pcall_err(exec_lua, [[
vim.api.nvim_create_user_command('test', 'echo "hi"', {})
]]))
-
- matches('Invalid command name', pcall_err(exec_lua, [[
+ eq("Invalid command name: 't@'", pcall_err(exec_lua, [[
vim.api.nvim_create_user_command('t@', 'echo "hi"', {})
]]))
-
- matches('Invalid command name', pcall_err(exec_lua, [[
+ eq("Invalid command name: 'T@st'", pcall_err(exec_lua, [[
vim.api.nvim_create_user_command('T@st', 'echo "hi"', {})
]]))
-
- matches('Invalid command name', pcall_err(exec_lua, [[
+ eq("Invalid command name: 'Test!'", pcall_err(exec_lua, [[
vim.api.nvim_create_user_command('Test!', 'echo "hi"', {})
]]))
-
- matches('Invalid command name', pcall_err(exec_lua, [[
+ eq("Invalid command name: '💩'", pcall_err(exec_lua, [[
vim.api.nvim_create_user_command('💩', 'echo "hi"', {})
]]))
end)
diff --git a/test/functional/api/extmark_spec.lua b/test/functional/api/extmark_spec.lua
index 00f5b25b8a..44a151cf6a 100644
--- a/test/functional/api/extmark_spec.lua
+++ b/test/functional/api/extmark_spec.lua
@@ -11,6 +11,7 @@ local insert = helpers.insert
local feed = helpers.feed
local clear = helpers.clear
local command = helpers.command
+local exec = helpers.exec
local meths = helpers.meths
local assert_alive = helpers.assert_alive
@@ -101,12 +102,31 @@ describe('API/extmarks', function()
ns2 = request('nvim_create_namespace', "my-fancy-plugin2")
end)
+ it('validation', function()
+ eq("Invalid 'end_col': expected Integer, got Array", pcall_err(set_extmark, ns, marks[2], 0, 0, { end_col = {}, end_row = 1 }))
+ eq("Invalid 'end_row': expected Integer, got Array", pcall_err(set_extmark, ns, marks[2], 0, 0, { end_col = 1, end_row = {} }))
+ eq("Invalid 'virt_text_pos': expected String, got Integer", pcall_err(set_extmark, ns, marks[2], 0, 0, { virt_text_pos = 0 }))
+ eq("Invalid 'virt_text_pos': 'foo'", pcall_err(set_extmark, ns, marks[2], 0, 0, { virt_text_pos = 'foo' }))
+ eq("Invalid 'hl_mode': expected String, got Integer", pcall_err(set_extmark, ns, marks[2], 0, 0, { hl_mode = 0 }))
+ eq("Invalid 'hl_mode': 'foo'", pcall_err(set_extmark, ns, marks[2], 0, 0, { hl_mode = 'foo' }))
+ eq("Invalid 'id': expected Integer, got Array", pcall_err(set_extmark, ns, {}, 0, 0, { end_col = 1, end_row = 1 }))
+ eq("Invalid mark position: expected 2 Integer items", pcall_err(get_extmarks, ns, {}, {-1, -1}))
+ eq("Invalid mark position: expected mark id Integer or 2-item Array", pcall_err(get_extmarks, ns, true, {-1, -1}))
+ -- No memory leak with virt_text, virt_lines, sign_text
+ eq("right_gravity is not a boolean", pcall_err(set_extmark, ns, marks[2], 0, 0, {
+ virt_text = {{'foo', 'Normal'}},
+ virt_lines = {{{'bar', 'Normal'}}},
+ sign_text = 'a',
+ right_gravity = 'baz',
+ }))
+ end)
+
it("can end extranges past final newline using end_col = 0", function()
set_extmark(ns, marks[1], 0, 0, {
end_col = 0,
end_row = 1
})
- eq("end_col value outside range",
+ eq("Invalid 'end_col': out of range",
pcall_err(set_extmark, ns, marks[2], 0, 0, { end_col = 1, end_row = 1 }))
end)
@@ -188,6 +208,23 @@ describe('API/extmarks', function()
eq({}, get_extmarks(ns2, {0, 0}, {-1, -1}))
end)
+ it('can undo with extmarks (#25147)', function()
+ feed('itest<esc>')
+ set_extmark(ns, 1, 0, 0)
+ set_extmark(ns, 2, 1, 0)
+ eq({ { 1, 0, 0 }, { 2, 1, 0 } }, get_extmarks(ns, {0, 0}, {-1, -1}))
+ feed('dd')
+ eq({ { 1, 1, 0 }, { 2, 1, 0 } }, get_extmarks(ns, {0, 0}, {-1, -1}))
+ curbufmeths.clear_namespace(ns, 0, -1)
+ eq({}, get_extmarks(ns, {0, 0}, {-1, -1}))
+ set_extmark(ns, 1, 0, 0, { right_gravity = false })
+ set_extmark(ns, 2, 1, 0, { right_gravity = false })
+ eq({ { 1, 0, 0 }, { 2, 1, 0 } }, get_extmarks(ns, {0, 0}, {-1, -1}))
+ feed('u')
+ eq({ { 1, 0, 0 }, { 2, 1, 0 } }, get_extmarks(ns, {0, 0}, {-1, -1}))
+ curbufmeths.clear_namespace(ns, 0, -1)
+ end)
+
it('querying for information and ranges', function()
--marks = {1, 2, 3}
--positions = {{0, 0,}, {0, 2}, {0, 3}}
@@ -733,7 +770,14 @@ describe('API/extmarks', function()
})
end)
- -- TODO(bfredl): add more tests!
+ it('can get overlapping extmarks', function()
+ set_extmark(ns, 1, 0, 0, {end_row = 5, end_col=0})
+ set_extmark(ns, 2, 2, 5, {end_row = 2, end_col=30})
+ set_extmark(ns, 3, 0, 5, {end_row = 2, end_col=10})
+ set_extmark(ns, 4, 0, 0, {end_row = 1, end_col=0})
+ eq({{ 2, 2, 5 }}, get_extmarks(ns, {2, 0}, {2, -1}, { overlap=false }))
+ eq({{ 1, 0, 0 }, { 3, 0, 5}, {2, 2, 5}}, get_extmarks(ns, {2, 0}, {2, -1}, { overlap=true }))
+ end)
end)
it('replace works', function()
@@ -1344,10 +1388,10 @@ describe('API/extmarks', function()
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(get_extmark_by_id, ns_invalid, marks[1]))
+ eq("Invalid 'ns_id': 3", pcall_err(set_extmark, ns_invalid, marks[1], positions[1][1], positions[1][2]))
+ eq("Invalid 'ns_id': 3", pcall_err(curbufmeths.del_extmark, ns_invalid, marks[1]))
+ eq("Invalid 'ns_id': 3", pcall_err(get_extmarks, ns_invalid, positions[1], positions[2]))
+ eq("Invalid 'ns_id': 3", pcall_err(get_extmark_by_id, ns_invalid, marks[1]))
end)
it('when col = line-length, set the mark on eol', function()
@@ -1362,13 +1406,13 @@ describe('API/extmarks', function()
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))
+ eq("Invalid 'col': out of 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("Invalid 'line': out of range", pcall_err(set_extmark, ns, marks[1], invalid_lnum, invalid_col))
eq({}, get_extmark_by_id(ns, marks[1]))
end)
@@ -1385,7 +1429,7 @@ describe('API/extmarks', function()
it('in read-only buffer', function()
command("view! runtime/doc/help.txt")
- eq(true, curbufmeths.get_option('ro'))
+ eq(true, meths.get_option_value('ro', {}))
local id = set_extmark(ns, 0, 0, 2)
eq({{id, 0, 2}}, get_extmarks(ns,0, -1))
end)
@@ -1398,12 +1442,12 @@ describe('API/extmarks', function()
end)
it('does not crash with append/delete/undo sequence', function()
- meths.exec([[
+ exec([[
let ns = nvim_create_namespace('myplugin')
call nvim_buf_set_extmark(0, ns, 0, 0, {})
call append(0, '')
%delete
- undo]],false)
+ undo]])
assert_alive()
end)
@@ -1435,7 +1479,7 @@ describe('API/extmarks', function()
feed('u')
-- handles pasting
- meths.exec([[let @a='asdfasdf']], false)
+ exec([[let @a='asdfasdf']])
feed([["ap]])
eq({ {1, 0, 0}, {2, 0, 8} },
meths.buf_get_extmarks(0, ns, 0, -1, {}))
@@ -1447,6 +1491,7 @@ describe('API/extmarks', function()
end_line = 1
})
eq({ {1, 0, 0, {
+ ns_id = 1,
end_col = 0,
end_row = 1,
right_gravity = true,
@@ -1454,52 +1499,187 @@ describe('API/extmarks', function()
}} }, get_extmarks(ns, 0, -1, {details=true}))
end)
+ it('in prompt buffer', function()
+ feed('dd')
+ local id = set_extmark(ns, marks[1], 0, 0, {})
+ meths.set_option_value('buftype', 'prompt', {})
+ feed('i<esc>')
+ eq({{id, 0, 2}}, get_extmarks(ns, 0, -1))
+ end)
+
it('can get details', function()
set_extmark(ns, marks[1], 0, 0, {
+ conceal = "c",
+ cursorline_hl_group = "Statement",
end_col = 0,
- end_row = 1,
- right_gravity = false,
end_right_gravity = true,
- priority = 0,
+ end_row = 1,
hl_eol = true,
- hl_mode = "blend",
hl_group = "String",
- virt_text = { { "text", "Statement" } },
- virt_text_pos = "right_align",
- virt_text_hide = true,
- virt_lines = { { { "lines", "Statement" } }},
+ hl_mode = "blend",
+ line_hl_group = "Statement",
+ number_hl_group = "Statement",
+ priority = 0,
+ right_gravity = false,
+ sign_hl_group = "Statement",
+ sign_text = ">>",
+ spell = true,
+ virt_lines = {
+ { { "lines", "Macro" }, { "???" } },
+ { { "stack", { "Type", "Search" } }, { "!!!" } },
+ },
virt_lines_above = true,
virt_lines_leftcol = true,
+ virt_text = { { "text", "Macro" }, { "???" }, { "stack", { "Type", "Search" } } },
+ virt_text_hide = true,
+ virt_text_pos = "right_align",
})
set_extmark(ns, marks[2], 0, 0, {
priority = 0,
- virt_text = { { "text", "Statement" } },
+ virt_text = { { "", "Macro" }, { "", { "Type", "Search" } }, { "" } },
virt_text_win_col = 1,
})
eq({0, 0, {
+ conceal = "c",
+ cursorline_hl_group = "Statement",
end_col = 0,
- end_row = 1,
- right_gravity = false,
end_right_gravity = true,
- priority = 0,
+ end_row = 1,
hl_eol = true,
- hl_mode = "blend",
hl_group = "String",
- virt_text = { { "text", "Statement" } },
- virt_text_pos = "right_align",
- virt_text_hide = true,
- virt_lines = { { { "lines", "Statement" } }},
+ hl_mode = "blend",
+ line_hl_group = "Statement",
+ ns_id = 1,
+ number_hl_group = "Statement",
+ priority = 0,
+ right_gravity = false,
+ sign_hl_group = "Statement",
+ sign_text = ">>",
+ spell = true,
+ virt_lines = {
+ { { "lines", "Macro" }, { "???" } },
+ { { "stack", { "Type", "Search" } }, { "!!!" } },
+ },
virt_lines_above = true,
virt_lines_leftcol = true,
+ virt_text = { { "text", "Macro" }, { "???" }, { "stack", { "Type", "Search" } } },
+ virt_text_hide = true,
+ virt_text_pos = "right_align",
} }, get_extmark_by_id(ns, marks[1], { details = true }))
eq({0, 0, {
+ ns_id = 1,
right_gravity = true,
priority = 0,
- virt_text = { { "text", "Statement" } },
+ virt_text = { { "", "Macro" }, { "", { "Type", "Search" } }, { "" } },
virt_text_hide = false,
virt_text_pos = "win_col",
virt_text_win_col = 1,
} }, get_extmark_by_id(ns, marks[2], { details = true }))
+ set_extmark(ns, marks[3], 0, 0, { cursorline_hl_group = "Statement" })
+ eq({0, 0, {
+ ns_id = 1,
+ cursorline_hl_group = "Statement",
+ priority = 4096,
+ right_gravity = true,
+ } }, get_extmark_by_id(ns, marks[3], { details = true }))
+ curbufmeths.clear_namespace(ns, 0, -1)
+ -- legacy sign mark includes sign name
+ command('sign define sign1 text=s1 texthl=Title linehl=LineNR numhl=Normal culhl=CursorLine')
+ command('sign place 1 name=sign1 line=1')
+ eq({ {1, 0, 0, {
+ cursorline_hl_group = 'CursorLine',
+ invalidate = true,
+ line_hl_group = 'LineNr',
+ ns_id = 0,
+ number_hl_group = 'Normal',
+ priority = 10,
+ right_gravity = true,
+ sign_hl_group = 'Title',
+ sign_name = 'sign1',
+ sign_text = 's1',
+ undo_restore = false
+ } } }, get_extmarks(-1, 0, -1, { details = true }))
+ end)
+
+ it('can get marks from anonymous namespaces', function()
+ ns = request('nvim_create_namespace', "")
+ ns2 = request('nvim_create_namespace', "")
+ set_extmark(ns, 1, 0, 0, {})
+ set_extmark(ns2, 2, 1, 0, {})
+ eq({{ 1, 0, 0, { ns_id = ns, right_gravity = true }},
+ { 2, 1, 0, { ns_id = ns2, right_gravity = true }}},
+ get_extmarks(-1, 0, -1, { details = true }))
+ end)
+
+ it('can filter by extmark properties', function()
+ set_extmark(ns, 1, 0, 0, {})
+ set_extmark(ns, 2, 0, 0, { hl_group = 'Normal' })
+ set_extmark(ns, 3, 0, 0, { sign_text = '>>' })
+ set_extmark(ns, 4, 0, 0, { virt_text = {{'text', 'Normal'}}})
+ set_extmark(ns, 5, 0, 0, { virt_lines = {{{ 'line', 'Normal' }}}})
+ eq(5, #get_extmarks(-1, 0, -1, {}))
+ eq({{ 2, 0, 0 }}, get_extmarks(-1, 0, -1, { type = 'highlight' }))
+ eq({{ 3, 0, 0 }}, get_extmarks(-1, 0, -1, { type = 'sign' }))
+ eq({{ 4, 0, 0 }}, get_extmarks(-1, 0, -1, { type = 'virt_text' }))
+ eq({{ 5, 0, 0 }}, get_extmarks(-1, 0, -1, { type = 'virt_lines' }))
+ end)
+
+ it("invalidated marks are deleted", function()
+ screen = Screen.new(40, 6)
+ screen:attach()
+ feed('dd6iaaa bbb ccc<CR><ESC>gg')
+ set_extmark(ns, 1, 0, 0, { invalidate = true, sign_text = 'S1' })
+ set_extmark(ns, 2, 1, 0, { invalidate = true, sign_text = 'S2' })
+ -- mark with invalidate is removed
+ command('d')
+ screen:expect([[
+ S2^aaa bbb ccc |
+ aaa bbb ccc |
+ aaa bbb ccc |
+ aaa bbb ccc |
+ aaa bbb ccc |
+ |
+ ]])
+ -- mark is restored with undo_restore == true
+ command('silent undo')
+ screen:expect([[
+ S1^aaa bbb ccc |
+ S2aaa bbb ccc |
+ aaa bbb ccc |
+ aaa bbb ccc |
+ aaa bbb ccc |
+ |
+ ]])
+ -- mark is deleted with undo_restore == false
+ set_extmark(ns, 1, 0, 0, { invalidate = true, undo_restore = false, sign_text = 'S1' })
+ set_extmark(ns, 2, 1, 0, { invalidate = true, undo_restore = false, sign_text = 'S2' })
+ command('1d 2')
+ eq(0, #get_extmarks(-1, 0, -1, {}))
+ -- mark is not removed when deleting bytes before the range
+ set_extmark(ns, 3, 0, 4, { invalidate = true, undo_restore = false,
+ hl_group = 'Error', end_col = 7 })
+ feed('dw')
+ eq(3, get_extmark_by_id(ns, 3, { details = true })[3].end_col)
+ -- mark is not removed when deleting bytes at the start of the range
+ feed('x')
+ eq(2, get_extmark_by_id(ns, 3, { details = true })[3].end_col)
+ -- mark is not removed when deleting bytes from the end of the range
+ feed('lx')
+ eq(1, get_extmark_by_id(ns, 3, { details = true})[3].end_col)
+ -- mark is not removed when deleting bytes beyond end of the range
+ feed('x')
+ eq(1, get_extmark_by_id(ns, 3, { details = true})[3].end_col)
+ -- mark is removed when all bytes in the range are deleted
+ feed('hx')
+ eq({}, get_extmark_by_id(ns, 3, {}))
+ -- multiline mark is not removed when start of its range is deleted
+ set_extmark(ns, 4, 1, 4, { undo_restore = false, invalidate = true,
+ hl_group = 'Error', end_col = 7, end_row = 3 })
+ feed('ddDdd')
+ eq({0, 0}, get_extmark_by_id(ns, 4, {}))
+ -- multiline mark is removed when entirety of its range is deleted
+ feed('vj2ed')
+ eq({}, get_extmark_by_id(ns, 4, {}))
end)
end)
diff --git a/test/functional/api/highlight_spec.lua b/test/functional/api/highlight_spec.lua
index 5941d4c68b..7d7d07e30e 100644
--- a/test/functional/api/highlight_spec.lua
+++ b/test/functional/api/highlight_spec.lua
@@ -57,9 +57,7 @@ describe('API: highlight',function()
eq(expected_rgb, nvim("get_hl_by_id", hl_id, true))
-- Test invalid id.
- local err, emsg = pcall(meths.get_hl_by_id, 30000, false)
- eq(false, err)
- eq('Invalid highlight id: 30000', string.match(emsg, 'Invalid.*'))
+ eq('Invalid highlight id: 30000', pcall_err(meths.get_hl_by_id, 30000, false))
-- Test all highlight properties.
command('hi NewHighlight gui=underline,bold,italic,reverse,strikethrough,altfont,nocombine')
@@ -70,22 +68,14 @@ describe('API: highlight',function()
eq(expected_undercurl, nvim("get_hl_by_id", hl_id, true))
-- Test nil argument.
- err, emsg = pcall(meths.get_hl_by_id, { nil }, false)
- eq(false, err)
eq('Wrong type for argument 1 when calling nvim_get_hl_by_id, expecting Integer',
- string.match(emsg, 'Wrong.*'))
+ pcall_err(meths.get_hl_by_id, { nil }, false))
-- Test 0 argument.
- err, emsg = pcall(meths.get_hl_by_id, 0, false)
- eq(false, err)
- eq('Invalid highlight id: 0',
- string.match(emsg, 'Invalid.*'))
+ eq('Invalid highlight id: 0', pcall_err(meths.get_hl_by_id, 0, false))
-- Test -1 argument.
- err, emsg = pcall(meths.get_hl_by_id, -1, false)
- eq(false, err)
- eq('Invalid highlight id: -1',
- string.match(emsg, 'Invalid.*'))
+ eq('Invalid highlight id: -1', pcall_err(meths.get_hl_by_id, -1, false))
-- Test highlight group without ctermbg value.
command('hi Normal ctermfg=red ctermbg=yellow')
@@ -119,22 +109,16 @@ describe('API: highlight',function()
eq(expected_normal, nvim("get_hl_by_name", 'Normal', true))
-- Test invalid name.
- local err, emsg = pcall(meths.get_hl_by_name , 'unknown_highlight', false)
- eq(false, err)
- eq('Invalid highlight name: unknown_highlight',
- string.match(emsg, 'Invalid.*'))
+ eq("Invalid highlight name: 'unknown_highlight'",
+ pcall_err(meths.get_hl_by_name , 'unknown_highlight', false))
-- Test nil argument.
- err, emsg = pcall(meths.get_hl_by_name , { nil }, false)
- eq(false, err)
eq('Wrong type for argument 1 when calling nvim_get_hl_by_name, expecting String',
- string.match(emsg, 'Wrong.*'))
+ pcall_err(meths.get_hl_by_name , { nil }, false))
-- Test empty string argument.
- err, emsg = pcall(meths.get_hl_by_name , '', false)
- eq(false, err)
- eq('Invalid highlight name: ',
- string.match(emsg, 'Invalid.*'))
+ eq('Invalid highlight name',
+ pcall_err(meths.get_hl_by_name , '', false))
-- Test "standout" attribute. #8054
eq({ underline = true, },
@@ -155,7 +139,7 @@ describe('API: highlight',function()
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("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")
@@ -171,9 +155,9 @@ describe('API: highlight',function()
it("nvim_buf_add_highlight to other buffer doesn't crash if undo is disabled #12873", function()
command('vsplit file')
- local err, _ = pcall(meths.buf_set_option, 1, 'undofile', false)
+ local err, _ = pcall(meths.set_option_value, 'undofile', false, { buf = 1 })
eq(true, err)
- err, _ = pcall(meths.buf_set_option, 1, 'undolevels', -1)
+ err, _ = pcall(meths.set_option_value, 'undolevels', -1, { buf = 1 })
eq(true, err)
err, _ = pcall(meths.buf_add_highlight, 1, -1, 'Question', 0, 0, -1)
eq(true, err)
@@ -214,7 +198,7 @@ describe("API: set highlight", function()
bold = true,
italic = true,
reverse = true,
- underline = true,
+ underdashed = true,
strikethrough = true,
altfont = true,
cterm = {
@@ -231,7 +215,7 @@ describe("API: set highlight", function()
bold = true,
italic = true,
reverse = true,
- underline = true,
+ underdashed = true,
strikethrough = true,
altfont = true,
}
@@ -253,25 +237,32 @@ describe("API: set highlight", function()
before_each(clear)
- it ("can set gui highlight", function()
+ it('validation', function()
+ eq("Invalid 'blend': out of range",
+ pcall_err(meths.set_hl, 0, 'Test_hl3', {fg='#FF00FF', blend=999}))
+ eq("Invalid 'blend': expected Integer, got Array",
+ pcall_err(meths.set_hl, 0, 'Test_hl3', {fg='#FF00FF', blend={}}))
+ end)
+
+ it("can set gui highlight", function()
local ns = get_ns()
meths.set_hl(ns, 'Test_hl', highlight1)
eq(highlight1, meths.get_hl_by_name('Test_hl', true))
end)
- it ("can set cterm highlight", function()
+ it("can set cterm highlight", function()
local ns = get_ns()
meths.set_hl(ns, 'Test_hl', highlight2_config)
eq(highlight2_result, meths.get_hl_by_name('Test_hl', false))
end)
- it ("can set empty cterm attr", function()
+ it("can set empty cterm attr", function()
local ns = get_ns()
meths.set_hl(ns, 'Test_hl', { cterm = {} })
eq({}, meths.get_hl_by_name('Test_hl', false))
end)
- it ("cterm attr defaults to gui attr", function()
+ it("cterm attr defaults to gui attr", function()
local ns = get_ns()
meths.set_hl(ns, 'Test_hl', highlight1)
eq({
@@ -280,14 +271,28 @@ describe("API: set highlight", function()
}, meths.get_hl_by_name('Test_hl', false))
end)
- it ("can overwrite attr for cterm", function()
+ it("can overwrite attr for cterm", function()
local ns = get_ns()
meths.set_hl(ns, 'Test_hl', highlight3_config)
eq(highlight3_result_gui, meths.get_hl_by_name('Test_hl', true))
eq(highlight3_result_cterm, meths.get_hl_by_name('Test_hl', false))
end)
- it ("can set a highlight in the global namespace", function()
+ it("only allows one underline attribute #22371", function()
+ local ns = get_ns()
+ meths.set_hl(ns, 'Test_hl', {
+ underdouble = true,
+ underdotted = true,
+ cterm = {
+ underline = true,
+ undercurl = true,
+ },
+ })
+ eq({ undercurl = true }, meths.get_hl_by_name('Test_hl', false))
+ eq({ underdotted = true }, meths.get_hl_by_name('Test_hl', true))
+ end)
+
+ it("can set a highlight in the global namespace", function()
meths.set_hl(0, 'Test_hl', highlight2_config)
eq('Test_hl xxx cterm=underline,reverse ctermfg=8 ctermbg=15 gui=underline,reverse',
exec_capture('highlight Test_hl'))
@@ -297,7 +302,7 @@ describe("API: set highlight", function()
exec_capture('highlight Test_hl'))
meths.set_hl(0, 'Test_hl2', highlight3_config)
- eq('Test_hl2 xxx cterm=italic,reverse,strikethrough,altfont,nocombine ctermfg=8 ctermbg=15 gui=bold,underline,italic,reverse,strikethrough,altfont guifg=#ff0000 guibg=#0032aa',
+ eq('Test_hl2 xxx cterm=italic,reverse,strikethrough,altfont,nocombine ctermfg=8 ctermbg=15 gui=bold,underdashed,italic,reverse,strikethrough,altfont guifg=#ff0000 guibg=#0032aa',
exec_capture('highlight Test_hl2'))
-- Colors are stored with the name they are defined, but
@@ -307,7 +312,7 @@ describe("API: set highlight", function()
exec_capture('highlight Test_hl3'))
end)
- it ("can modify a highlight in the global namespace", function()
+ it("can modify a highlight in the global namespace", function()
meths.set_hl(0, 'Test_hl3', { bg = 'red', fg = 'blue'})
eq('Test_hl3 xxx guifg=Blue guibg=Red',
exec_capture('highlight Test_hl3'))
@@ -328,17 +333,17 @@ describe("API: set highlight", function()
eq('Test_hl3 xxx ctermbg=9',
exec_capture('highlight Test_hl3'))
- eq("'redd' is not a valid color",
+ eq("Invalid highlight color: 'redd'",
pcall_err(meths.set_hl, 0, 'Test_hl3', {fg='redd'}))
- eq("'bleu' is not a valid color",
+ eq("Invalid highlight color: 'bleu'",
pcall_err(meths.set_hl, 0, 'Test_hl3', {ctermfg='bleu'}))
meths.set_hl(0, 'Test_hl3', {fg='#FF00FF'})
eq('Test_hl3 xxx guifg=#ff00ff',
exec_capture('highlight Test_hl3'))
- eq("'#FF00FF' is not a valid color",
+ eq("Invalid highlight color: '#FF00FF'",
pcall_err(meths.set_hl, 0, 'Test_hl3', {ctermfg='#FF00FF'}))
for _, fg_val in ipairs{ nil, 'NONE', 'nOnE', '', -1 } do
@@ -353,14 +358,298 @@ describe("API: set highlight", function()
end)
- it ("correctly sets 'Normal' internal properties", function()
+ it("correctly sets 'Normal' internal properties", function()
-- Normal has some special handling internally. #18024
meths.set_hl(0, 'Normal', {fg='#000083', bg='#0000F3'})
eq({foreground = 131, background = 243}, nvim("get_hl_by_name", 'Normal', true))
end)
it('does not segfault on invalid group name #20009', function()
- eq('Invalid highlight name: foo bar', pcall_err(meths.set_hl, 0, 'foo bar', {bold = true}))
+ eq("Invalid highlight name: 'foo bar'", pcall_err(meths.set_hl, 0, 'foo bar', {bold = true}))
assert_alive()
end)
end)
+
+describe('API: get highlight', function()
+ local highlight_color = {
+ fg = tonumber('0xff0000'),
+ bg = tonumber('0x0032aa'),
+ ctermfg = 8,
+ ctermbg = 15,
+ }
+ local highlight1 = {
+ bg = highlight_color.bg,
+ fg = highlight_color.fg,
+ bold = true, italic = true,
+ cterm = {bold = true, italic = true},
+ }
+ local highlight2 = {
+ ctermbg = highlight_color.ctermbg,
+ ctermfg = highlight_color.ctermfg,
+ underline = true, reverse = true,
+ cterm = {underline = true, reverse = true},
+ }
+ local highlight3_config = {
+ bg = highlight_color.bg,
+ fg = highlight_color.fg,
+ ctermbg = highlight_color.ctermbg,
+ ctermfg = highlight_color.ctermfg,
+ bold = true,
+ italic = true,
+ reverse = true,
+ underdashed = true,
+ strikethrough = true,
+ altfont = true,
+ cterm = {
+ italic = true,
+ reverse = true,
+ strikethrough = true,
+ altfont = true,
+ nocombine = true,
+ },
+ }
+ local highlight3_result = {
+ bg = highlight_color.bg,
+ fg = highlight_color.fg,
+ ctermbg = highlight_color.ctermbg,
+ ctermfg = highlight_color.ctermfg,
+ bold = true, italic = true, reverse = true, underdashed = true, strikethrough = true, altfont = true,
+ cterm = {italic = true, nocombine = true, reverse = true, strikethrough = true, altfont = true}
+ }
+
+ local function get_ns()
+ -- Test namespace filtering behavior
+ local ns2 = meths.create_namespace('Another_namespace')
+ meths.set_hl(ns2, 'Test_hl', { ctermfg = 23 })
+ meths.set_hl(ns2, 'Test_another_hl', { link = 'Test_hl' })
+ meths.set_hl(ns2, 'Test_hl_link', { link = 'Test_another_hl' })
+ meths.set_hl(ns2, 'Test_another_hl_link', { link = 'Test_hl_link' })
+
+ local ns = meths.create_namespace('Test_set_hl')
+ meths.set_hl_ns(ns)
+
+ return ns
+ end
+
+ before_each(clear)
+
+ it('validation', function()
+ eq("Invalid 'name': expected String, got Integer",
+ pcall_err(meths.get_hl, 0, { name = 177 }))
+ eq('Highlight id out of bounds', pcall_err(meths.get_hl, 0, { name = 'Test set hl' }))
+ end)
+
+ it('nvim_get_hl with create flag', function()
+ eq({}, nvim("get_hl", 0, {name = 'Foo', create = false}))
+ eq(0, funcs.hlexists('Foo'))
+ meths.get_hl(0, {name = 'Bar', create = true})
+ eq(1, funcs.hlexists('Bar'))
+ meths.get_hl(0, {name = 'FooBar'})
+ eq(1, funcs.hlexists('FooBar'))
+ end)
+
+ it('can get all highlights in current namespace', function()
+ local ns = get_ns()
+ meths.set_hl(ns, 'Test_hl', { bg = '#B4BEFE' })
+ meths.set_hl(ns, 'Test_hl_link', { link = 'Test_hl' })
+ eq({
+ Test_hl = {
+ bg = 11845374
+ },
+ Test_hl_link = {
+ link = 'Test_hl'
+ }
+ }, meths.get_hl(ns, {}))
+ end)
+
+ it('can get gui highlight', function()
+ local ns = get_ns()
+ meths.set_hl(ns, 'Test_hl', highlight1)
+ eq(highlight1, meths.get_hl(ns, { name = 'Test_hl' }))
+ end)
+
+ it('can get cterm highlight', function()
+ local ns = get_ns()
+ meths.set_hl(ns, 'Test_hl', highlight2)
+ eq(highlight2, meths.get_hl(ns, { name = 'Test_hl' }))
+ end)
+
+ it('can get empty cterm attr', function()
+ local ns = get_ns()
+ meths.set_hl(ns, 'Test_hl', { cterm = {} })
+ eq({}, meths.get_hl(ns, { name = 'Test_hl' }))
+ end)
+
+ it('cterm attr defaults to gui attr', function()
+ local ns = get_ns()
+ meths.set_hl(ns, 'Test_hl', highlight1)
+ eq(highlight1, meths.get_hl(ns, { name = 'Test_hl' }))
+ end)
+
+ it('can overwrite attr for cterm', function()
+ local ns = get_ns()
+ meths.set_hl(ns, 'Test_hl', highlight3_config)
+ eq(highlight3_result, meths.get_hl(ns, { name = 'Test_hl' }))
+ end)
+
+ it('only allows one underline attribute #22371', function()
+ local ns = get_ns()
+ meths.set_hl(ns, 'Test_hl', {
+ underdouble = true,
+ underdotted = true,
+ cterm = {
+ underline = true,
+ undercurl = true,
+ },
+ })
+ eq({ underdotted = true, cterm = { undercurl = true} },
+ meths.get_hl(ns, { name = 'Test_hl' }))
+ end)
+
+ it('can get a highlight in the global namespace', function()
+ meths.set_hl(0, 'Test_hl', highlight2)
+ eq(highlight2, meths.get_hl(0, { name = 'Test_hl' }))
+
+ meths.set_hl(0, 'Test_hl', { background = highlight_color.bg })
+ eq({
+ bg = 12970,
+ }, meths.get_hl(0, { name = 'Test_hl' }))
+
+ meths.set_hl(0, 'Test_hl2', highlight3_config)
+ eq(highlight3_result, meths.get_hl(0, { name = 'Test_hl2' }))
+
+ -- Colors are stored with the name they are defined, but
+ -- with canonical casing
+ meths.set_hl(0, 'Test_hl3', { bg = 'reD', fg = 'bLue' })
+ eq({
+ bg = 16711680,
+ fg = 255,
+ }, meths.get_hl(0, { name = 'Test_hl3' }))
+ end)
+
+ it('nvim_get_hl by id', function()
+ local hl_id = meths.get_hl_id_by_name('NewHighlight')
+
+ command(
+ 'hi NewHighlight cterm=underline ctermbg=green guifg=red guibg=yellow guisp=blue gui=bold'
+ )
+ eq({ fg = 16711680, bg = 16776960, sp = 255, bold = true,
+ ctermbg = 10, cterm = { underline = true },
+ }, meths.get_hl(0, { id = hl_id }))
+
+ -- Test 0 argument
+ eq('Highlight id out of bounds', pcall_err(meths.get_hl, 0, { id = 0 }))
+
+ eq(
+ "Invalid 'id': expected Integer, got String",
+ pcall_err(meths.get_hl, 0, { id = 'Test_set_hl' })
+ )
+
+ -- Test all highlight properties.
+ command('hi NewHighlight gui=underline,bold,italic,reverse,strikethrough,altfont,nocombine')
+ eq({ fg = 16711680, bg = 16776960, sp = 255,
+ altfont = true, bold = true, italic = true, nocombine = true, reverse = true, strikethrough = true, underline = true,
+ ctermbg = 10, cterm = {underline = true},
+ }, meths.get_hl(0, { id = hl_id }))
+
+ -- Test undercurl
+ command('hi NewHighlight gui=undercurl')
+ eq({ fg = 16711680, bg = 16776960, sp = 255, undercurl = true,
+ ctermbg = 10,
+ cterm = {underline = true},
+ }, meths.get_hl(0, { id = hl_id }))
+ end)
+
+ it('can correctly detect links', function()
+ command('hi String guifg=#a6e3a1')
+ command('hi link @string string')
+ command('hi link @string.cpp @string')
+ eq({ fg = 10937249 }, meths.get_hl(0, { name = 'String' }))
+ eq({ link = 'String' }, meths.get_hl(0, { name = '@string' }))
+ eq({ fg = 10937249 }, meths.get_hl(0, { name = '@string.cpp', link = false }))
+ end)
+
+ it('can get all attributes for a linked group', function()
+ command('hi Bar guifg=red')
+ command('hi Foo guifg=#00ff00 gui=bold,underline')
+ command('hi! link Foo Bar')
+ eq({ link = 'Bar', fg = tonumber('00ff00', 16), bold = true, underline = true }, meths.get_hl(0, { name = 'Foo', link = true }))
+ end)
+
+ it('can set link as well as other attributes', function()
+ command('hi Bar guifg=red')
+ local hl = { link = 'Bar', fg = tonumber('00ff00', 16), bold = true, cterm = { bold = true } }
+ meths.set_hl(0, 'Foo', hl)
+ eq(hl, meths.get_hl(0, { name = 'Foo', link = true }))
+ end)
+
+ it("doesn't contain unset groups", function()
+ local id = meths.get_hl_id_by_name "@foobar.hubbabubba"
+ ok(id > 0)
+
+ local data = meths.get_hl(0, {})
+ eq(nil, data["@foobar.hubbabubba"])
+ eq(nil, data["@foobar"])
+
+ command 'hi @foobar.hubbabubba gui=bold'
+ data = meths.get_hl(0, {})
+ eq({bold = true}, data["@foobar.hubbabubba"])
+ eq(nil, data["@foobar"])
+
+ -- @foobar.hubbabubba was explicitly cleared and thus shows up
+ -- but @foobar was never touched, and thus doesn't
+ command 'hi clear @foobar.hubbabubba'
+ data = meths.get_hl(0, {})
+ eq({}, data["@foobar.hubbabubba"])
+ eq(nil, data["@foobar"])
+ end)
+
+ it('should return default flag', function()
+ meths.set_hl(0, 'Tried', { fg = "#00ff00", default = true })
+ eq({ fg = tonumber('00ff00', 16), default = true }, meths.get_hl(0, { name = 'Tried' }))
+ end)
+
+ it('should not output empty gui and cterm #23474', function()
+ meths.set_hl(0, 'Foo', {default = true})
+ meths.set_hl(0, 'Bar', { default = true, fg = '#ffffff' })
+ meths.set_hl(0, 'FooBar', { default = true, fg = '#ffffff', cterm = {bold = true} })
+ meths.set_hl(0, 'FooBarA', { default = true, fg = '#ffffff', cterm = {bold = true,italic = true}})
+
+ eq('Foo xxx cleared',
+ exec_capture('highlight Foo'))
+ eq({default = true}, meths.get_hl(0, {name = 'Foo'}))
+ eq('Bar xxx guifg=#ffffff',
+ exec_capture('highlight Bar'))
+ eq('FooBar xxx cterm=bold guifg=#ffffff',
+ exec_capture('highlight FooBar'))
+ eq('FooBarA xxx cterm=bold,italic guifg=#ffffff',
+ exec_capture('highlight FooBarA'))
+ end)
+
+ it('can override exist highlight group by force #20323', function()
+ local white = tonumber('ffffff', 16)
+ local green = tonumber('00ff00', 16)
+ meths.set_hl(0, 'Foo', { fg=white })
+ meths.set_hl(0, 'Foo', { fg=green, force = true })
+ eq({ fg = green },meths.get_hl(0, {name = 'Foo'}))
+ meths.set_hl(0, 'Bar', {link = 'Comment', default = true})
+ meths.set_hl(0, 'Bar', {link = 'Foo',default = true, force = true})
+ eq({link ='Foo', default = true}, meths.get_hl(0, {name = 'Bar'}))
+ end)
+end)
+
+describe('API: set/get highlight namespace', function()
+ it('set/get highlight namespace', function()
+ eq(0, meths.get_hl_ns({}))
+ local ns = meths.create_namespace('')
+ meths.set_hl_ns(ns)
+ eq(ns, meths.get_hl_ns({}))
+ end)
+
+ it('set/get window highlight namespace', function()
+ eq(-1, meths.get_hl_ns({winid = 0}))
+ local ns = meths.create_namespace('')
+ meths.win_set_hl_ns(0, ns)
+ eq(ns, meths.get_hl_ns({winid = 0}))
+ end)
+end)
diff --git a/test/functional/api/keymap_spec.lua b/test/functional/api/keymap_spec.lua
index 5be4425162..434f117956 100644
--- a/test/functional/api/keymap_spec.lua
+++ b/test/functional/api/keymap_spec.lua
@@ -19,6 +19,20 @@ local sleep = helpers.sleep
local sid_api_client = -9
local sid_lua = -8
+local mode_bits_map = {
+ ['n'] = 0x01,
+ ['x'] = 0x02,
+ ['o'] = 0x04,
+ ['c'] = 0x08,
+ ['i'] = 0x10,
+ ['l'] = 0x20,
+ ['s'] = 0x40,
+ ['t'] = 0x80,
+ [' '] = 0x47,
+ ['v'] = 0x42,
+ ['!'] = 0x18,
+}
+
describe('nvim_get_keymap', function()
before_each(clear)
@@ -32,9 +46,12 @@ describe('nvim_get_keymap', function()
rhs='bar',
expr=0,
sid=0,
+ scriptversion=1,
buffer=0,
nowait=0,
mode='n',
+ mode_bits=0x01,
+ abbr=0,
noremap=1,
lnum=0,
}
@@ -81,6 +98,7 @@ describe('nvim_get_keymap', function()
-- The table will be the same except for the mode
local insert_table = shallowcopy(foo_bar_map_table)
insert_table['mode'] = 'i'
+ insert_table['mode_bits'] = 0x10
eq({insert_table}, meths.get_keymap('i'))
end)
@@ -258,8 +276,10 @@ describe('nvim_get_keymap', function()
silent=0,
expr=0,
sid=0,
+ scriptversion=1,
buffer=0,
nowait=0,
+ abbr=0,
noremap=1,
lnum=0,
}
@@ -268,6 +288,7 @@ describe('nvim_get_keymap', function()
ret.lhs = lhs
ret.rhs = rhs
ret.mode = mode
+ ret.mode_bits = mode_bits_map[mode]
return ret
end
@@ -323,10 +344,13 @@ describe('nvim_get_keymap', function()
lhsraw='| |',
rhs='| |',
mode='n',
+ mode_bits=0x01,
+ abbr=0,
script=0,
silent=0,
expr=0,
sid=0,
+ scriptversion=1,
buffer=0,
nowait=0,
noremap=1,
@@ -365,15 +389,18 @@ describe('nvim_get_keymap', function()
silent=0,
expr=0,
sid=sid_lua,
+ scriptversion=1,
buffer=0,
nowait=0,
mode='n',
+ mode_bits=0x01,
+ abbr=0,
noremap=0,
lnum=0,
}, mapargs[1])
end)
- it ('can handle map descriptions', function()
+ it('can handle map descriptions', function()
meths.set_keymap('n', 'lhs', 'rhs', {desc="map description"})
eq({
lhs='lhs',
@@ -383,9 +410,12 @@ describe('nvim_get_keymap', function()
silent=0,
expr=0,
sid=sid_api_client,
+ scriptversion=1,
buffer=0,
nowait=0,
mode='n',
+ mode_bits=0x01,
+ abbr=0,
noremap=0,
lnum=0,
desc='map description'
@@ -400,6 +430,9 @@ describe('nvim_set_keymap, nvim_del_keymap', function()
-- maparg(), which does not accept "!" (though it returns "!" in its output
-- if getting a mapping set with |:map!|).
local function normalize_mapmode(mode, generate_expected)
+ if mode:sub(-1) == 'a' then
+ mode = mode:sub(1, -2)
+ end
if not generate_expected and mode == '!' then
-- Cannot retrieve mapmode-ic mappings with "!", but can with "i" or "c".
mode = 'i'
@@ -417,7 +450,10 @@ describe('nvim_set_keymap, nvim_del_keymap', function()
end
local to_return = {}
- to_return.mode = normalize_mapmode(mode, true)
+ local expected_mode = normalize_mapmode(mode, true)
+ to_return.mode = expected_mode
+ to_return.mode_bits = mode_bits_map[expected_mode]
+ to_return.abbr = mode:sub(-1) == 'a' and 1 or 0
to_return.noremap = not opts.noremap and 0 or 1
to_return.lhs = lhs
to_return.rhs = rhs
@@ -426,6 +462,7 @@ describe('nvim_set_keymap, nvim_del_keymap', function()
to_return.nowait = not opts.nowait and 0 or 1
to_return.expr = not opts.expr and 0 or 1
to_return.sid = not opts.sid and sid_api_client or opts.sid
+ to_return.scriptversion = 1
to_return.buffer = not opts.buffer and 0 or opts.buffer
to_return.lnum = not opts.lnum and 0 or opts.lnum
to_return.desc = opts.desc
@@ -435,7 +472,7 @@ describe('nvim_set_keymap, nvim_del_keymap', function()
-- Gets a maparg() dict from Nvim, if one exists.
local function get_mapargs(mode, lhs)
- local mapargs = funcs.maparg(lhs, normalize_mapmode(mode), false, true)
+ local mapargs = funcs.maparg(lhs, normalize_mapmode(mode), mode:sub(-1) == 'a', true)
-- drop "lhsraw" and "lhsrawalt" which are hard to check
mapargs.lhsraw = nil
mapargs.lhsrawalt = nil
@@ -450,7 +487,7 @@ describe('nvim_set_keymap, nvim_del_keymap', function()
end)
it('error if LHS longer than MAXMAPLEN', function()
- -- assume MAXMAPLEN of 50 chars, as declared in vim.h
+ -- assume MAXMAPLEN of 50 chars, as declared in mapping_defs.h
local MAXMAPLEN = 50
local lhs = ''
for i=1,MAXMAPLEN do
@@ -484,35 +521,33 @@ describe('nvim_set_keymap, nvim_del_keymap', function()
get_mapargs('', 'lhs'))
end)
- it('throws errors when given too-long mode shortnames', function()
- eq('Shortname is too long: map',
- pcall_err(meths.set_keymap, 'map', 'lhs', 'rhs', {}))
-
- eq('Shortname is too long: vmap',
- pcall_err(meths.set_keymap, 'vmap', 'lhs', 'rhs', {}))
-
- eq('Shortname is too long: xnoremap',
- pcall_err(meths.set_keymap, 'xnoremap', 'lhs', 'rhs', {}))
-
- eq('Shortname is too long: map', pcall_err(meths.del_keymap, 'map', 'lhs'))
- eq('Shortname is too long: vmap', pcall_err(meths.del_keymap, 'vmap', 'lhs'))
- eq('Shortname is too long: xnoremap', pcall_err(meths.del_keymap, 'xnoremap', 'lhs'))
- end)
-
it('error on invalid mode shortname', function()
- eq('Invalid mode shortname: " "',
- pcall_err(meths.set_keymap, ' ', 'lhs', 'rhs', {}))
- eq('Invalid mode shortname: "m"',
- pcall_err(meths.set_keymap, 'm', 'lhs', 'rhs', {}))
- eq('Invalid mode shortname: "?"',
- pcall_err(meths.set_keymap, '?', 'lhs', 'rhs', {}))
- eq('Invalid mode shortname: "y"',
- pcall_err(meths.set_keymap, 'y', 'lhs', 'rhs', {}))
- eq('Invalid mode shortname: "p"',
- pcall_err(meths.set_keymap, 'p', 'lhs', 'rhs', {}))
+ eq('Invalid mode shortname: " "', pcall_err(meths.set_keymap, ' ', 'lhs', 'rhs', {}))
+ eq('Invalid mode shortname: "m"', pcall_err(meths.set_keymap, 'm', 'lhs', 'rhs', {}))
+ eq('Invalid mode shortname: "?"', pcall_err(meths.set_keymap, '?', 'lhs', 'rhs', {}))
+ eq('Invalid mode shortname: "y"', pcall_err(meths.set_keymap, 'y', 'lhs', 'rhs', {}))
+ eq('Invalid mode shortname: "p"', pcall_err(meths.set_keymap, 'p', 'lhs', 'rhs', {}))
+ eq('Invalid mode shortname: "a"', pcall_err(meths.set_keymap, 'a', 'lhs', 'rhs', {}))
+ eq('Invalid mode shortname: "oa"', pcall_err(meths.set_keymap, 'oa', 'lhs', 'rhs', {}))
+ eq('Invalid mode shortname: "!o"', pcall_err(meths.set_keymap, '!o', 'lhs', 'rhs', {}))
+ eq('Invalid mode shortname: "!i"', pcall_err(meths.set_keymap, '!i', 'lhs', 'rhs', {}))
+ eq('Invalid mode shortname: "!!"', pcall_err(meths.set_keymap, '!!', 'lhs', 'rhs', {}))
+ eq('Invalid mode shortname: "map"', pcall_err(meths.set_keymap, 'map', 'lhs', 'rhs', {}))
+ eq('Invalid mode shortname: "vmap"', pcall_err(meths.set_keymap, 'vmap', 'lhs', 'rhs', {}))
+ eq('Invalid mode shortname: "xnoremap"', pcall_err(meths.set_keymap, 'xnoremap', 'lhs', 'rhs', {}))
+ eq('Invalid mode shortname: " "', pcall_err(meths.del_keymap, ' ', 'lhs'))
+ eq('Invalid mode shortname: "m"', pcall_err(meths.del_keymap, 'm', 'lhs'))
eq('Invalid mode shortname: "?"', pcall_err(meths.del_keymap, '?', 'lhs'))
eq('Invalid mode shortname: "y"', pcall_err(meths.del_keymap, 'y', 'lhs'))
eq('Invalid mode shortname: "p"', pcall_err(meths.del_keymap, 'p', 'lhs'))
+ eq('Invalid mode shortname: "a"', pcall_err(meths.del_keymap, 'a', 'lhs'))
+ eq('Invalid mode shortname: "oa"', pcall_err(meths.del_keymap, 'oa', 'lhs'))
+ eq('Invalid mode shortname: "!o"', pcall_err(meths.del_keymap, '!o', 'lhs'))
+ eq('Invalid mode shortname: "!i"', pcall_err(meths.del_keymap, '!i', 'lhs'))
+ eq('Invalid mode shortname: "!!"', pcall_err(meths.del_keymap, '!!', 'lhs'))
+ eq('Invalid mode shortname: "map"', pcall_err(meths.del_keymap, 'map', 'lhs'))
+ eq('Invalid mode shortname: "vmap"', pcall_err(meths.del_keymap, 'vmap', 'lhs'))
+ eq('Invalid mode shortname: "xnoremap"', pcall_err(meths.del_keymap, 'xnoremap', 'lhs'))
end)
it('error on invalid optnames', function()
@@ -681,13 +716,13 @@ describe('nvim_set_keymap, nvim_del_keymap', function()
end)
it('can set <expr> mappings whose RHS change dynamically', function()
- meths.exec([[
+ 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', {}))
@@ -744,7 +779,7 @@ describe('nvim_set_keymap, nvim_del_keymap', function()
end)
-- Perform exhaustive tests of basic functionality
- local mapmodes = {'n', 'v', 'x', 's', 'o', '!', 'i', 'l', 'c', 't', ''}
+ local mapmodes = {'n', 'v', 'x', 's', 'o', '!', 'i', 'l', 'c', 't', '', 'ia', 'ca', '!a'}
for _, mapmode in ipairs(mapmodes) do
it('can set/unset normal mappings in mapmode '..mapmode, function()
meths.set_keymap(mapmode, 'lhs', 'rhs', {})
@@ -773,11 +808,9 @@ describe('nvim_set_keymap, nvim_del_keymap', function()
-- remove some map arguments that are harder to test, or were already tested
optnames = {'nowait', 'silent', 'expr', 'noremap'}
for _, mapmode in ipairs(mapmodes) do
- local printable_mode = normalize_mapmode(mapmode)
-
-- Test with single mappings
for _, maparg in ipairs(optnames) do
- it('can set/unset '..printable_mode..'-mappings with maparg: '..maparg,
+ it('can set/unset '..mapmode..'-mappings with maparg: '..maparg,
function()
meths.set_keymap(mapmode, 'lhs', 'rhs', {[maparg] = true})
eq(generate_mapargs(mapmode, 'lhs', 'rhs', {[maparg] = true}),
@@ -785,7 +818,7 @@ describe('nvim_set_keymap, nvim_del_keymap', function()
meths.del_keymap(mapmode, 'lhs')
eq({}, get_mapargs(mapmode, 'lhs'))
end)
- it ('can set/unset '..printable_mode..'-mode mappings with maparg '..
+ it ('can set/unset '..mapmode..'-mode mappings with maparg '..
maparg..', whose value is false', function()
meths.set_keymap(mapmode, 'lhs', 'rhs', {[maparg] = false})
eq(generate_mapargs(mapmode, 'lhs', 'rhs'),
@@ -798,7 +831,7 @@ describe('nvim_set_keymap, nvim_del_keymap', function()
-- Test with triplets of mappings, one of which is false
for i = 1, (#optnames - 2) do
local opt1, opt2, opt3 = optnames[i], optnames[i + 1], optnames[i + 2]
- it('can set/unset '..printable_mode..'-mode mappings with mapargs '..
+ it('can set/unset '..mapmode..'-mode mappings with mapargs '..
opt1..', '..opt2..', '..opt3, function()
local opts = {[opt1] = true, [opt2] = false, [opt3] = true}
meths.set_keymap(mapmode, 'lhs', 'rhs', opts)
@@ -813,33 +846,36 @@ describe('nvim_set_keymap, nvim_del_keymap', function()
it('can make lua mappings', function()
eq(0, exec_lua [[
GlobalCount = 0
- vim.api.nvim_set_keymap ('n', 'asdf', '', {callback = function() GlobalCount = GlobalCount + 1 end })
+ vim.api.nvim_set_keymap('n', 'asdf', '', {callback = function() GlobalCount = GlobalCount + 1 end })
return GlobalCount
]])
feed('asdf\n')
eq(1, exec_lua[[return GlobalCount]])
-
end)
- it (':map command shows lua mapping correctly', function()
+ it(':map command shows lua mapping correctly', function()
exec_lua [[
- vim.api.nvim_set_keymap ('n', 'asdf', '', {callback = function() print('jkl;') end })
+ vim.api.nvim_set_keymap('n', 'asdf', '', {callback = function() print('jkl;') end })
]]
- assert.truthy(string.match(exec_lua[[return vim.api.nvim_exec(':nmap asdf', true)]],
- "^\nn asdf <Lua %d+>"))
+ assert.truthy(
+ string.match(
+ exec_lua[[return vim.api.nvim_exec2(':nmap asdf', { output = true }).output]],
+ "^\nn asdf <Lua %d+>"
+ )
+ )
end)
- it ('mapcheck() returns lua mapping correctly', function()
+ it('mapcheck() returns lua mapping correctly', function()
exec_lua [[
- vim.api.nvim_set_keymap ('n', 'asdf', '', {callback = function() print('jkl;') end })
+ vim.api.nvim_set_keymap('n', 'asdf', '', {callback = function() print('jkl;') end })
]]
assert.truthy(string.match(funcs.mapcheck('asdf', 'n'),
"^<Lua %d+>"))
end)
- it ('maparg() returns lua mapping correctly', function()
+ it('maparg() returns lua mapping correctly', function()
eq(0, exec_lua([[
GlobalCount = 0
vim.api.nvim_set_keymap('n', 'asdf', '', {callback = function() GlobalCount = GlobalCount + 1 end })
@@ -867,7 +903,7 @@ describe('nvim_set_keymap, nvim_del_keymap', function()
it('can make lua expr mappings replacing keycodes', function()
exec_lua [[
- vim.api.nvim_set_keymap ('n', 'aa', '', {callback = function() return '<Insert>π<C-V><M-π>foo<lt><Esc>' end, expr = true, replace_keycodes = true })
+ vim.api.nvim_set_keymap('n', 'aa', '', {callback = function() return '<Insert>π<C-V><M-π>foo<lt><Esc>' end, expr = true, replace_keycodes = true })
]]
feed('aa')
@@ -877,7 +913,7 @@ describe('nvim_set_keymap, nvim_del_keymap', function()
it('can make lua expr mappings without replacing keycodes', function()
exec_lua [[
- vim.api.nvim_set_keymap ('i', 'aa', '', {callback = function() return '<space>' end, expr = true })
+ vim.api.nvim_set_keymap('i', 'aa', '', {callback = function() return '<space>' end, expr = true })
]]
feed('iaa<esc>')
@@ -887,7 +923,7 @@ describe('nvim_set_keymap, nvim_del_keymap', function()
it('lua expr mapping returning nil is equivalent to returning an empty string', function()
exec_lua [[
- vim.api.nvim_set_keymap ('i', 'aa', '', {callback = function() return nil end, expr = true })
+ vim.api.nvim_set_keymap('i', 'aa', '', {callback = function() return nil end, expr = true })
]]
feed('iaa<esc>')
@@ -898,17 +934,29 @@ describe('nvim_set_keymap, nvim_del_keymap', function()
it('does not reset pum in lua mapping', function()
eq(0, exec_lua [[
VisibleCount = 0
- vim.api.nvim_set_keymap ('i', '<F2>', '', {callback = function() VisibleCount = VisibleCount + vim.fn.pumvisible() end})
+ vim.api.nvim_set_keymap('i', '<F2>', '', {callback = function() VisibleCount = VisibleCount + vim.fn.pumvisible() end})
return VisibleCount
]])
feed('i<C-X><C-V><F2><F2><esc>')
eq(2, exec_lua[[return VisibleCount]])
end)
+ it('redo of lua mappings in op-pending mode work', function()
+ eq(0, exec_lua [[
+ OpCount = 0
+ vim.api.nvim_set_keymap('o', '<F2>', '', {callback = function() OpCount = OpCount + 1 end})
+ return OpCount
+ ]])
+ feed('d<F2>')
+ eq(1, exec_lua[[return OpCount]])
+ feed('.')
+ eq(2, exec_lua[[return OpCount]])
+ end)
+
it('can overwrite lua mappings', function()
eq(0, exec_lua [[
GlobalCount = 0
- vim.api.nvim_set_keymap ('n', 'asdf', '', {callback = function() GlobalCount = GlobalCount + 1 end })
+ vim.api.nvim_set_keymap('n', 'asdf', '', {callback = function() GlobalCount = GlobalCount + 1 end })
return GlobalCount
]])
@@ -917,7 +965,7 @@ describe('nvim_set_keymap, nvim_del_keymap', function()
eq(1, exec_lua[[return GlobalCount]])
exec_lua [[
- vim.api.nvim_set_keymap ('n', 'asdf', '', {callback = function() GlobalCount = GlobalCount - 1 end })
+ vim.api.nvim_set_keymap('n', 'asdf', '', {callback = function() GlobalCount = GlobalCount - 1 end })
]]
feed('asdf\n')
@@ -928,7 +976,7 @@ describe('nvim_set_keymap, nvim_del_keymap', function()
it('can unmap lua mappings', function()
eq(0, exec_lua [[
GlobalCount = 0
- vim.api.nvim_set_keymap ('n', 'asdf', '', {callback = function() GlobalCount = GlobalCount + 1 end })
+ vim.api.nvim_set_keymap('n', 'asdf', '', {callback = function() GlobalCount = GlobalCount + 1 end })
return GlobalCount
]])
@@ -973,6 +1021,54 @@ describe('nvim_set_keymap, nvim_del_keymap', function()
eq("\nn lhs rhs\n map description",
helpers.exec_capture("nmap lhs"))
end)
+
+ it('can define !-mode abbreviations with lua callbacks', function()
+ exec_lua [[
+ GlobalCount = 0
+ vim.api.nvim_set_keymap('!a', 'foo', '', {expr = true, callback = function()
+ GlobalCount = GlobalCount + 1
+ return tostring(GlobalCount)
+ end})
+ ]]
+
+ feed 'iThe foo and the bar and the foo again<esc>'
+ eq('The 1 and the bar and the 2 again', meths.get_current_line())
+
+ feed ':let x = "The foo is the one"<cr>'
+ eq('The 3 is the one', meths.eval'x')
+ end)
+
+ it('can define insert mode abbreviations with lua callbacks', function()
+ exec_lua [[
+ GlobalCount = 0
+ vim.api.nvim_set_keymap('ia', 'foo', '', {expr = true, callback = function()
+ GlobalCount = GlobalCount + 1
+ return tostring(GlobalCount)
+ end})
+ ]]
+
+ feed 'iThe foo and the bar and the foo again<esc>'
+ eq('The 1 and the bar and the 2 again', meths.get_current_line())
+
+ feed ':let x = "The foo is the one"<cr>'
+ eq('The foo is the one', meths.eval'x')
+ end)
+
+ it('can define cmdline mode abbreviations with lua callbacks', function()
+ exec_lua [[
+ GlobalCount = 0
+ vim.api.nvim_set_keymap('ca', 'foo', '', {expr = true, callback = function()
+ GlobalCount = GlobalCount + 1
+ return tostring(GlobalCount)
+ end})
+ ]]
+
+ feed 'iThe foo and the bar and the foo again<esc>'
+ eq('The foo and the bar and the foo again', meths.get_current_line())
+
+ feed ':let x = "The foo is the one"<cr>'
+ eq('The 1 is the one', meths.eval'x')
+ end)
end)
describe('nvim_buf_set_keymap, nvim_buf_del_keymap', function()
@@ -1074,7 +1170,7 @@ describe('nvim_buf_set_keymap, nvim_buf_del_keymap', function()
it('can make lua mappings', function()
eq(0, exec_lua [[
GlobalCount = 0
- vim.api.nvim_buf_set_keymap (0, 'n', 'asdf', '', {callback = function() GlobalCount = GlobalCount + 1 end })
+ vim.api.nvim_buf_set_keymap(0, 'n', 'asdf', '', {callback = function() GlobalCount = GlobalCount + 1 end })
return GlobalCount
]])
@@ -1085,7 +1181,7 @@ describe('nvim_buf_set_keymap, nvim_buf_del_keymap', function()
it('can make lua expr mappings replacing keycodes', function()
exec_lua [[
- vim.api.nvim_buf_set_keymap (0, 'n', 'aa', '', {callback = function() return '<Insert>π<C-V><M-π>foo<lt><Esc>' end, expr = true, replace_keycodes = true })
+ vim.api.nvim_buf_set_keymap(0, 'n', 'aa', '', {callback = function() return '<Insert>π<C-V><M-π>foo<lt><Esc>' end, expr = true, replace_keycodes = true })
]]
feed('aa')
@@ -1095,7 +1191,7 @@ describe('nvim_buf_set_keymap, nvim_buf_del_keymap', function()
it('can make lua expr mappings without replacing keycodes', function()
exec_lua [[
- vim.api.nvim_buf_set_keymap (0, 'i', 'aa', '', {callback = function() return '<space>' end, expr = true })
+ vim.api.nvim_buf_set_keymap(0, 'i', 'aa', '', {callback = function() return '<space>' end, expr = true })
]]
feed('iaa<esc>')
@@ -1107,7 +1203,7 @@ describe('nvim_buf_set_keymap, nvim_buf_del_keymap', function()
it('can overwrite lua mappings', function()
eq(0, exec_lua [[
GlobalCount = 0
- vim.api.nvim_buf_set_keymap (0, 'n', 'asdf', '', {callback = function() GlobalCount = GlobalCount + 1 end })
+ vim.api.nvim_buf_set_keymap(0, 'n', 'asdf', '', {callback = function() GlobalCount = GlobalCount + 1 end })
return GlobalCount
]])
@@ -1116,7 +1212,7 @@ describe('nvim_buf_set_keymap, nvim_buf_del_keymap', function()
eq(1, exec_lua[[return GlobalCount]])
exec_lua [[
- vim.api.nvim_buf_set_keymap (0, 'n', 'asdf', '', {callback = function() GlobalCount = GlobalCount - 1 end })
+ vim.api.nvim_buf_set_keymap(0, 'n', 'asdf', '', {callback = function() GlobalCount = GlobalCount - 1 end })
]]
feed('asdf\n')
@@ -1127,7 +1223,7 @@ describe('nvim_buf_set_keymap, nvim_buf_del_keymap', function()
it('can unmap lua mappings', function()
eq(0, exec_lua [[
GlobalCount = 0
- vim.api.nvim_buf_set_keymap (0, 'n', 'asdf', '', {callback = function() GlobalCount = GlobalCount + 1 end })
+ vim.api.nvim_buf_set_keymap(0, 'n', 'asdf', '', {callback = function() GlobalCount = GlobalCount + 1 end })
return GlobalCount
]])
diff --git a/test/functional/api/proc_spec.lua b/test/functional/api/proc_spec.lua
index 2028a8fba5..20edea3feb 100644
--- a/test/functional/api/proc_spec.lua
+++ b/test/functional/api/proc_spec.lua
@@ -42,14 +42,14 @@ describe('API', function()
end)
end)
- it('validates input', function()
+ it('validation', function()
local status, rv = pcall(request, "nvim_get_proc_children", -1)
eq(false, status)
- eq("Invalid pid: -1", string.match(rv, "Invalid.*"))
+ eq("Invalid 'pid': -1", string.match(rv, "Invalid.*"))
status, rv = pcall(request, "nvim_get_proc_children", 0)
eq(false, status)
- eq("Invalid pid: 0", string.match(rv, "Invalid.*"))
+ eq("Invalid 'pid': 0", string.match(rv, "Invalid.*"))
-- Assume PID 99999 does not exist.
status, rv = pcall(request, "nvim_get_proc_children", 99999)
@@ -68,14 +68,14 @@ describe('API', function()
neq(pid, pinfo.ppid)
end)
- it('validates input', function()
+ it('validation', function()
local status, rv = pcall(request, "nvim_get_proc", -1)
eq(false, status)
- eq("Invalid pid: -1", string.match(rv, "Invalid.*"))
+ eq("Invalid 'pid': -1", string.match(rv, "Invalid.*"))
status, rv = pcall(request, "nvim_get_proc", 0)
eq(false, status)
- eq("Invalid pid: 0", string.match(rv, "Invalid.*"))
+ eq("Invalid 'pid': 0", string.match(rv, "Invalid.*"))
-- Assume PID 99999 does not exist.
status, rv = pcall(request, "nvim_get_proc", 99999)
diff --git a/test/functional/api/rpc_fixture.lua b/test/functional/api/rpc_fixture.lua
index 94df751363..c860a6da59 100644
--- a/test/functional/api/rpc_fixture.lua
+++ b/test/functional/api/rpc_fixture.lua
@@ -4,9 +4,8 @@
package.path = arg[1]
package.cpath = arg[2]
-local mpack = require('mpack')
-local StdioStream = require('nvim.stdio_stream')
-local Session = require('nvim.session')
+local StdioStream = require'test.client.uv_stream'.StdioStream
+local Session = require'test.client.session'
local stdio_stream = StdioStream.open()
local session = Session.new(stdio_stream)
@@ -19,7 +18,7 @@ local function on_request(method, args)
return "done!"
elseif method == "exit" then
session:stop()
- return mpack.NIL
+ return vim.NIL
end
end
diff --git a/test/functional/api/server_notifications_spec.lua b/test/functional/api/server_notifications_spec.lua
index 53642858b2..bc43f6564d 100644
--- a/test/functional/api/server_notifications_spec.lua
+++ b/test/functional/api/server_notifications_spec.lua
@@ -6,9 +6,7 @@ local eq, clear, eval, command, nvim, next_msg =
local meths = helpers.meths
local exec_lua = helpers.exec_lua
local retry = helpers.retry
-local is_ci = helpers.is_ci
local assert_alive = helpers.assert_alive
-local skip = helpers.skip
local testlog = 'Xtest-server-notify-log'
@@ -90,7 +88,6 @@ describe('notify', function()
end)
it('cancels stale events on channel close', function()
- skip(is_ci(), 'hangs on CI #14083 #15251')
local catchan = eval("jobstart(['cat'], {'rpc': v:true})")
local catpath = eval('exepath("cat")')
eq({id=catchan, argv={catpath}, stream='job', mode='rpc', client = {}}, exec_lua ([[
diff --git a/test/functional/api/server_requests_spec.lua b/test/functional/api/server_requests_spec.lua
index ceff390dc5..1ad4ad3a02 100644
--- a/test/functional/api/server_requests_spec.lua
+++ b/test/functional/api/server_requests_spec.lua
@@ -199,7 +199,7 @@ describe('server -> client', function()
it('can communicate buffers, tabpages, and windows', function()
eq({1}, eval("rpcrequest(vim, 'nvim_list_tabpages')"))
- -- Window IDs start at 1000 (LOWEST_WIN_ID in vim.h)
+ -- Window IDs start at 1000 (LOWEST_WIN_ID in window.h)
eq({1000}, eval("rpcrequest(vim, 'nvim_list_wins')"))
local buf = eval("rpcrequest(vim, 'nvim_list_bufs')")[1]
@@ -237,7 +237,7 @@ describe('server -> client', function()
\ }
]])
meths.set_var("args", {
- helpers.test_lua_prg,
+ nvim_prog, '-ll',
'test/functional/api/rpc_fixture.lua',
package.path,
package.cpath,
@@ -337,6 +337,21 @@ describe('server -> client', function()
eq('localhost:', string.sub(address,1,10))
connect_test(server, 'tcp', address)
end)
+
+ it('does not crash on receiving UI events', function()
+ local server = spawn(nvim_argv)
+ set_session(server)
+ local address = funcs.serverlist()[1]
+ local client = spawn(nvim_argv, false, nil, true)
+ set_session(client)
+
+ local id = funcs.sockconnect('pipe', address, {rpc=true})
+ funcs.rpcrequest(id, 'nvim_ui_attach', 80, 24, {})
+ assert_alive()
+
+ server:close()
+ client:close()
+ end)
end)
describe('connecting to its own pipe address', function()
diff --git a/test/functional/api/ui_spec.lua b/test/functional/api/ui_spec.lua
index 279cd1856d..6efb6726fe 100644
--- a/test/functional/api/ui_spec.lua
+++ b/test/functional/api/ui_spec.lua
@@ -1,8 +1,11 @@
local helpers = require('test.functional.helpers')(after_each)
local Screen = require('test.functional.ui.screen')
local clear = helpers.clear
+local command = helpers.command
local eq = helpers.eq
local eval = helpers.eval
+local exec = helpers.exec
+local feed = helpers.feed
local meths = helpers.meths
local request = helpers.request
local pcall_err = helpers.pcall_err
@@ -11,17 +14,35 @@ describe('nvim_ui_attach()', function()
before_each(function()
clear()
end)
+
it('handles very large width/height #2180', function()
local screen = Screen.new(999, 999)
screen:attach()
eq(999, eval('&lines'))
eq(999, eval('&columns'))
end)
- it('invalid option returns error', function()
+
+ it('validation', function()
eq('No such UI option: foo',
pcall_err(meths.ui_attach, 80, 24, { foo={'foo'} }))
- end)
- it('validates channel arg', function()
+
+ eq("Invalid 'ext_linegrid': expected Boolean, got Array",
+ pcall_err(meths.ui_attach, 80, 24, { ext_linegrid={} }))
+ eq("Invalid 'override': expected Boolean, got Array",
+ pcall_err(meths.ui_attach, 80, 24, { override={} }))
+ eq("Invalid 'rgb': expected Boolean, got Array",
+ pcall_err(meths.ui_attach, 80, 24, { rgb={} }))
+ eq("Invalid 'term_name': expected String, got Boolean",
+ pcall_err(meths.ui_attach, 80, 24, { term_name=true }))
+ eq("Invalid 'term_colors': expected Integer, got Boolean",
+ pcall_err(meths.ui_attach, 80, 24, { term_colors=true }))
+ eq("Invalid 'stdin_fd': expected Integer, got String",
+ pcall_err(meths.ui_attach, 80, 24, { stdin_fd='foo' }))
+ eq("Invalid 'stdin_tty': expected Boolean, got String",
+ pcall_err(meths.ui_attach, 80, 24, { stdin_tty='foo' }))
+ eq("Invalid 'stdout_tty': expected Boolean, got String",
+ pcall_err(meths.ui_attach, 80, 24, { stdout_tty='foo' }))
+
eq('UI not attached to channel: 1',
pcall_err(request, 'nvim_ui_try_resize', 40, 10))
eq('UI not attached to channel: 1',
@@ -37,14 +58,13 @@ describe('nvim_ui_attach()', function()
end)
it('autocmds UIEnter/UILeave', function()
- clear{
- args_rm={'--headless'},
- args={
- '--cmd', 'let g:evs = []',
- '--cmd', 'autocmd UIEnter * :call add(g:evs, "UIEnter") | let g:uienter_ev = deepcopy(v:event)',
- '--cmd', 'autocmd UILeave * :call add(g:evs, "UILeave") | let g:uileave_ev = deepcopy(v:event)',
- '--cmd', 'autocmd VimEnter * :call add(g:evs, "VimEnter")',
- }}
+ clear{args_rm={'--headless'}}
+ exec([[
+ let g:evs = []
+ autocmd UIEnter * call add(g:evs, "UIEnter") | let g:uienter_ev = deepcopy(v:event)
+ autocmd UILeave * call add(g:evs, "UILeave") | let g:uileave_ev = deepcopy(v:event)
+ autocmd VimEnter * call add(g:evs, "VimEnter")
+ ]])
local screen = Screen.new()
screen:attach()
eq({chan=1}, eval('g:uienter_ev'))
@@ -56,3 +76,45 @@ it('autocmds UIEnter/UILeave', function()
'UILeave',
}, eval('g:evs'))
end)
+
+it('autocmds VimSuspend/VimResume #22041', function()
+ clear()
+ local screen = Screen.new()
+ screen:attach()
+ exec([[
+ let g:ev = []
+ autocmd VimResume * :call add(g:ev, 'r')
+ autocmd VimSuspend * :call add(g:ev, 's')
+ ]])
+
+ eq(false, screen.suspended)
+ feed('<C-Z>')
+ screen:expect(function() eq(true, screen.suspended) end)
+ eq({ 's' }, eval('g:ev'))
+ screen.suspended = false
+ feed('<Ignore>')
+ eq({ 's', 'r' }, eval('g:ev'))
+
+ command('suspend')
+ screen:expect(function() eq(true, screen.suspended) end)
+ eq({ 's', 'r', 's' }, eval('g:ev'))
+ screen.suspended = false
+ meths.input_mouse('move', '', '', 0, 0, 0)
+ eq({ 's', 'r', 's', 'r' }, eval('g:ev'))
+
+ feed('<C-Z><C-Z><C-Z>')
+ screen:expect(function() eq(true, screen.suspended) end)
+ meths.ui_set_focus(false)
+ eq({ 's', 'r', 's', 'r', 's' }, eval('g:ev'))
+ screen.suspended = false
+ meths.ui_set_focus(true)
+ eq({ 's', 'r', 's', 'r', 's', 'r' }, eval('g:ev'))
+
+ command('suspend | suspend | suspend')
+ screen:expect(function() eq(true, screen.suspended) end)
+ screen:detach()
+ eq({ 's', 'r', 's', 'r', 's', 'r', 's' }, eval('g:ev'))
+ screen.suspended = false
+ screen:attach()
+ eq({ 's', 'r', 's', 'r', 's', 'r', 's', 'r' }, eval('g:ev'))
+end)
diff --git a/test/functional/api/version_spec.lua b/test/functional/api/version_spec.lua
index 771192e9ab..6d466b0cc1 100644
--- a/test/functional/api/version_spec.lua
+++ b/test/functional/api/version_spec.lua
@@ -34,6 +34,7 @@ describe("api_info()['version']", function()
local minor = version['minor']
local patch = version['patch']
local prerelease = version['prerelease']
+ local build = version['build']
eq("number", type(major))
eq("number", type(minor))
eq("number", type(patch))
@@ -42,6 +43,7 @@ describe("api_info()['version']", function()
eq(0, funcs.has("nvim-"..major.."."..minor.."."..(patch + 1)))
eq(0, funcs.has("nvim-"..major.."."..(minor + 1).."."..patch))
eq(0, funcs.has("nvim-"..(major + 1).."."..minor.."."..patch))
+ assert(build == nil or type(build) == 'string')
end)
end)
diff --git a/test/functional/api/vim_spec.lua b/test/functional/api/vim_spec.lua
index 8fcdd9620b..8bbadda9b0 100644
--- a/test/functional/api/vim_spec.lua
+++ b/test/functional/api/vim_spec.lua
@@ -1,6 +1,5 @@
local helpers = require('test.functional.helpers')(after_each)
local Screen = require('test.functional.ui.screen')
-local lfs = require('lfs')
local luv = require('luv')
local fmt = string.format
@@ -9,6 +8,7 @@ local NIL = helpers.NIL
local clear, nvim, eq, neq = helpers.clear, helpers.nvim, helpers.eq, helpers.neq
local command = helpers.command
local exec = helpers.exec
+local exec_capture = helpers.exec_capture
local eval = helpers.eval
local expect = helpers.expect
local funcs = helpers.funcs
@@ -28,7 +28,6 @@ local write_file = helpers.write_file
local exec_lua = helpers.exec_lua
local exc_exec = helpers.exc_exec
local insert = helpers.insert
-local expect_exit = helpers.expect_exit
local skip = helpers.skip
local pcall_err = helpers.pcall_err
@@ -59,7 +58,7 @@ describe('API', function()
-- XXX: This must be the last one, else next one will fail:
-- "Packer instance already working. Use another Packer ..."
- matches("can't serialize object$",
+ matches("can't serialize object of type .$",
pcall_err(request, nil))
end)
@@ -89,132 +88,145 @@ describe('API', function()
eq({mode='i', blocking=false}, nvim("get_mode"))
end)
- describe('nvim_exec', function()
+ describe('nvim_exec2', function()
+ it('always returns table', function()
+ -- In built version this results into `vim.empty_dict()`
+ eq({}, nvim('exec2', 'echo "Hello"', {}))
+ eq({}, nvim('exec2', 'echo "Hello"', { output = false }))
+ eq({ output = 'Hello' }, nvim('exec2', 'echo "Hello"', { output = true }))
+ end)
+
+ it('default options', function()
+ -- Should be equivalent to { output = false }
+ nvim('exec2', "let x0 = 'a'", {})
+ eq('a', nvim('get_var', 'x0'))
+ end)
+
it('one-line input', function()
- nvim('exec', "let x1 = 'a'", false)
+ nvim('exec2', "let x1 = 'a'", { output = 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))
+ nvim('exec2', 'set nowrap', { output = false })
+ eq({ output = 'nowrap\n\tLast set from anonymous :source' },
+ nvim('exec2', 'verbose set wrap?', { output = true }))
-- Using script var to force creation of a script item
- nvim('exec', [[
+ nvim('exec2', [[
let s:a = 1
set nowrap
- ]], false)
- eq('nowrap\n\tLast set from anonymous :source (script id 1)',
- nvim('exec', 'verbose set wrap?', true))
+ ]], { output = false })
+ eq({ output = 'nowrap\n\tLast set from anonymous :source (script id 1)' },
+ nvim('exec2', 'verbose set wrap?', { output = true }))
end)
it('multiline input', function()
-- Heredoc + empty lines.
- nvim('exec', "let x2 = 'a'\n", false)
+ nvim('exec2', "let x2 = 'a'\n", { output = false })
eq('a', nvim('get_var', 'x2'))
- nvim('exec','lua <<EOF\n\n\n\ny=3\n\n\nEOF', false)
+ nvim('exec2','lua <<EOF\n\n\n\ny=3\n\n\nEOF', { output = false })
eq(3, nvim('eval', "luaeval('y')"))
- eq('', nvim('exec', 'lua <<EOF\ny=3\nEOF', false))
+ eq({}, nvim('exec2', 'lua <<EOF\ny=3\nEOF', { output = false }))
eq(3, nvim('eval', "luaeval('y')"))
-- Multiple statements
- nvim('exec', 'let x1=1\nlet x2=2\nlet x3=3\n', false)
+ nvim('exec2', 'let x1=1\nlet x2=2\nlet x3=3\n', { output = 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)
+ nvim('exec2', 'function Foo()\ncall setline(1,["xxx"])\nendfunction', { output = false })
eq('', nvim('get_current_line'))
- nvim('exec', 'call Foo()', false)
+ nvim('exec2', 'call Foo()', { output = false })
eq('xxx', nvim('get_current_line'))
-- Autocmds
- nvim('exec','autocmd BufAdd * :let x1 = "Hello"', false)
+ nvim('exec2','autocmd BufAdd * :let x1 = "Hello"', { output = false })
nvim('command', 'new foo')
eq('Hello', request('nvim_eval', 'g:x1'))
-- Line continuations
- nvim('exec', [[
+ nvim('exec2', [[
let abc = #{
\ a: 1,
"\ b: 2,
\ c: 3
- \ }]], false)
+ \ }]], { output = false })
eq({a = 1, c = 3}, request('nvim_eval', 'g:abc'))
-- try no spaces before continuations to catch off-by-one error
- nvim('exec', 'let ab = #{\n\\a: 98,\n"\\ b: 2\n\\}', false)
+ nvim('exec2', 'let ab = #{\n\\a: 98,\n"\\ b: 2\n\\}', { output = false })
eq({a = 98}, request('nvim_eval', 'g:ab'))
-- Script scope (s:)
- eq('ahoy! script-scoped varrrrr', nvim('exec', [[
+ eq({ output = 'ahoy! script-scoped varrrrr' }, nvim('exec2', [[
let s:pirate = 'script-scoped varrrrr'
function! s:avast_ye_hades(s) abort
return a:s .. ' ' .. s:pirate
endfunction
echo <sid>avast_ye_hades('ahoy!')
- ]], true))
+ ]], { output = true }))
- eq('ahoy! script-scoped varrrrr', nvim('exec', [[
+ eq({ output = "{'output': 'ahoy! script-scoped varrrrr'}" }, nvim('exec2', [[
let s:pirate = 'script-scoped varrrrr'
function! Avast_ye_hades(s) abort
return a:s .. ' ' .. s:pirate
endfunction
- echo nvim_exec('echo Avast_ye_hades(''ahoy!'')', 1)
- ]], true))
+ echo nvim_exec2('echo Avast_ye_hades(''ahoy!'')', {'output': v:true})
+ ]], { output = true }))
matches('Vim%(echo%):E121: Undefined variable: s:pirate$',
- pcall_err(request, 'nvim_exec', [[
+ pcall_err(request, 'nvim_exec2', [[
let s:pirate = 'script-scoped varrrrr'
- call nvim_exec('echo s:pirate', 1)
- ]], false))
+ call nvim_exec2('echo s:pirate', {'output': v:true})
+ ]], { output = false }))
-- Script items are created only on script var access
- eq('1\n0', nvim('exec', [[
+ eq({ output = '1\n0' }, nvim('exec2', [[
echo expand("<SID>")->empty()
let s:a = 123
echo expand("<SID>")->empty()
- ]], true))
+ ]], { output = true }))
- eq('1\n0', nvim('exec', [[
+ eq({ output = '1\n0' }, nvim('exec2', [[
echo expand("<SID>")->empty()
function s:a() abort
endfunction
echo expand("<SID>")->empty()
- ]], true))
+ ]], { output = true }))
end)
it('non-ASCII input', function()
- nvim('exec', [=[
+ nvim('exec2', [=[
new
exe "normal! i ax \n Ax "
:%s/ax/--a1234--/g | :%s/Ax/--A1234--/g
- ]=], false)
+ ]=], { output = false })
nvim('command', '1')
eq(' --a1234-- ', nvim('get_current_line'))
nvim('command', '2')
eq(' --A1234-- ', nvim('get_current_line'))
- nvim('exec', [[
+ nvim('exec2', [[
new
call setline(1,['xxx'])
call feedkeys('r')
call feedkeys('ñ', 'xt')
- ]], false)
+ ]], { output = false })
eq('ñxx', nvim('get_current_line'))
end)
it('execution error', function()
- eq('nvim_exec(): Vim:E492: Not an editor command: bogus_command',
- pcall_err(request, 'nvim_exec', 'bogus_command', false))
+ eq('nvim_exec2(): Vim:E492: Not an editor command: bogus_command',
+ pcall_err(request, 'nvim_exec2', 'bogus_command', {}))
eq('', nvim('eval', 'v:errmsg')) -- v:errmsg was not updated.
eq('', eval('v:exception'))
- eq('nvim_exec(): Vim(buffer):E86: Buffer 23487 does not exist',
- pcall_err(request, 'nvim_exec', 'buffer 23487', false))
+ eq('nvim_exec2(): Vim(buffer):E86: Buffer 23487 does not exist',
+ pcall_err(request, 'nvim_exec2', 'buffer 23487', {}))
eq('', eval('v:errmsg')) -- v:errmsg was not updated.
eq('', eval('v:exception'))
end)
@@ -222,17 +234,17 @@ describe('API', function()
it('recursion', function()
local fname = tmpname()
write_file(fname, 'let x1 = "set from :source file"\n')
- -- nvim_exec
+ -- nvim_exec2
-- :source
- -- nvim_exec
- request('nvim_exec', [[
+ -- nvim_exec2
+ request('nvim_exec2', [[
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)
+ call nvim_exec2("source ]]..fname..[[\nlet x3 = substitute('foo','foo','set by recursive nvim_exec2','g')\nlet x5='overwritten'\nlet x4=x5\n", {'output': v:false})
+ ]], { output = 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('set by recursive nvim_exec2', request('nvim_get_var', 'x3'))
eq('overwritten', request('nvim_get_var', 'x4'))
eq('overwritten', request('nvim_get_var', 'x5'))
os.remove(fname)
@@ -242,35 +254,35 @@ describe('API', 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)
+ write_file(sourcing_fname, 'call nvim_exec2("source '..fname..'", {"output": v:false})\n')
+ meths.exec2('set verbose=2', { output = 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'..
+ 'continuing in nvim_exec2() 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))
+ 'continuing in nvim_exec2() called at nvim_exec2():0'
+ eq({ output = traceback_output },
+ meths.exec2('call nvim_exec2("source '..sourcing_fname..'", {"output": v:false})', { output = 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))
+ eq({ output = 'this is spinal tap' },
+ nvim('exec2', 'lua <<EOF\n\n\nprint("this is spinal tap")\n\n\nEOF', { output = true }))
+ eq({ output = '' }, nvim('exec2', 'echo', { output = true }))
+ eq({ output = 'foo 42' }, nvim('exec2', 'echo "foo" 42', { output = true }))
end)
- it('displays messages when output=false', function()
+ it('displays messages when opts.output=false', function()
local screen = Screen.new(40, 8)
screen:attach()
screen:set_default_attr_ids({
[0] = {bold=true, foreground=Screen.colors.Blue},
})
- meths.exec("echo 'hello'", false)
+ meths.exec2("echo 'hello'", { output = false })
screen:expect{grid=[[
^ |
{0:~ }|
@@ -289,7 +301,7 @@ describe('API', function()
screen:set_default_attr_ids({
[0] = {bold=true, foreground=Screen.colors.Blue},
})
- meths.exec("echo 'hello'", true)
+ meths.exec2("echo 'hello'", { output = true })
screen:expect{grid=[[
^ |
{0:~ }|
@@ -300,7 +312,7 @@ describe('API', function()
]]}
exec([[
func Print()
- call nvim_exec('echo "hello"', v:true)
+ call nvim_exec2('echo "hello"', { 'output': v:true })
endfunc
]])
feed([[:echon 1 | call Print() | echon 5<CR>]])
@@ -322,8 +334,7 @@ describe('API', function()
nvim('command', 'edit '..fname)
nvim('command', 'normal itesting\napi')
nvim('command', 'w')
- local f = io.open(fname)
- ok(f ~= nil)
+ local f = assert(io.open(fname))
if is_os('win') then
eq('testing\r\napi\r\n', f:read('*a'))
else
@@ -333,21 +344,27 @@ describe('API', function()
os.remove(fname)
end)
- it('VimL validation error: fails with specific error', function()
+ it('Vimscript validation error: fails with specific error', function()
local status, rv = pcall(nvim, "command", "bogus_command")
eq(false, status) -- nvim_command() failed.
- eq("E492:", string.match(rv, "E%d*:")) -- VimL error was returned.
+ eq("E492:", string.match(rv, "E%d*:")) -- Vimscript error was returned.
eq('', nvim('eval', 'v:errmsg')) -- v:errmsg was not updated.
eq('', eval('v:exception'))
end)
- it('VimL execution error: fails with specific error', function()
+ it('Vimscript execution error: fails with specific error', function()
local status, rv = pcall(nvim, "command", "buffer 23487")
eq(false, status) -- nvim_command() failed.
eq("E86: Buffer 23487 does not exist", string.match(rv, "E%d*:.*"))
eq('', eval('v:errmsg')) -- v:errmsg was not updated.
eq('', eval('v:exception'))
end)
+
+ it('gives E493 instead of prompting on backwards range', function()
+ command('split')
+ eq('Vim(windo):E493: Backwards range given: 2,1windo echo',
+ pcall_err(command, '2,1windo echo'))
+ end)
end)
describe('nvim_command_output', function()
@@ -403,7 +420,7 @@ describe('API', function()
eq(':!echo foo\r\n\nfoo'..win_lf..'\n', nvim('command_output', [[!echo foo]]))
end)
- it('VimL validation error: fails with specific error', function()
+ it('Vimscript validation error: fails with specific error', function()
local status, rv = pcall(nvim, "command_output", "bogus commannnd")
eq(false, status) -- nvim_command_output() failed.
eq("E492: Not an editor command: bogus commannnd",
@@ -413,7 +430,7 @@ describe('API', function()
eq({mode='n', blocking=false}, nvim("get_mode"))
end)
- it('VimL execution error: fails with specific error', function()
+ it('Vimscript execution error: fails with specific error', function()
local status, rv = pcall(nvim, "command_output", "buffer 42")
eq(false, status) -- nvim_command_output() failed.
eq("E86: Buffer 42 does not exist", string.match(rv, "E%d*:.*"))
@@ -422,7 +439,7 @@ describe('API', function()
eq({mode='n', blocking=false}, nvim("get_mode"))
end)
- it('Does not cause heap buffer overflow with large output', function()
+ it('does not cause heap buffer overflow with large output', function()
eq(eval('string(range(1000000))'),
nvim('command_output', 'echo range(1000000)'))
end)
@@ -444,7 +461,7 @@ describe('API', function()
eq(2, request("vim_eval", "1+1"))
end)
- it("VimL error: returns error details, does NOT update v:errmsg", function()
+ it("Vimscript error: returns error details, does NOT update v:errmsg", function()
eq('Vim:E121: Undefined variable: bogus',
pcall_err(request, 'nvim_eval', 'bogus expression'))
eq('', eval('v:errmsg')) -- v:errmsg was not updated.
@@ -459,7 +476,7 @@ describe('API', function()
eq('foo', nvim('call_function', 'simplify', {'this/./is//redundant/../../../foo'}))
end)
- it("VimL validation error: returns specific error, does NOT update v:errmsg", function()
+ it("Vimscript validation error: returns specific error, does NOT update v:errmsg", function()
eq('Vim:E117: Unknown function: bogus function',
pcall_err(request, 'nvim_call_function', 'bogus function', {'arg1'}))
eq('Vim:E119: Not enough arguments for function: atan',
@@ -468,7 +485,7 @@ describe('API', function()
eq('', eval('v:errmsg')) -- v:errmsg was not updated.
end)
- it("VimL error: returns error details, does NOT update v:errmsg", function()
+ it("Vimscript error: returns error details, does NOT update v:errmsg", function()
eq('Vim:E808: Number or Float required',
pcall_err(request, 'nvim_call_function', 'atan', {'foo'}))
eq('Vim:Invalid channel stream "xxx"',
@@ -479,7 +496,7 @@ describe('API', function()
eq('', eval('v:errmsg')) -- v:errmsg was not updated.
end)
- it("VimL exception: returns exception details, does NOT update v:errmsg", function()
+ it("Vimscript exception: returns exception details, does NOT update v:errmsg", function()
source([[
function! Foo() abort
throw 'wtf'
@@ -490,7 +507,7 @@ describe('API', function()
eq('', eval('v:errmsg')) -- v:errmsg was not updated.
end)
- it('validates args', function()
+ it('validation', function()
local too_many_args = { 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x' }
source([[
function! Foo(...) abort
@@ -504,7 +521,7 @@ describe('API', function()
end)
describe('nvim_call_dict_function', function()
- it('invokes VimL dict function', function()
+ it('invokes Vimscript dict function', function()
source([[
function! F(name) dict
return self.greeting.', '.a:name.'!'
@@ -532,7 +549,7 @@ describe('API', function()
eq('@it works@', nvim('call_dict_function', { result = 'it works', G = 'G'}, 'G', {}))
end)
- it('validates args', function()
+ it('validation', function()
command('let g:d={"baz":"zub","meep":[]}')
eq('Not found: bogus',
pcall_err(request, 'nvim_call_dict_function', 'g:d', 'bogus', {1,2}))
@@ -559,7 +576,6 @@ describe('API', function()
local start_dir
before_each(function()
- clear()
funcs.mkdir("Xtestdir")
start_dir = funcs.getcwd()
end)
@@ -575,7 +591,7 @@ describe('API', function()
it('sets previous directory', function()
meths.set_current_dir("Xtestdir")
- meths.exec('cd -', false)
+ command('cd -')
eq(funcs.getcwd(), start_dir)
end)
end)
@@ -634,7 +650,7 @@ describe('API', function()
end)
describe('nvim_input', function()
- it("VimL error: does NOT fail, updates v:errmsg", function()
+ it("Vimscript error: does NOT fail, updates v:errmsg", function()
local status, _ = pcall(nvim, "input", ":call bogus_fn()<CR>")
local v_errnum = string.match(nvim("eval", "v:errmsg"), "E%d*:")
eq(true, status) -- nvim_input() did not fail.
@@ -648,10 +664,10 @@ describe('API', function()
end)
describe('nvim_paste', function()
- it('validates args', function()
- eq('Invalid phase: -2',
+ it('validation', function()
+ eq("Invalid 'phase': -2",
pcall_err(request, 'nvim_paste', 'foo', true, -2))
- eq('Invalid phase: 4',
+ eq("Invalid 'phase': 4",
pcall_err(request, 'nvim_paste', 'foo', true, 4))
end)
local function run_streamed_paste_tests()
@@ -1032,7 +1048,7 @@ describe('API', function()
line 3
]])
eq({0,4,1,0}, funcs.getpos('.')) -- Cursor follows the paste.
- eq(false, nvim('get_option', 'paste'))
+ eq(false, nvim('get_option_value', 'paste', {}))
command('%delete _')
-- Without final "\n".
nvim('paste', 'line 1\nline 2\nline 3', true, -1)
@@ -1072,7 +1088,7 @@ describe('API', function()
nvim('paste', 'line 1\r\n\r\rline 2\nline 3\rline 4\r', true, -1)
expect('line 1\n\n\nline 2\nline 3\nline 4\n')
eq({0,7,1,0}, funcs.getpos('.'))
- eq(false, nvim('get_option', 'paste'))
+ eq(false, nvim('get_option_value', 'paste', {}))
end)
it('Replace-mode', function()
-- Within single line
@@ -1154,10 +1170,10 @@ describe('API', function()
end)
describe('nvim_put', function()
- it('validates args', function()
- eq('Invalid lines (expected array of strings)',
+ it('validation', function()
+ eq("Invalid 'line': expected String, got Integer",
pcall_err(request, 'nvim_put', {42}, 'l', false, false))
- eq("Invalid type: 'x'",
+ eq("Invalid 'type': 'x'",
pcall_err(request, 'nvim_put', {'foo'}, 'x', false, false))
end)
it("fails if 'nomodifiable'", function()
@@ -1259,9 +1275,9 @@ describe('API', function()
yyybc line 2
line 3
]])
- eq("Invalid type: 'bx'",
+ eq("Invalid 'type': 'bx'",
pcall_err(meths.put, {'xxx', 'yyy'}, 'bx', false, true))
- eq("Invalid type: 'b3x'",
+ eq("Invalid 'type': 'b3x'",
pcall_err(meths.put, {'xxx', 'yyy'}, 'b3x', false, true))
end)
end)
@@ -1288,6 +1304,11 @@ describe('API', function()
end)
describe('set/get/del variables', function()
+ it('validation', function()
+ eq('Key not found: bogus', pcall_err(meths.get_var, 'bogus'))
+ eq('Key not found: bogus', pcall_err(meths.del_var, 'bogus'))
+ end)
+
it('nvim_get_var, nvim_set_var, nvim_del_var', function()
nvim('set_var', 'lua', {1, 2, {['3'] = 1}})
eq({1, 2, {['3'] = 1}}, nvim('get_var', 'lua'))
@@ -1329,12 +1350,59 @@ describe('API', function()
end)
it('nvim_get_vvar, nvim_set_vvar', function()
- -- Set readonly v: var.
- eq('Key is read-only: count',
- pcall_err(request, 'nvim_set_vvar', 'count', 42))
- -- Set writable v: var.
+ eq('Key is read-only: count', pcall_err(request, 'nvim_set_vvar', 'count', 42))
+ eq('Dictionary is locked', pcall_err(request, 'nvim_set_vvar', 'nosuchvar', 42))
meths.set_vvar('errmsg', 'set by API')
eq('set by API', meths.get_vvar('errmsg'))
+ meths.set_vvar('errmsg', 42)
+ eq('42', eval('v:errmsg'))
+ meths.set_vvar('oldfiles', { 'one', 'two' })
+ eq({ 'one', 'two' }, eval('v:oldfiles'))
+ meths.set_vvar('oldfiles', {})
+ eq({}, eval('v:oldfiles'))
+ eq('Setting v:oldfiles to value with wrong type', pcall_err(meths.set_vvar, 'oldfiles', 'a'))
+ eq({}, eval('v:oldfiles'))
+
+ feed('i foo foo foo<Esc>0/foo<CR>')
+ eq({1, 1}, meths.win_get_cursor(0))
+ eq(1, eval('v:searchforward'))
+ feed('n')
+ eq({1, 5}, meths.win_get_cursor(0))
+ meths.set_vvar('searchforward', 0)
+ eq(0, eval('v:searchforward'))
+ feed('n')
+ eq({1, 1}, meths.win_get_cursor(0))
+ meths.set_vvar('searchforward', 1)
+ eq(1, eval('v:searchforward'))
+ feed('n')
+ eq({1, 5}, meths.win_get_cursor(0))
+
+ local screen = Screen.new(60, 3)
+ screen:set_default_attr_ids({
+ [0] = {bold = true, foreground = Screen.colors.Blue},
+ [1] = {background = Screen.colors.Yellow},
+ })
+ screen:attach()
+ eq(1, eval('v:hlsearch'))
+ screen:expect{grid=[[
+ {1:foo} {1:^foo} {1:foo} |
+ {0:~ }|
+ |
+ ]]}
+ meths.set_vvar('hlsearch', 0)
+ eq(0, eval('v:hlsearch'))
+ screen:expect{grid=[[
+ foo ^foo foo |
+ {0:~ }|
+ |
+ ]]}
+ meths.set_vvar('hlsearch', 1)
+ eq(1, eval('v:hlsearch'))
+ screen:expect{grid=[[
+ {1:foo} {1:^foo} {1:foo} |
+ {0:~ }|
+ |
+ ]]}
end)
it('vim_set_var returns the old value', function()
@@ -1358,56 +1426,71 @@ describe('API', function()
end)
end)
- describe('nvim_get_option, nvim_set_option', function()
+ describe('nvim_get_option_value, nvim_set_option_value', function()
it('works', function()
- ok(nvim('get_option', 'equalalways'))
- nvim('set_option', 'equalalways', false)
- ok(not nvim('get_option', 'equalalways'))
+ ok(nvim('get_option_value', 'equalalways', {}))
+ nvim('set_option_value', 'equalalways', false, {})
+ ok(not nvim('get_option_value', 'equalalways', {}))
end)
it('works to get global value of local options', function()
- eq(false, nvim('get_option', 'lisp'))
- eq(8, nvim('get_option', 'shiftwidth'))
+ eq(false, nvim('get_option_value', 'lisp', {}))
+ eq(8, nvim('get_option_value', 'shiftwidth', {}))
end)
it('works to set global value of local options', function()
- nvim('set_option', 'lisp', true)
- eq(true, nvim('get_option', 'lisp'))
- eq(false, helpers.curbuf('get_option', 'lisp'))
+ nvim('set_option_value', 'lisp', true, {scope='global'})
+ eq(true, nvim('get_option_value', 'lisp', {scope='global'}))
+ eq(false, nvim('get_option_value', 'lisp', {}))
eq(nil, nvim('command_output', 'setglobal lisp?'):match('nolisp'))
eq('nolisp', nvim('command_output', 'setlocal lisp?'):match('nolisp'))
- nvim('set_option', 'shiftwidth', 20)
+ nvim('set_option_value', 'shiftwidth', 20, {scope='global'})
eq('20', nvim('command_output', 'setglobal shiftwidth?'):match('%d+'))
eq('8', nvim('command_output', 'setlocal shiftwidth?'):match('%d+'))
end)
- it('most window-local options have no global value', function()
- local status, err = pcall(nvim, 'get_option', 'foldcolumn')
- eq(false, status)
- ok(err:match('Invalid option name') ~= nil)
- end)
-
it('updates where the option was last set from', function()
- nvim('set_option', 'equalalways', false)
+ nvim('set_option_value', 'equalalways', false, {})
local status, rv = pcall(nvim, 'command_output',
'verbose set equalalways?')
eq(true, status)
ok(nil ~= string.find(rv, 'noequalalways\n'..
'\tLast set from API client %(channel id %d+%)'))
- nvim('exec_lua', 'vim.api.nvim_set_option("equalalways", true)', {})
+ nvim('exec_lua', 'vim.api.nvim_set_option_value("equalalways", true, {})', {})
status, rv = pcall(nvim, 'command_output',
'verbose set equalalways?')
eq(true, status)
eq(' equalalways\n\tLast set from Lua', rv)
end)
- end)
- describe('nvim_get_option_value, nvim_set_option_value', function()
- it('works', function()
- ok(nvim('get_option_value', 'equalalways', {}))
- nvim('set_option_value', 'equalalways', false, {})
- ok(not nvim('get_option_value', 'equalalways', {}))
+ it('updates whether the option has ever been set #25025', function()
+ eq(false, nvim('get_option_info2', 'autochdir', {}).was_set)
+ nvim('set_option_value', 'autochdir', true, {})
+ eq(true, nvim('get_option_info2', 'autochdir', {}).was_set)
+
+ eq(false, nvim('get_option_info2', 'cmdwinheight', {}).was_set)
+ nvim('set_option_value', 'cmdwinheight', 10, {})
+ eq(true, nvim('get_option_info2', 'cmdwinheight', {}).was_set)
+
+ eq(false, nvim('get_option_info2', 'debug', {}).was_set)
+ nvim('set_option_value', 'debug', 'beep', {})
+ eq(true, nvim('get_option_info2', 'debug', {}).was_set)
+ end)
+
+ it('validation', function()
+ eq("Invalid 'scope': expected 'local' or 'global'",
+ pcall_err(nvim, 'get_option_value', 'scrolloff', {scope = 'bogus'}))
+ eq("Invalid 'scope': expected 'local' or 'global'",
+ pcall_err(nvim, 'set_option_value', 'scrolloff', 1, {scope = 'bogus'}))
+ eq("Invalid 'scope': expected String, got Integer",
+ pcall_err(nvim, 'get_option_value', 'scrolloff', {scope = 42}))
+ eq("Invalid 'value': expected valid option type, got Array",
+ pcall_err(nvim, 'set_option_value', 'scrolloff', {}, {}))
+ eq("Invalid value for option 'scrolloff': expected Number, got Boolean true",
+ pcall_err(nvim, 'set_option_value', 'scrolloff', true, {}))
+ eq("Invalid value for option 'scrolloff': expected Number, got String \"wrong\"",
+ pcall_err(nvim, 'set_option_value', 'scrolloff', 'wrong', {}))
end)
it('can get local values when global value is set', function()
@@ -1453,19 +1536,22 @@ describe('API', function()
-- Now try with options with a special "local is unset" value (e.g. 'undolevels')
nvim('set_option_value', 'undolevels', 1000, {})
- eq(1000, nvim('get_option_value', 'undolevels', {scope = 'local'}))
+ nvim('set_option_value', 'undolevels', 1200, {scope = 'local'})
+ eq(1200, nvim('get_option_value', 'undolevels', {scope = 'local'}))
nvim('set_option_value', 'undolevels', NIL, {scope = 'local'})
eq(-123456, nvim('get_option_value', 'undolevels', {scope = 'local'}))
+ eq(1000, nvim('get_option_value', 'undolevels', {}))
nvim('set_option_value', 'autoread', true, {})
- eq(true, nvim('get_option_value', 'autoread', {scope = 'local'}))
+ nvim('set_option_value', 'autoread', false, {scope = 'local'})
+ eq(false, nvim('get_option_value', 'autoread', {scope = 'local'}))
nvim('set_option_value', 'autoread', NIL, {scope = 'local'})
eq(NIL, nvim('get_option_value', 'autoread', {scope = 'local'}))
+ eq(true, nvim('get_option_value', 'autoread', {}))
end)
it('set window options', function()
- -- Same as to nvim_win_set_option
- nvim('set_option_value', 'colorcolumn', '4,3', {win=0})
+ nvim('set_option_value', 'colorcolumn', '4,3', {})
eq('4,3', nvim('get_option_value', 'colorcolumn', {scope = 'local'}))
command("set modified hidden")
command("enew") -- edit new buffer, window option is preserved
@@ -1473,7 +1559,6 @@ describe('API', function()
end)
it('set local window options', function()
- -- Different to nvim_win_set_option
nvim('set_option_value', 'colorcolumn', '4,3', {win=0, scope='local'})
eq('4,3', nvim('get_option_value', 'colorcolumn', {win = 0, scope = 'local'}))
command("set modified hidden")
@@ -1484,11 +1569,11 @@ describe('API', function()
it('get buffer or window-local options', function()
nvim('command', 'new')
local buf = nvim('get_current_buf').id
- nvim('buf_set_option', buf, 'tagfunc', 'foobar')
+ nvim('set_option_value', 'tagfunc', 'foobar', {buf=buf})
eq('foobar', nvim('get_option_value', 'tagfunc', {buf = buf}))
local win = nvim('get_current_win').id
- nvim('win_set_option', win, 'number', true)
+ nvim('set_option_value', 'number', true, {win=win})
eq(true, nvim('get_option_value', 'number', {win = win}))
end)
@@ -1502,6 +1587,40 @@ describe('API', function()
nvim('get_option_value', 'filetype', {buf = buf})
eq({1, 9}, nvim('win_get_cursor', win))
end)
+
+ it('can get default option values for filetypes', function()
+ command('filetype plugin on')
+ for ft, opts in pairs {
+ lua = { commentstring = '-- %s' },
+ vim = { commentstring = '"%s' },
+ man = { tagfunc = 'v:lua.require\'man\'.goto_tag' },
+ xml = { formatexpr = 'xmlformat#Format()' }
+ } do
+ for option, value in pairs(opts) do
+ eq(value, nvim('get_option_value', option, { filetype = ft }))
+ end
+ end
+
+ command'au FileType lua setlocal commentstring=NEW\\ %s'
+
+ eq('NEW %s', nvim('get_option_value', 'commentstring', { filetype = 'lua' }))
+ end)
+
+ it('errors for bad FileType autocmds', function()
+ command'au FileType lua setlocal commentstring=BAD'
+ eq([[FileType Autocommands for "lua": Vim(setlocal):E537: 'commentstring' must be empty or contain %s: commentstring=BAD]],
+ pcall_err(nvim, 'get_option_value', 'commentstring', { filetype = 'lua' }))
+ end)
+
+ it("value of 'modified' is always false for scratch buffers", function()
+ nvim('set_current_buf', nvim('create_buf', true, true))
+ insert([[
+ foo
+ bar
+ baz
+ ]])
+ eq(false, nvim('get_option_value', 'modified', {}))
+ end)
end)
describe('nvim_{get,set}_current_buf, nvim_list_bufs', function()
@@ -1774,15 +1893,32 @@ describe('API', function()
feed('<C-D>')
expect('a') -- recognized i_0_CTRL-D
end)
+
+ it("does not interrupt with 'digraph'", function()
+ command('set digraph')
+ feed('i,')
+ eq(2, eval('1+1')) -- causes K_EVENT key
+ feed('<BS>')
+ eq(2, eval('1+1')) -- causes K_EVENT key
+ feed('.')
+ expect('…') -- digraph ",." worked
+ feed('<Esc>')
+ feed(':,')
+ eq(2, eval('1+1')) -- causes K_EVENT key
+ feed('<BS>')
+ eq(2, eval('1+1')) -- causes K_EVENT key
+ feed('.')
+ eq('…', funcs.getcmdline()) -- digraph ",." worked
+ end)
end)
describe('nvim_get_context', function()
- it('validates args', function()
+ it('validation', function()
eq("Invalid key: 'blah'",
pcall_err(nvim, 'get_context', {blah={}}))
- eq('invalid value for key: types',
+ eq("Invalid 'types': expected Array, got Integer",
pcall_err(nvim, 'get_context', {types=42}))
- eq('unexpected type: zub',
+ eq("Invalid 'type': 'zub'",
pcall_err(nvim, 'get_context', {types={'jumps', 'zub', 'zam',}}))
end)
it('returns map of current editor state', function()
@@ -1843,6 +1979,13 @@ describe('API', function()
nvim('load_context', ctx)
eq({1, 2 ,3}, eval('[g:one, g:Two, g:THREE]'))
end)
+
+ it('errors when context dictionary is invalid', function()
+ eq('E474: Failed to convert list to msgpack string buffer',
+ pcall_err(nvim, 'load_context', { regs = { {} }, jumps = { {} } }))
+ eq("Empty dictionary keys aren't allowed",
+ pcall_err(nvim, 'load_context', { regs = { { [''] = '' } } }))
+ end)
end)
describe('nvim_replace_termcodes', function()
@@ -1886,6 +2029,12 @@ describe('API', function()
-- value.
eq('', meths.replace_termcodes('', true, true, true))
end)
+
+ -- Not exactly the case, as nvim_replace_termcodes() escapes K_SPECIAL in Unicode
+ it('translates the result of keytrans() on string with 0x80 byte back', function()
+ local s = 'ff\128\253\097tt'
+ eq(s, meths.replace_termcodes(funcs.keytrans(s), true, true, true))
+ end)
end)
describe('nvim_feedkeys', function()
@@ -1916,6 +2065,19 @@ describe('API', function()
end)
describe('nvim_out_write', function()
+ local screen
+
+ before_each(function()
+ screen = Screen.new(40, 8)
+ screen:attach()
+ screen:set_default_attr_ids({
+ [0] = {bold = true, foreground = Screen.colors.Blue},
+ [1] = {bold = true, foreground = Screen.colors.SeaGreen},
+ [2] = {bold = true, reverse = true},
+ [3] = {foreground = Screen.colors.Blue},
+ })
+ end)
+
it('prints long messages correctly #20534', function()
exec([[
set more
@@ -1935,6 +2097,46 @@ describe('API', function()
]])
eq('\naaa\n' .. ('a'):rep(5002) .. '\naaa', meths.get_var('out'))
end)
+
+ it('blank line in message', function()
+ feed([[:call nvim_out_write("\na\n")<CR>]])
+ screen:expect{grid=[[
+ |
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {2: }|
+ |
+ a |
+ {1:Press ENTER or type command to continue}^ |
+ ]]}
+ feed('<CR>')
+ feed([[:call nvim_out_write("b\n\nc\n")<CR>]])
+ screen:expect{grid=[[
+ |
+ {0:~ }|
+ {0:~ }|
+ {2: }|
+ b |
+ |
+ c |
+ {1:Press ENTER or type command to continue}^ |
+ ]]}
+ end)
+
+ it('NUL bytes in message', function()
+ feed([[:lua vim.api.nvim_out_write('aaa\0bbb\0\0ccc\nddd\0\0\0eee\n')<CR>]])
+ screen:expect{grid=[[
+ |
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {2: }|
+ aaa{3:^@}bbb{3:^@^@}ccc |
+ ddd{3:^@^@^@}eee |
+ {1:Press ENTER or type command to continue}^ |
+ ]]}
+ end)
end)
describe('nvim_err_write', function()
@@ -2023,6 +2225,60 @@ describe('API', function()
]])
feed('<cr>') -- exit the press ENTER screen
end)
+
+ it('NUL bytes in message', function()
+ nvim_async('err_write', 'aaa\0bbb\0\0ccc\nddd\0\0\0eee\n')
+ screen:expect{grid=[[
+ |
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {3: }|
+ {1:aaa^@bbb^@^@ccc} |
+ {1:ddd^@^@^@eee} |
+ {2:Press ENTER or type command to continue}^ |
+ ]]}
+ end)
+ end)
+
+ describe('nvim_err_writeln', function()
+ local screen
+
+ before_each(function()
+ screen = Screen.new(40, 8)
+ screen:attach()
+ screen:set_default_attr_ids({
+ [0] = {bold=true, foreground=Screen.colors.Blue},
+ [1] = {foreground = Screen.colors.White, background = Screen.colors.Red},
+ [2] = {bold = true, foreground = Screen.colors.SeaGreen},
+ [3] = {bold = true, reverse = true},
+ })
+ end)
+
+ it('shows only one return prompt after all lines are shown', function()
+ nvim_async('err_writeln', 'FAILURE\nERROR\nEXCEPTION\nTRACEBACK')
+ screen:expect([[
+ |
+ {0:~ }|
+ {3: }|
+ {1:FAILURE} |
+ {1:ERROR} |
+ {1:EXCEPTION} |
+ {1:TRACEBACK} |
+ {2:Press ENTER or type command to continue}^ |
+ ]])
+ feed('<CR>')
+ screen:expect([[
+ ^ |
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ |
+ ]])
+ end)
end)
describe('nvim_list_chans, nvim_get_chan_info', function()
@@ -2115,7 +2371,7 @@ describe('API', function()
it('stream=job :terminal channel', function()
command(':terminal')
eq({id=1}, meths.get_current_buf())
- eq(3, meths.buf_get_option(1, 'channel'))
+ eq(3, meths.get_option_value('channel', {buf=1}))
local info = {
stream='job',
@@ -2223,15 +2479,14 @@ describe('API', function()
eq(5, meths.get_var('avar'))
end)
- it('throws error on malformed arguments', function()
+ it('validation', function()
local req = {
{'nvim_set_var', {'avar', 1}},
{'nvim_set_var'},
{'nvim_set_var', {'avar', 2}},
}
- local status, err = pcall(meths.call_atomic, req)
- eq(false, status)
- ok(err:match('Items in calls array must be arrays of size 2') ~= nil)
+ eq("Invalid 'calls' item: expected 2-item Array",
+ pcall_err(meths.call_atomic, req))
-- call before was done, but not after
eq(1, meths.get_var('avar'))
@@ -2239,18 +2494,16 @@ describe('API', function()
{ 'nvim_set_var', { 'bvar', { 2, 3 } } },
12,
}
- status, err = pcall(meths.call_atomic, req)
- eq(false, status)
- ok(err:match('Items in calls array must be arrays') ~= nil)
+ eq("Invalid 'calls' item: expected Array, got Integer",
+ pcall_err(meths.call_atomic, req))
eq({2,3}, meths.get_var('bvar'))
req = {
{'nvim_set_current_line', 'little line'},
{'nvim_set_var', {'avar', 3}},
}
- status, err = pcall(meths.call_atomic, req)
- eq(false, status)
- ok(err:match('Args must be Array') ~= nil)
+ eq("Invalid call args: expected Array, got String",
+ pcall_err(meths.call_atomic, req))
-- call before was done, but not after
eq(1, meths.get_var('avar'))
eq({''}, meths.buf_get_lines(0, 0, -1, true))
@@ -2271,45 +2524,45 @@ describe('API', function()
end)
it('returns nothing with empty &runtimepath', function()
- meths.set_option('runtimepath', '')
+ meths.set_option_value('runtimepath', '', {})
eq({}, meths.list_runtime_paths())
end)
it('returns single runtimepath', function()
- meths.set_option('runtimepath', 'a')
+ meths.set_option_value('runtimepath', 'a', {})
eq({'a'}, meths.list_runtime_paths())
end)
it('returns two runtimepaths', function()
- meths.set_option('runtimepath', 'a,b')
+ meths.set_option_value('runtimepath', 'a,b', {})
eq({'a', 'b'}, meths.list_runtime_paths())
end)
it('returns empty strings when appropriate', function()
- meths.set_option('runtimepath', 'a,,b')
+ meths.set_option_value('runtimepath', 'a,,b', {})
eq({'a', '', 'b'}, meths.list_runtime_paths())
- meths.set_option('runtimepath', ',a,b')
+ meths.set_option_value('runtimepath', ',a,b', {})
eq({'', 'a', 'b'}, meths.list_runtime_paths())
-- Trailing "," is ignored. Use ",," if you really really want CWD.
- meths.set_option('runtimepath', 'a,b,')
+ meths.set_option_value('runtimepath', 'a,b,', {})
eq({'a', 'b'}, meths.list_runtime_paths())
- meths.set_option('runtimepath', 'a,b,,')
+ meths.set_option_value('runtimepath', 'a,b,,', {})
eq({'a', 'b', ''}, meths.list_runtime_paths())
end)
it('truncates too long paths', function()
local long_path = ('/a'):rep(8192)
- meths.set_option('runtimepath', long_path)
+ meths.set_option_value('runtimepath', long_path, {})
local paths_list = meths.list_runtime_paths()
eq({}, paths_list)
end)
end)
it('can throw exceptions', function()
- local status, err = pcall(nvim, 'get_option', 'invalid-option')
+ local status, err = pcall(nvim, 'get_option_value', 'invalid-option', {})
eq(false, status)
- ok(err:match('Invalid option name') ~= nil)
+ ok(err:match("Unknown option 'invalid%-option'") ~= nil)
end)
it('does not truncate error message <1 MB #5984', function()
local very_long_name = 'A'..('x'):rep(10000)..'Z'
- local status, err = pcall(nvim, 'get_option', very_long_name)
+ local status, err = pcall(nvim, 'get_option_value', very_long_name, {})
eq(false, status)
eq(very_long_name, err:match('Ax+Z?'))
end)
@@ -2322,7 +2575,7 @@ describe('API', function()
describe('nvim_parse_expression', function()
before_each(function()
- meths.set_option('isident', '')
+ meths.set_option_value('isident', '', {})
end)
local function simplify_east_api_node(line, east_api_node)
@@ -2536,20 +2789,26 @@ describe('API', function()
{
chan = 1,
ext_cmdline = false,
- ext_popupmenu = false,
- ext_tabline = false,
- ext_wildmenu = false,
+ ext_hlstate = false,
ext_linegrid = screen._options.ext_linegrid or false,
+ ext_messages = false,
ext_multigrid = false,
- ext_hlstate = false,
+ ext_popupmenu = false,
+ ext_tabline = false,
ext_termcolors = false,
- ext_messages = false,
+ ext_wildmenu = false,
height = 4,
- rgb = true,
override = true,
+ rgb = true,
+ stdin_tty = false,
+ stdout_tty = false,
+ term_background = '',
+ term_colors = 0,
+ term_name = '',
width = 20,
}
}
+
eq(expected, nvim("list_uis"))
screen:detach()
@@ -2601,9 +2860,9 @@ describe('API', function()
end)
it('can change buftype before visiting', function()
- meths.set_option("hidden", false)
+ meths.set_option_value("hidden", false, {})
eq({id=2}, meths.create_buf(true, false))
- meths.buf_set_option(2, "buftype", "nofile")
+ meths.set_option_value("buftype", "nofile", {buf=2})
meths.buf_set_lines(2, 0, -1, true, {"test text"})
command("split | buffer 2")
eq({id=2}, meths.get_current_buf())
@@ -2622,6 +2881,18 @@ describe('API', function()
eq(false, eval('g:fired'))
end)
+ it('TextChanged and TextChangedI do not trigger without changes', function()
+ local buf = meths.create_buf(true, false)
+ command([[let g:changed = '']])
+ meths.create_autocmd({'TextChanged', 'TextChangedI'}, {
+ buffer = buf,
+ command = 'let g:changed ..= mode()',
+ })
+ meths.set_current_buf(buf)
+ feed('i')
+ eq('', meths.get_var('changed'))
+ end)
+
it('scratch-buffer', function()
eq({id=2}, meths.create_buf(false, true))
eq({id=3}, meths.create_buf(true, true))
@@ -2630,7 +2901,7 @@ describe('API', function()
eq(' 1 %a "[No Name]" line 1\n'..
' 3 h "[Scratch]" line 0\n'..
' 4 h "[Scratch]" line 0',
- meths.exec('ls', true))
+ exec_capture('ls'))
-- current buffer didn't change
eq({id=1}, meths.get_current_buf())
@@ -2646,10 +2917,10 @@ describe('API', function()
local edited_buf = 2
meths.buf_set_lines(edited_buf, 0, -1, true, {"some text"})
for _,b in ipairs(scratch_bufs) do
- 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'))
+ eq('nofile', meths.get_option_value('buftype', {buf=b}))
+ eq('hide', meths.get_option_value('bufhidden', {buf=b}))
+ eq(false, meths.get_option_value('swapfile', {buf=b}))
+ eq(false, meths.get_option_value('modeline', {buf=b}))
end
--
@@ -2662,10 +2933,10 @@ describe('API', function()
{1:~ }|
|
]])
- 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'))
+ eq('nofile', meths.get_option_value('buftype', {buf=edited_buf}))
+ eq('hide', meths.get_option_value('bufhidden', {buf=edited_buf}))
+ eq(false, meths.get_option_value('swapfile', {buf=edited_buf}))
+ eq(false, meths.get_option_value('modeline', {buf=edited_buf}))
-- Scratch buffer can be wiped without error.
command('bwipe')
@@ -2679,7 +2950,9 @@ describe('API', function()
it('does not cause heap-use-after-free on exit while setting options', function()
command('au OptionSet * q')
- expect_exit(command, 'silent! call nvim_create_buf(0, 1)')
+ command('silent! call nvim_create_buf(0, 1)')
+ -- nowadays this works because we don't execute any spurious autocmds at all #24824
+ assert_alive()
end)
end)
@@ -2744,13 +3017,13 @@ describe('API', function()
end)
it('should not crash when echoed', function()
- meths.exec("echo nvim_get_all_options_info()", true)
+ meths.exec2("echo nvim_get_all_options_info()", { output = true })
end)
end)
describe('nvim_get_option_info', function()
it('should error for unknown options', function()
- eq("no such option: 'bogus'", pcall_err(meths.get_option_info, 'bogus'))
+ eq("Invalid option (not found): 'bogus'", pcall_err(meths.get_option_info, 'bogus'))
end)
it('should return the same options for short and long name', function()
@@ -2796,7 +3069,7 @@ describe('API', function()
it('should have information about global options', function()
-- precondition: the option was changed from its default
-- in test setup.
- eq(false, meths.get_option'showcmd')
+ eq(false, meths.get_option_value('showcmd', {}))
eq({
allows_duplicates = true,
@@ -2813,6 +3086,117 @@ describe('API', function()
type = "boolean",
was_set = true
}, meths.get_option_info'showcmd')
+
+ meths.set_option_value('showcmd', true, {})
+
+ eq({
+ allows_duplicates = true,
+ commalist = false,
+ default = true,
+ flaglist = false,
+ global_local = false,
+ last_set_chan = 1,
+ last_set_linenr = 0,
+ last_set_sid = -9,
+ name = "showcmd",
+ scope = "global",
+ shortname = "sc",
+ type = "boolean",
+ was_set = true
+ }, meths.get_option_info'showcmd')
+ end)
+ end)
+
+ describe('nvim_get_option_info2', function()
+ local fname
+ local bufs
+ local wins
+
+ before_each(function()
+ fname = tmpname()
+ write_file(fname, [[
+ setglobal dictionary=mydict " 1, global-local (buffer)
+ setlocal formatprg=myprg " 2, global-local (buffer)
+ setglobal equalprg=prg1 " 3, global-local (buffer)
+ setlocal equalprg=prg2 " 4, global-local (buffer)
+ setglobal fillchars=stl:x " 5, global-local (window)
+ setlocal listchars=eol:c " 6, global-local (window)
+ setglobal showbreak=aaa " 7, global-local (window)
+ setlocal showbreak=bbb " 8, global-local (window)
+ setglobal completeopt=menu " 9, global
+ ]])
+
+ exec_lua 'vim.cmd.vsplit()'
+ meths.create_buf(false, false)
+
+ bufs = meths.list_bufs()
+ wins = meths.list_wins()
+
+ meths.win_set_buf(wins[1].id, bufs[1].id)
+ meths.win_set_buf(wins[2].id, bufs[2].id)
+
+ meths.set_current_win(wins[2].id)
+ meths.exec('source ' .. fname, false)
+
+ meths.set_current_win(wins[1].id)
+ end)
+
+ after_each(function()
+ os.remove(fname)
+ end)
+
+ it('should return option information', function()
+ eq(meths.get_option_info('dictionary'), meths.get_option_info2('dictionary', {})) -- buffer
+ eq(meths.get_option_info('fillchars'), meths.get_option_info2('fillchars', {})) -- window
+ eq(meths.get_option_info('completeopt'), meths.get_option_info2('completeopt', {})) -- global
+ end)
+
+ describe('last set', function()
+ local tests = {
+ {desc="(buf option, global requested, global set) points to global", linenr=1, sid=1, args={'dictionary', {scope='global'}}},
+ {desc="(buf option, global requested, local set) is not set", linenr=0, sid=0, args={'formatprg', {scope='global'}}},
+ {desc="(buf option, global requested, both set) points to global", linenr=3, sid=1, args={'equalprg', {scope='global'}}},
+ {desc="(buf option, local requested, global set) is not set", linenr=0, sid=0, args={'dictionary', {scope='local'}}},
+ {desc="(buf option, local requested, local set) points to local", linenr=2, sid=1, args={'formatprg', {scope='local'}}},
+ {desc="(buf option, local requested, both set) points to local", linenr=4, sid=1, args={'equalprg', {scope='local'}}},
+ {desc="(buf option, fallback requested, global set) points to global", linenr=1, sid=1, args={'dictionary', {}}},
+ {desc="(buf option, fallback requested, local set) points to local", linenr=2, sid=1, args={'formatprg', {}}},
+ {desc="(buf option, fallback requested, both set) points to local", linenr=4, sid=1, args={'equalprg', {}}},
+ {desc="(win option, global requested, global set) points to global", linenr=5, sid=1, args={'fillchars', {scope='global'}}},
+ {desc="(win option, global requested, local set) is not set", linenr=0, sid=0, args={'listchars', {scope='global'}}},
+ {desc="(win option, global requested, both set) points to global", linenr=7, sid=1, args={'showbreak', {scope='global'}}},
+ {desc="(win option, local requested, global set) is not set", linenr=0, sid=0, args={'fillchars', {scope='local'}}},
+ {desc="(win option, local requested, local set) points to local", linenr=6, sid=1, args={'listchars', {scope='local'}}},
+ {desc="(win option, local requested, both set) points to local", linenr=8, sid=1, args={'showbreak', {scope='local'}}},
+ {desc="(win option, fallback requested, global set) points to global", linenr=5, sid=1, args={'fillchars', {}}},
+ {desc="(win option, fallback requested, local set) points to local", linenr=6, sid=1, args={'listchars', {}}},
+ {desc="(win option, fallback requested, both set) points to local", linenr=8, sid=1, args={'showbreak', {}}},
+ {desc="(global option, global requested) points to global", linenr=9, sid=1, args={'completeopt', {scope='global'}}},
+ {desc="(global option, local requested) is not set", linenr=0, sid=0, args={'completeopt', {scope='local'}}},
+ {desc="(global option, fallback requested) points to global", linenr=9, sid=1, args={'completeopt', {}}},
+ }
+
+ for _, t in pairs(tests) do
+ it(t.desc, function()
+ -- Switch to the target buffer/window so that curbuf/curwin are used.
+ meths.set_current_win(wins[2].id)
+ local info = meths.get_option_info2(unpack(t.args))
+ eq(t.linenr, info.last_set_linenr)
+ eq(t.sid, info.last_set_sid)
+ end)
+ end
+
+ it('is provided for cross-buffer requests', function()
+ local info = meths.get_option_info2('formatprg', {buf=bufs[2].id})
+ eq(2, info.last_set_linenr)
+ eq(1, info.last_set_sid)
+ end)
+
+ it('is provided for cross-window requests', function()
+ local info = meths.get_option_info2('listchars', {win=wins[2].id})
+ eq(6, info.last_set_linenr)
+ eq(1, info.last_set_sid)
+ end)
end)
end)
@@ -2820,11 +3204,10 @@ describe('API', function()
local screen
before_each(function()
- clear()
screen = Screen.new(40, 8)
screen:attach()
screen:set_default_attr_ids({
- [0] = {bold=true, foreground=Screen.colors.Blue},
+ [0] = {bold = true, foreground = Screen.colors.Blue},
[1] = {bold = true, foreground = Screen.colors.SeaGreen},
[2] = {bold = true, reverse = true},
[3] = {foreground = Screen.colors.Brown, bold = true}, -- Statement
@@ -2879,13 +3262,13 @@ describe('API', function()
it('can save message history', function()
nvim('command', 'set cmdheight=2') -- suppress Press ENTER
nvim("echo", {{"msg\nmsg"}, {"msg"}}, true, {})
- eq("msg\nmsgmsg", meths.exec('messages', true))
+ eq("msg\nmsgmsg", exec_capture('messages'))
end)
it('can disable saving message history', function()
nvim('command', 'set cmdheight=2') -- suppress Press ENTER
nvim_async("echo", {{"msg\nmsg"}, {"msg"}}, false, {})
- eq("", meths.exec("messages", true))
+ eq("", exec_capture('messages'))
end)
end)
@@ -2894,7 +3277,6 @@ describe('API', function()
local screen
before_each(function()
- clear()
screen = Screen.new(100, 35)
screen:attach()
screen:set_default_attr_ids({
@@ -3031,10 +3413,10 @@ describe('API', function()
eq(true, meths.del_mark('F'))
eq({0, 0}, meths.buf_get_mark(buf, 'F'))
end)
- it('fails when invalid marks are used', function()
- eq(false, pcall(meths.del_mark, 'f'))
- eq(false, pcall(meths.del_mark, '!'))
- eq(false, pcall(meths.del_mark, 'fail'))
+ it('validation', function()
+ eq("Invalid mark name (must be file/uppercase): 'f'", pcall_err(meths.del_mark, 'f'))
+ eq("Invalid mark name (must be file/uppercase): '!'", pcall_err(meths.del_mark, '!'))
+ eq("Invalid mark name (must be a single char): 'fail'", pcall_err(meths.del_mark, 'fail'))
end)
end)
describe('nvim_get_mark', function()
@@ -3048,10 +3430,10 @@ describe('API', function()
assert(string.find(mark[4], "mybuf$"))
eq({2, 2, buf.id, mark[4]}, mark)
end)
- it('fails when invalid marks are used', function()
- eq(false, pcall(meths.del_mark, 'f'))
- eq(false, pcall(meths.del_mark, '!'))
- eq(false, pcall(meths.del_mark, 'fail'))
+ it('validation', function()
+ eq("Invalid mark name (must be file/uppercase): 'f'", pcall_err(meths.get_mark, 'f', {}))
+ eq("Invalid mark name (must be file/uppercase): '!'", pcall_err(meths.get_mark, '!', {}))
+ eq("Invalid mark name (must be a single char): 'fail'", pcall_err(meths.get_mark, 'fail', {}))
end)
it('returns the expected when mark is not set', function()
eq(true, meths.del_mark('A'))
@@ -3113,15 +3495,15 @@ describe('API', function()
meths.eval_statusline('a%=b', { fillchar = '\031', maxwidth = 5 }))
end)
it('rejects multiple-character fillchar', function()
- eq('fillchar must be a single character',
+ eq("Invalid 'fillchar': expected single character",
pcall_err(meths.eval_statusline, '', { fillchar = 'aa' }))
end)
it('rejects empty string fillchar', function()
- eq('fillchar must be a single character',
+ eq("Invalid 'fillchar': expected single character",
pcall_err(meths.eval_statusline, '', { fillchar = '' }))
end)
it('rejects non-string fillchar', function()
- eq('fillchar must be a single character',
+ eq("Invalid 'fillchar': expected String, got Integer",
pcall_err(meths.eval_statusline, '', { fillchar = 1 }))
end)
it('rejects invalid string', function()
@@ -3213,6 +3595,38 @@ describe('API', function()
'TextWithNoHighlight%#WarningMsg#TextWithWarningHighlight',
{ use_winbar = true, highlights = true }))
end)
+ it('works with statuscolumn', function()
+ exec([[
+ let &stc='%C%s%=%l '
+ set cul nu nuw=3 scl=yes:2 fdc=2
+ call setline(1, repeat(['aaaaa'], 5))
+ let g:ns = nvim_create_namespace('')
+ call sign_define('a', {'text':'aa', 'texthl':'IncSearch', 'numhl':'Normal'})
+ call sign_place(2, 1, 'a', bufnr(), {'lnum':4})
+ call nvim_buf_set_extmark(0, g:ns, 3, 1, { 'sign_text':'bb', 'sign_hl_group':'ErrorMsg' })
+ 1,5fold | 1,5 fold | foldopen!
+ norm 4G
+ ]])
+ eq({
+ str = '││aabb 4 ',
+ width = 9,
+ highlights = {
+ { group = 'CursorLineFold', start = 0 },
+ { group = 'Normal', start = 6 },
+ { group = 'IncSearch', start = 6 },
+ { group = 'ErrorMsg', start = 8 },
+ { group = 'Normal', start = 10 }
+ }
+ }, meths.eval_statusline('%C%s%=%l ', { use_statuscol_lnum = 4, highlights = true }))
+ eq({
+ str = '3 ' ,
+ width = 2,
+ highlights = {
+ { group = 'LineNr', start = 0 },
+ { group = 'ErrorMsg', start = 1 }
+ }
+ }, meths.eval_statusline('%l%#ErrorMsg# ', { use_statuscol_lnum = 3, highlights = true }))
+ end)
it('no memory leak with click functions', function()
meths.eval_statusline('%@ClickFunc@StatusLineStringWithClickFunc%T', {})
eq({
@@ -3226,6 +3640,7 @@ describe('API', function()
end)
end)
end)
+
describe('nvim_parse_cmd', function()
it('works', function()
eq({
@@ -3708,7 +4123,7 @@ describe('API', function()
}
}, meths.parse_cmd('MyCommand test it', {}))
end)
- it('errors for invalid command', function()
+ it('validates command', function()
eq('Error while parsing command line', pcall_err(meths.parse_cmd, '', {}))
eq('Error while parsing command line', pcall_err(meths.parse_cmd, '" foo', {}))
eq('Error while parsing command line: E492: Not an editor command: Fubar',
@@ -3807,20 +4222,87 @@ describe('API', function()
meths.cmd(meths.parse_cmd("set cursorline", {}), {})
eq(true, meths.get_option_value("cursorline", {}))
end)
+ it('no side-effects (error messages) in pcall() #20339', function()
+ eq({ false, 'Error while parsing command line: E16: Invalid range' },
+ exec_lua([=[return {pcall(vim.api.nvim_parse_cmd, "'<,'>n", {})}]=]))
+ eq('', eval('v:errmsg'))
+ end)
end)
+
describe('nvim_cmd', function()
it('works', function ()
meths.cmd({ cmd = "set", args = { "cursorline" } }, {})
eq(true, meths.get_option_value("cursorline", {}))
end)
+
+ it('validation', function()
+ eq("Invalid 'cmd': expected non-empty String",
+ pcall_err(meths.cmd, { cmd = ""}, {}))
+ eq("Invalid 'cmd': expected String, got Array",
+ pcall_err(meths.cmd, { cmd = {}}, {}))
+ eq("Invalid 'args': expected Array, got Boolean",
+ pcall_err(meths.cmd, { cmd = "set", args = true }, {}))
+ eq("Invalid command arg: expected non-whitespace",
+ pcall_err(meths.cmd, { cmd = "set", args = {' '}, }, {}))
+ eq("Invalid command arg: expected valid type, got Array",
+ pcall_err(meths.cmd, { cmd = "set", args = {{}}, }, {}))
+ eq("Wrong number of arguments",
+ pcall_err(meths.cmd, { cmd = "aboveleft", args = {}, }, {}))
+ eq("Command cannot accept bang: print",
+ pcall_err(meths.cmd, { cmd = "print", args = {}, bang = true }, {}))
+
+ eq("Command cannot accept range: set",
+ pcall_err(meths.cmd, { cmd = "set", args = {}, range = {1} }, {}))
+ eq("Invalid 'range': expected Array, got Boolean",
+ pcall_err(meths.cmd, { cmd = "print", args = {}, range = true }, {}))
+ eq("Invalid 'range': expected <=2 elements",
+ pcall_err(meths.cmd, { cmd = "print", args = {}, range = {1,2,3,4} }, {}))
+ eq("Invalid range element: expected non-negative Integer",
+ pcall_err(meths.cmd, { cmd = "print", args = {}, range = {-1} }, {}))
+
+ eq("Command cannot accept count: set",
+ pcall_err(meths.cmd, { cmd = "set", args = {}, count = 1 }, {}))
+ eq("Invalid 'count': expected Integer, got Boolean",
+ pcall_err(meths.cmd, { cmd = "print", args = {}, count = true }, {}))
+ eq("Invalid 'count': expected non-negative Integer",
+ pcall_err(meths.cmd, { cmd = "print", args = {}, count = -1 }, {}))
+
+ eq("Command cannot accept register: set",
+ pcall_err(meths.cmd, { cmd = "set", args = {}, reg = 'x' }, {}))
+ eq('Cannot use register "=',
+ pcall_err(meths.cmd, { cmd = "put", args = {}, reg = '=' }, {}))
+ eq("Invalid 'reg': expected single character, got xx",
+ pcall_err(meths.cmd, { cmd = "put", args = {}, reg = 'xx' }, {}))
+
+ -- #20681
+ eq('Invalid command: "win_getid"', pcall_err(meths.cmd, { cmd = 'win_getid'}, {}))
+ eq('Invalid command: "echo "hi""', pcall_err(meths.cmd, { cmd = 'echo "hi"'}, {}))
+ eq('Invalid command: "win_getid"', pcall_err(exec_lua, [[return vim.cmd.win_getid{}]]))
+
+ -- Lua call allows empty {} for dict item.
+ eq('', exec_lua([[return vim.cmd{ cmd = "set", args = {}, magic = {} }]]))
+ eq('', exec_lua([[return vim.cmd{ cmd = "set", args = {}, mods = {} }]]))
+ eq('', meths.cmd({ cmd = "set", args = {}, magic = {} }, {}))
+
+ -- Lua call does not allow non-empty list-like {} for dict item.
+ eq("Invalid 'magic': Expected Dict-like Lua table",
+ pcall_err(exec_lua, [[return vim.cmd{ cmd = "set", args = {}, magic = { 'a' } }]]))
+ eq("Invalid key: 'bogus'",
+ pcall_err(exec_lua, [[return vim.cmd{ cmd = "set", args = {}, magic = { bogus = true } }]]))
+ eq("Invalid key: 'bogus'",
+ pcall_err(exec_lua, [[return vim.cmd{ cmd = "set", args = {}, mods = { bogus = true } }]]))
+ end)
+
it('captures output', function()
eq("foo", meths.cmd({ cmd = "echo", args = { '"foo"' } }, { output = true }))
end)
+
it('sets correct script context', function()
meths.cmd({ cmd = "set", args = { "cursorline" } }, {})
- local str = meths.exec([[verbose set cursorline?]], true)
+ local str = exec_capture([[verbose set cursorline?]])
neq(nil, str:find("cursorline\n\tLast set from API client %(channel id %d+%)"))
end)
+
it('works with range', function()
insert [[
line1
@@ -3867,7 +4349,7 @@ describe('API', function()
line6
]]
meths.cmd({ cmd = "del", range = { 2, 4 }, reg = 'a' }, {})
- meths.exec("1put a", false)
+ command('1put a')
expect [[
line1
line2
@@ -3914,7 +4396,7 @@ describe('API', function()
meths.cmd({ cmd = "buffers", mods = { filter = { pattern = "foo", force = true } } },
{ output = true }))
- -- with emsg_silent = true error is suppresed
+ -- with emsg_silent = true error is suppressed
feed([[:lua vim.api.nvim_cmd({ cmd = 'call', mods = { emsg_silent = true } }, {})<CR>]])
eq('', meths.cmd({ cmd = 'messages' }, { output = true }))
-- error from the next command typed is not suppressed #21420
@@ -3927,16 +4409,16 @@ describe('API', function()
vim.api.nvim_echo({{ opts.fargs[1] }}, false, {})
end, { nargs = 1 })
]])
- eq(lfs.currentdir(),
+ eq(luv.cwd(),
meths.cmd({ cmd = "Foo", args = { '%:p:h' }, magic = { file = true } },
{ output = true }))
end)
it('splits arguments correctly', function()
- meths.exec([[
+ exec([[
function! FooFunc(...)
echo a:000
endfunction
- ]], false)
+ ]])
meths.create_user_command("Foo", "call FooFunc(<f-args>)", { nargs = '+' })
eq([=[['a quick', 'brown fox', 'jumps over the', 'lazy dog']]=],
meths.cmd({ cmd = "Foo", args = { "a quick", "brown fox", "jumps over the", "lazy dog"}},
@@ -3948,7 +4430,7 @@ describe('API', function()
it('splits arguments correctly for Lua callback', function()
meths.exec_lua([[
local function FooFunc(opts)
- vim.pretty_print(opts.fargs)
+ vim.print(opts.fargs)
end
vim.api.nvim_create_user_command("Foo", FooFunc, { nargs = '+' })
@@ -4054,8 +4536,6 @@ describe('API', function()
eq('1', meths.cmd({cmd = 'echo', args = {true}}, {output = true}))
end)
describe('first argument as count', function()
- before_each(clear)
-
it('works', function()
command('vsplit | enew')
meths.cmd({cmd = 'bdelete', args = {meths.get_current_buf()}}, {})
diff --git a/test/functional/api/window_spec.lua b/test/functional/api/window_spec.lua
index ecab6a4713..6737c2d15b 100644
--- a/test/functional/api/window_spec.lua
+++ b/test/functional/api/window_spec.lua
@@ -43,6 +43,25 @@ describe('API/win', function()
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)
+
+ it('disallowed in cmdwin if win={old_}curwin or buf=curbuf', function()
+ local new_buf = meths.create_buf(true, true)
+ local old_win = meths.get_current_win()
+ local new_win = meths.open_win(new_buf, false, {
+ relative='editor', row=10, col=10, width=50, height=10,
+ })
+ feed('q:')
+ eq('E11: Invalid in command-line window; <CR> executes, CTRL-C quits',
+ pcall_err(meths.win_set_buf, 0, new_buf))
+ eq('E11: Invalid in command-line window; <CR> executes, CTRL-C quits',
+ pcall_err(meths.win_set_buf, old_win, new_buf))
+ eq('E11: Invalid in command-line window; <CR> executes, CTRL-C quits',
+ pcall_err(meths.win_set_buf, new_win, 0))
+
+ local next_buf = meths.create_buf(true, true)
+ meths.win_set_buf(new_win, next_buf)
+ eq(next_buf, meths.win_get_buf(new_win))
+ end)
end)
describe('{get,set}_cursor', function()
@@ -285,6 +304,22 @@ describe('API/win', function()
eq(2, window('get_height', nvim('list_wins')[2]))
end)
+ it('correctly handles height=1', function()
+ nvim('command', 'split')
+ nvim('set_current_win', nvim('list_wins')[1])
+ window('set_height', nvim('list_wins')[2], 1)
+ eq(1, window('get_height', nvim('list_wins')[2]))
+ end)
+
+ it('correctly handles height=1 with a winbar', function()
+ nvim('command', 'set winbar=foobar')
+ nvim('command', 'set winminheight=0')
+ nvim('command', 'split')
+ nvim('set_current_win', nvim('list_wins')[1])
+ window('set_height', nvim('list_wins')[2], 1)
+ eq(1, window('get_height', nvim('list_wins')[2]))
+ end)
+
it('do not cause ml_get errors with foldmethod=expr #19989', function()
insert([[
aaaaa
@@ -363,22 +398,22 @@ describe('API/win', function()
end)
end)
- describe('nvim_win_get_option, nvim_win_set_option', function()
+ describe('nvim_get_option_value, nvim_set_option_value', function()
it('works', function()
- curwin('set_option', 'colorcolumn', '4,3')
- eq('4,3', curwin('get_option', 'colorcolumn'))
+ nvim('set_option_value', 'colorcolumn', '4,3', {})
+ eq('4,3', nvim('get_option_value', 'colorcolumn', {}))
command("set modified hidden")
command("enew") -- edit new buffer, window option is preserved
- eq('4,3', curwin('get_option', 'colorcolumn'))
+ eq('4,3', nvim('get_option_value', 'colorcolumn', {}))
-- global-local option
- curwin('set_option', 'statusline', 'window-status')
- eq('window-status', curwin('get_option', 'statusline'))
- eq('', nvim('get_option', 'statusline'))
+ nvim('set_option_value', 'statusline', 'window-status', {win=0})
+ eq('window-status', nvim('get_option_value', 'statusline', {win=0}))
+ eq('', nvim('get_option_value', 'statusline', {scope='global'}))
command("set modified")
command("enew") -- global-local: not preserved in new buffer
-- confirm local value was not copied
- eq('', curwin('get_option', 'statusline'))
+ eq('', nvim('get_option_value', 'statusline', {win = 0}))
eq('', eval('&l:statusline'))
end)
@@ -386,16 +421,16 @@ describe('API/win', function()
nvim('command', 'tabnew')
local tab1 = unpack(nvim('list_tabpages'))
local win1 = unpack(tabpage('list_wins', tab1))
- window('set_option', win1, 'statusline', 'window-status')
+ nvim('set_option_value', 'statusline', 'window-status', {win=win1.id})
nvim('command', 'split')
nvim('command', 'wincmd J')
nvim('command', 'wincmd j')
- eq('window-status', window('get_option', win1, 'statusline'))
+ eq('window-status', nvim('get_option_value', 'statusline', {win = win1.id}))
assert_alive()
end)
it('returns values for unset local options', function()
- eq(-1, curwin('get_option', 'scrolloff'))
+ eq(-1, nvim('get_option_value', 'scrolloff', {win=0, scope='local'}))
end)
end)
@@ -508,15 +543,21 @@ describe('API/win', function()
command('split')
eq(2, #meths.list_wins())
local oldwin = meths.get_current_win()
+ local otherwin = meths.open_win(0, false, {
+ relative='editor', row=10, col=10, width=10, height=10,
+ })
-- Open cmdline-window.
feed('q:')
- eq(3, #meths.list_wins())
+ eq(4, #meths.list_wins())
eq(':', funcs.getcmdwintype())
- -- Vim: not allowed to close other windows from cmdline-window.
+ -- Not allowed to close previous window from cmdline-window.
eq('E11: Invalid in command-line window; <CR> executes, CTRL-C quits',
- pcall_err(meths.win_close, oldwin, true))
+ pcall_err(meths.win_close, oldwin, true))
+ -- Closing other windows is fine.
+ meths.win_close(otherwin, true)
+ eq(false, meths.win_is_valid(otherwin))
-- Close cmdline-window.
- meths.win_close(0,true)
+ meths.win_close(0, true)
eq(2, #meths.list_wins())
eq('', funcs.getcmdwintype())
end)
@@ -568,15 +609,247 @@ describe('API/win', function()
it('deletes the buffer when bufhidden=wipe', function()
local oldwin = meths.get_current_win()
local oldbuf = meths.get_current_buf()
- local buf = meths.create_buf(true, false)
+ local buf = meths.create_buf(true, false).id
local newwin = meths.open_win(buf, true, {
relative='win', row=3, col=3, width=12, height=3
})
- meths.buf_set_option(buf, 'bufhidden', 'wipe')
+ meths.set_option_value('bufhidden', 'wipe', {buf=buf})
meths.win_hide(newwin)
eq({oldwin}, meths.list_wins())
eq({oldbuf}, meths.list_bufs())
end)
+ it('in the cmdwin', function()
+ feed('q:')
+ -- Can close the cmdwin.
+ meths.win_hide(0)
+ eq('', funcs.getcmdwintype())
+
+ local old_win = meths.get_current_win()
+ local other_win = meths.open_win(0, false, {
+ relative='win', row=3, col=3, width=12, height=3
+ })
+ feed('q:')
+ -- Cannot close the previous window.
+ eq('E11: Invalid in command-line window; <CR> executes, CTRL-C quits',
+ pcall_err(meths.win_hide, old_win))
+ -- Can close other windows.
+ meths.win_hide(other_win)
+ eq(false, meths.win_is_valid(other_win))
+ end)
+ end)
+
+ describe('text_height', function()
+ it('validation', function()
+ local X = meths.get_vvar('maxcol')
+ insert([[
+ aaa
+ bbb
+ ccc
+ ddd
+ eee]])
+ eq("Invalid window id: 23",
+ pcall_err(meths.win_text_height, 23, {}))
+ eq("Line index out of bounds",
+ pcall_err(curwinmeths.text_height, { start_row = 5 }))
+ eq("Line index out of bounds",
+ pcall_err(curwinmeths.text_height, { start_row = -6 }))
+ eq("Line index out of bounds",
+ pcall_err(curwinmeths.text_height, { end_row = 5 }))
+ eq("Line index out of bounds",
+ pcall_err(curwinmeths.text_height, { end_row = -6 }))
+ eq("'start_row' is higher than 'end_row'",
+ pcall_err(curwinmeths.text_height, { start_row = 3, end_row = 1 }))
+ eq("'start_vcol' specified without 'start_row'",
+ pcall_err(curwinmeths.text_height, { end_row = 2, start_vcol = 0 }))
+ eq("'end_vcol' specified without 'end_row'",
+ pcall_err(curwinmeths.text_height, { start_row = 2, end_vcol = 0 }))
+ eq("Invalid 'start_vcol': out of range",
+ pcall_err(curwinmeths.text_height, { start_row = 2, start_vcol = -1 }))
+ eq("Invalid 'start_vcol': out of range",
+ pcall_err(curwinmeths.text_height, { start_row = 2, start_vcol = X + 1 }))
+ eq("Invalid 'end_vcol': out of range",
+ pcall_err(curwinmeths.text_height, { end_row = 2, end_vcol = -1 }))
+ eq("Invalid 'end_vcol': out of range",
+ pcall_err(curwinmeths.text_height, { end_row = 2, end_vcol = X + 1 }))
+ eq("'start_vcol' is higher than 'end_vcol'",
+ pcall_err(curwinmeths.text_height, { start_row = 2, end_row = 2, start_vcol = 10, end_vcol = 5 }))
+ end)
+
+ it('with two diff windows', function()
+ local X = meths.get_vvar('maxcol')
+ local screen = Screen.new(45, 22)
+ screen:set_default_attr_ids({
+ [0] = {foreground = Screen.colors.Blue1, bold = true};
+ [1] = {foreground = Screen.colors.Blue4, background = Screen.colors.Grey};
+ [2] = {foreground = Screen.colors.Brown};
+ [3] = {foreground = Screen.colors.Blue1, background = Screen.colors.LightCyan1, bold = true};
+ [4] = {background = Screen.colors.LightBlue};
+ [5] = {foreground = Screen.colors.Blue4, background = Screen.colors.LightGrey};
+ [6] = {background = Screen.colors.Plum1};
+ [7] = {background = Screen.colors.Red, bold = true};
+ [8] = {reverse = true};
+ [9] = {bold = true, reverse = true};
+ })
+ screen:attach()
+ exec([[
+ set diffopt+=context:2 number
+ let expr = 'printf("%08d", v:val) .. repeat("!", v:val)'
+ call setline(1, map(range(1, 20) + range(25, 45), expr))
+ vnew
+ call setline(1, map(range(3, 20) + range(28, 50), expr))
+ windo diffthis
+ ]])
+ feed('24gg')
+ screen:expect{grid=[[
+ {1: }{2: }{3:----------------}│{1: }{2: 1 }{4:00000001! }|
+ {1: }{2: }{3:----------------}│{1: }{2: 2 }{4:00000002!! }|
+ {1: }{2: 1 }00000003!!! │{1: }{2: 3 }00000003!!! |
+ {1: }{2: 2 }00000004!!!! │{1: }{2: 4 }00000004!!!! |
+ {1:+ }{2: 3 }{5:+-- 14 lines: 00}│{1:+ }{2: 5 }{5:+-- 14 lines: 00}|
+ {1: }{2: 17 }00000019!!!!!!!!│{1: }{2: 19 }00000019!!!!!!!!|
+ {1: }{2: 18 }00000020!!!!!!!!│{1: }{2: 20 }00000020!!!!!!!!|
+ {1: }{2: }{3:----------------}│{1: }{2: 21 }{4:00000025!!!!!!!!}|
+ {1: }{2: }{3:----------------}│{1: }{2: 22 }{4:00000026!!!!!!!!}|
+ {1: }{2: }{3:----------------}│{1: }{2: 23 }{4:00000027!!!!!!!!}|
+ {1: }{2: 19 }00000028!!!!!!!!│{1: }{2: 24 }^00000028!!!!!!!!|
+ {1: }{2: 20 }00000029!!!!!!!!│{1: }{2: 25 }00000029!!!!!!!!|
+ {1:+ }{2: 21 }{5:+-- 14 lines: 00}│{1:+ }{2: 26 }{5:+-- 14 lines: 00}|
+ {1: }{2: 35 }00000044!!!!!!!!│{1: }{2: 40 }00000044!!!!!!!!|
+ {1: }{2: 36 }00000045!!!!!!!!│{1: }{2: 41 }00000045!!!!!!!!|
+ {1: }{2: 37 }{4:00000046!!!!!!!!}│{1: }{2: }{3:----------------}|
+ {1: }{2: 38 }{4:00000047!!!!!!!!}│{1: }{2: }{3:----------------}|
+ {1: }{2: 39 }{4:00000048!!!!!!!!}│{1: }{2: }{3:----------------}|
+ {1: }{2: 40 }{4:00000049!!!!!!!!}│{1: }{2: }{3:----------------}|
+ {1: }{2: 41 }{4:00000050!!!!!!!!}│{1: }{2: }{3:----------------}|
+ {8:[No Name] [+] }{9:[No Name] [+] }|
+ |
+ ]]}
+ screen:try_resize(45, 3)
+ screen:expect{grid=[[
+ {1: }{2: 19 }00000028!!!!!!!!│{1: }{2: 24 }^00000028!!!!!!!!|
+ {8:[No Name] [+] }{9:[No Name] [+] }|
+ |
+ ]]}
+ eq({ all = 20, fill = 5 }, meths.win_text_height(1000, {}))
+ eq({ all = 20, fill = 5 }, meths.win_text_height(1001, {}))
+ eq({ all = 20, fill = 5 }, meths.win_text_height(1000, { start_row = 0 }))
+ eq({ all = 20, fill = 5 }, meths.win_text_height(1001, { start_row = 0 }))
+ eq({ all = 15, fill = 0 }, meths.win_text_height(1000, { end_row = -1 }))
+ eq({ all = 15, fill = 0 }, meths.win_text_height(1000, { end_row = 40 }))
+ eq({ all = 20, fill = 5 }, meths.win_text_height(1001, { end_row = -1 }))
+ eq({ all = 20, fill = 5 }, meths.win_text_height(1001, { end_row = 40 }))
+ eq({ all = 10, fill = 5 }, meths.win_text_height(1000, { start_row = 23 }))
+ eq({ all = 13, fill = 3 }, meths.win_text_height(1001, { start_row = 18 }))
+ eq({ all = 11, fill = 0 }, meths.win_text_height(1000, { end_row = 23 }))
+ eq({ all = 11, fill = 5 }, meths.win_text_height(1001, { end_row = 18 }))
+ eq({ all = 11, fill = 0 }, meths.win_text_height(1000, { start_row = 3, end_row = 39 }))
+ eq({ all = 11, fill = 3 }, meths.win_text_height(1001, { start_row = 1, end_row = 34 }))
+ eq({ all = 9, fill = 0 }, meths.win_text_height(1000, { start_row = 4, end_row = 38 }))
+ eq({ all = 9, fill = 3 }, meths.win_text_height(1001, { start_row = 2, end_row = 33 }))
+ eq({ all = 9, fill = 0 }, meths.win_text_height(1000, { start_row = 5, end_row = 37 }))
+ eq({ all = 9, fill = 3 }, meths.win_text_height(1001, { start_row = 3, end_row = 32 }))
+ eq({ all = 9, fill = 0 }, meths.win_text_height(1000, { start_row = 17, end_row = 25 }))
+ eq({ all = 9, fill = 3 }, meths.win_text_height(1001, { start_row = 15, end_row = 20 }))
+ eq({ all = 7, fill = 0 }, meths.win_text_height(1000, { start_row = 18, end_row = 24 }))
+ eq({ all = 7, fill = 3 }, meths.win_text_height(1001, { start_row = 16, end_row = 19 }))
+ eq({ all = 6, fill = 5 }, meths.win_text_height(1000, { start_row = -1 }))
+ eq({ all = 5, fill = 5 }, meths.win_text_height(1000, { start_row = -1, start_vcol = X }))
+ eq({ all = 0, fill = 0 }, meths.win_text_height(1000, { start_row = -1, start_vcol = X, end_row = -1 }))
+ eq({ all = 0, fill = 0 }, meths.win_text_height(1000, { start_row = -1, start_vcol = X, end_row = -1, end_vcol = X }))
+ eq({ all = 1, fill = 0 }, meths.win_text_height(1000, { start_row = -1, start_vcol = 0, end_row = -1, end_vcol = X }))
+ eq({ all = 3, fill = 2 }, meths.win_text_height(1001, { end_row = 0 }))
+ eq({ all = 2, fill = 2 }, meths.win_text_height(1001, { end_row = 0, end_vcol = 0 }))
+ eq({ all = 2, fill = 2 }, meths.win_text_height(1001, { start_row = 0, end_row = 0, end_vcol = 0 }))
+ eq({ all = 0, fill = 0 }, meths.win_text_height(1001, { start_row = 0, start_vcol = 0, end_row = 0, end_vcol = 0 }))
+ eq({ all = 1, fill = 0 }, meths.win_text_height(1001, { start_row = 0, start_vcol = 0, end_row = 0, end_vcol = X }))
+ eq({ all = 11, fill = 5 }, meths.win_text_height(1001, { end_row = 18 }))
+ eq({ all = 9, fill = 3 }, meths.win_text_height(1001, { start_row = 0, start_vcol = 0, end_row = 18 }))
+ eq({ all = 10, fill = 5 }, meths.win_text_height(1001, { end_row = 18, end_vcol = 0 }))
+ eq({ all = 8, fill = 3 }, meths.win_text_height(1001, { start_row = 0, start_vcol = 0, end_row = 18, end_vcol = 0 }))
+ end)
+
+ it('with wrapped lines', function()
+ local X = meths.get_vvar('maxcol')
+ local screen = Screen.new(45, 22)
+ screen:set_default_attr_ids({
+ [0] = {foreground = Screen.colors.Blue1, bold = true};
+ [1] = {foreground = Screen.colors.Brown};
+ [2] = {background = Screen.colors.Yellow};
+ })
+ screen:attach()
+ exec([[
+ set number cpoptions+=n
+ call setline(1, repeat([repeat('foobar-', 36)], 3))
+ ]])
+ local ns = meths.create_namespace('')
+ meths.buf_set_extmark(0, ns, 1, 100, { virt_text = {{('?'):rep(15), 'Search'}}, virt_text_pos = 'inline' })
+ meths.buf_set_extmark(0, ns, 2, 200, { virt_text = {{('!'):rep(75), 'Search'}}, virt_text_pos = 'inline' })
+ screen:expect{grid=[[
+ {1: 1 }^foobar-foobar-foobar-foobar-foobar-foobar|
+ -foobar-foobar-foobar-foobar-foobar-foobar-fo|
+ obar-foobar-foobar-foobar-foobar-foobar-fooba|
+ r-foobar-foobar-foobar-foobar-foobar-foobar-f|
+ oobar-foobar-foobar-foobar-foobar-foobar-foob|
+ ar-foobar-foobar-foobar-foobar- |
+ {1: 2 }foobar-foobar-foobar-foobar-foobar-foobar|
+ -foobar-foobar-foobar-foobar-foobar-foobar-fo|
+ obar-foobar-fo{2:???????????????}obar-foobar-foob|
+ ar-foobar-foobar-foobar-foobar-foobar-foobar-|
+ foobar-foobar-foobar-foobar-foobar-foobar-foo|
+ bar-foobar-foobar-foobar-foobar-foobar-foobar|
+ - |
+ {1: 3 }foobar-foobar-foobar-foobar-foobar-foobar|
+ -foobar-foobar-foobar-foobar-foobar-foobar-fo|
+ obar-foobar-foobar-foobar-foobar-foobar-fooba|
+ r-foobar-foobar-foobar-foobar-foobar-foobar-f|
+ oobar-foobar-foobar-foob{2:!!!!!!!!!!!!!!!!!!!!!}|
+ {2:!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!}|
+ {2:!!!!!!!!!}ar-foobar-foobar-foobar-foobar-fooba|
+ r-foobar-foobar- |
+ |
+ ]]}
+ screen:try_resize(45, 2)
+ screen:expect{grid=[[
+ {1: 1 }^foobar-foobar-foobar-foobar-foobar-foobar|
+ |
+ ]]}
+ eq({ all = 21, fill = 0 }, meths.win_text_height(0, {}))
+ eq({ all = 6, fill = 0 }, meths.win_text_height(0, { start_row = 0, end_row = 0 }))
+ eq({ all = 7, fill = 0 }, meths.win_text_height(0, { start_row = 1, end_row = 1 }))
+ eq({ all = 8, fill = 0 }, meths.win_text_height(0, { start_row = 2, end_row = 2 }))
+ eq({ all = 0, fill = 0 }, meths.win_text_height(0, { start_row = 1, start_vcol = 0, end_row = 1, end_vcol = 0 }))
+ eq({ all = 1, fill = 0 }, meths.win_text_height(0, { start_row = 1, start_vcol = 0, end_row = 1, end_vcol = 41 }))
+ eq({ all = 2, fill = 0 }, meths.win_text_height(0, { start_row = 1, start_vcol = 0, end_row = 1, end_vcol = 42 }))
+ eq({ all = 2, fill = 0 }, meths.win_text_height(0, { start_row = 1, start_vcol = 0, end_row = 1, end_vcol = 86 }))
+ eq({ all = 3, fill = 0 }, meths.win_text_height(0, { start_row = 1, start_vcol = 0, end_row = 1, end_vcol = 87 }))
+ eq({ all = 6, fill = 0 }, meths.win_text_height(0, { start_row = 1, start_vcol = 0, end_row = 1, end_vcol = 266 }))
+ eq({ all = 7, fill = 0 }, meths.win_text_height(0, { start_row = 1, start_vcol = 0, end_row = 1, end_vcol = 267 }))
+ eq({ all = 7, fill = 0 }, meths.win_text_height(0, { start_row = 1, start_vcol = 0, end_row = 1, end_vcol = 311 }))
+ eq({ all = 7, fill = 0 }, meths.win_text_height(0, { start_row = 1, start_vcol = 0, end_row = 1, end_vcol = 312 }))
+ eq({ all = 7, fill = 0 }, meths.win_text_height(0, { start_row = 1, start_vcol = 0, end_row = 1, end_vcol = X }))
+ eq({ all = 7, fill = 0 }, meths.win_text_height(0, { start_row = 1, start_vcol = 40, end_row = 1, end_vcol = X }))
+ eq({ all = 6, fill = 0 }, meths.win_text_height(0, { start_row = 1, start_vcol = 41, end_row = 1, end_vcol = X }))
+ eq({ all = 6, fill = 0 }, meths.win_text_height(0, { start_row = 1, start_vcol = 85, end_row = 1, end_vcol = X }))
+ eq({ all = 5, fill = 0 }, meths.win_text_height(0, { start_row = 1, start_vcol = 86, end_row = 1, end_vcol = X }))
+ eq({ all = 2, fill = 0 }, meths.win_text_height(0, { start_row = 1, start_vcol = 265, end_row = 1, end_vcol = X }))
+ eq({ all = 1, fill = 0 }, meths.win_text_height(0, { start_row = 1, start_vcol = 266, end_row = 1, end_vcol = X }))
+ eq({ all = 1, fill = 0 }, meths.win_text_height(0, { start_row = 1, start_vcol = 310, end_row = 1, end_vcol = X }))
+ eq({ all = 0, fill = 0 }, meths.win_text_height(0, { start_row = 1, start_vcol = 311, end_row = 1, end_vcol = X }))
+ eq({ all = 1, fill = 0 }, meths.win_text_height(0, { start_row = 1, start_vcol = 86, end_row = 1, end_vcol = 131 }))
+ eq({ all = 1, fill = 0 }, meths.win_text_height(0, { start_row = 1, start_vcol = 221, end_row = 1, end_vcol = 266 }))
+ eq({ all = 18, fill = 0 }, meths.win_text_height(0, { start_row = 0, start_vcol = 131 }))
+ eq({ all = 19, fill = 0 }, meths.win_text_height(0, { start_row = 0, start_vcol = 130 }))
+ eq({ all = 20, fill = 0 }, meths.win_text_height(0, { end_row = 2, end_vcol = 311 }))
+ eq({ all = 21, fill = 0 }, meths.win_text_height(0, { end_row = 2, end_vcol = 312 }))
+ eq({ all = 17, fill = 0 }, meths.win_text_height(0, { start_row = 0, start_vcol = 131, end_row = 2, end_vcol = 311 }))
+ eq({ all = 19, fill = 0 }, meths.win_text_height(0, { start_row = 0, start_vcol = 130, end_row = 2, end_vcol = 312 }))
+ eq({ all = 16, fill = 0 }, meths.win_text_height(0, { start_row = 0, start_vcol = 221 }))
+ eq({ all = 17, fill = 0 }, meths.win_text_height(0, { start_row = 0, start_vcol = 220 }))
+ eq({ all = 14, fill = 0 }, meths.win_text_height(0, { end_row = 2, end_vcol = 41 }))
+ eq({ all = 15, fill = 0 }, meths.win_text_height(0, { end_row = 2, end_vcol = 42 }))
+ eq({ all = 9, fill = 0 }, meths.win_text_height(0, { start_row = 0, start_vcol = 221, end_row = 2, end_vcol = 41 }))
+ eq({ all = 11, fill = 0 }, meths.win_text_height(0, { start_row = 0, start_vcol = 220, end_row = 2, end_vcol = 42 }))
+ end)
end)
describe('open_win', function()
@@ -591,19 +864,45 @@ describe('API/win', function()
})
eq(1, funcs.exists('g:fired'))
end)
+
+ it('disallowed in cmdwin if enter=true or buf=curbuf', function()
+ local new_buf = meths.create_buf(true, true)
+ feed('q:')
+ eq('E11: Invalid in command-line window; <CR> executes, CTRL-C quits',
+ pcall_err(meths.open_win, new_buf, true, {
+ relative='editor', row=5, col=5, width=5, height=5,
+ }))
+ eq('E11: Invalid in command-line window; <CR> executes, CTRL-C quits',
+ pcall_err(meths.open_win, 0, false, {
+ relative='editor', row=5, col=5, width=5, height=5,
+ }))
+
+ eq(new_buf, meths.win_get_buf(meths.open_win(new_buf, false, {
+ relative='editor', row=5, col=5, width=5, height=5,
+ })))
+ end)
+
+ it('aborts if buffer is invalid', function()
+ local wins_before = meths.list_wins()
+ eq('Invalid buffer id: 1337', pcall_err(meths.open_win, 1337, false, {
+ relative='editor', row=5, col=5, width=5, height=5,
+ }))
+ eq(wins_before, meths.list_wins())
+ end)
end)
describe('get_config', function()
it('includes border', function()
local b = { 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h' }
local win = meths.open_win(0, true, {
- relative='win', row=3, col=3, width=12, height=3,
- border = b,
+ relative='win', row=3, col=3, width=12, height=3,
+ border = b,
})
local cfg = meths.win_get_config(win)
eq(b, cfg.border)
end)
+
it('includes border with highlight group', function()
local b = {
{'a', 'Normal'},
@@ -616,12 +915,25 @@ describe('API/win', function()
{'h', 'PreProc'},
}
local win = meths.open_win(0, true, {
- relative='win', row=3, col=3, width=12, height=3,
- border = b,
+ relative='win', row=3, col=3, width=12, height=3,
+ border = b,
})
local cfg = meths.win_get_config(win)
eq(b, cfg.border)
end)
+
+ it('includes title and footer', function()
+ local title = { {'A', {'StatusLine', 'TabLine'}}, {'B'}, {'C', 'WinBar'} }
+ local footer = { {'A', 'WinBar'}, {'B'}, {'C', {'StatusLine', 'TabLine'}} }
+ local win = meths.open_win(0, true, {
+ relative='win', row=3, col=3, width=12, height=3,
+ border = 'single', title = title, footer = footer,
+ })
+
+ local cfg = meths.win_get_config(win)
+ eq(title, cfg.title)
+ eq(footer, cfg.footer)
+ end)
end)
end)