diff --git a/runtime/doc/lsp.txt b/runtime/doc/lsp.txt index 228bbafdd2..3d83d91233 100644 --- a/runtime/doc/lsp.txt +++ b/runtime/doc/lsp.txt @@ -909,8 +909,11 @@ formatexpr({opts}) *vim.lsp.formatexpr()* Currently only supports a single client. This can be set via `setlocal formatexpr=v:lua.vim.lsp.formatexpr()` or (more typically) in - `on_attach` via - `vim.bo[bufnr].formatexpr = 'v:lua.vim.lsp.formatexpr(#{timeout_ms:250})'`. + `on_attach` via: >lua + vim.bo[bufnr].formatexpr = function() + return vim.lsp.formatexpr({ timeout_ms = 250 }) + end +< Parameters: ~ • {opts} (`table?`) A table with the following fields: diff --git a/runtime/doc/lua.txt b/runtime/doc/lua.txt index dad3d92238..759277cd49 100644 --- a/runtime/doc/lua.txt +++ b/runtime/doc/lua.txt @@ -1293,6 +1293,14 @@ window-scoped options. Note that this must NOT be confused with |local-options| and |:setlocal|. There is also |vim.go| that only accesses the global value of a |global-local| option, see |:setglobal|. +Unlike |nvim_set_option_value()|, |vim.o|, |vim.go|, |vim.bo| +and |vim.wo| can be assigned function values. + +Example: >lua + vim.bo.tagfunc = vim.lsp.tagfunc + vim.wo[1000].foldexpr = vim.treesitter.foldexpr +< + *vim.opt_local* *vim.opt_global* diff --git a/runtime/lua/vim/_options.lua b/runtime/lua/vim/_options.lua index 77d7054626..c4f908384f 100644 --- a/runtime/lua/vim/_options.lua +++ b/runtime/lua/vim/_options.lua @@ -134,6 +134,145 @@ local function get_options_info(name) return info end +local func_opts = { + -- Options that can be set as a function + func = { + completefunc = true, + findfunc = true, + omnifunc = true, + operatorfunc = true, + quickfixtextfunc = true, + tagfunc = true, + thesaurusfunc = true, + }, + + -- Options that can be set as an expression + expr = { + diffexpr = true, + foldexpr = true, + formatexpr = true, + includeexpr = true, + indentexpr = true, + modelineexpr = true, + patchexpr = true, + }, + + -- Options that can be set as an expression with a prefix of '%!' + pct_bang_expr = { + statuscolumn = true, + statusline = true, + tabline = true, + winbar = true, + }, + + -- Options that can be set as an ex command if prefixed with ':' + ex_cmd_expr = { + keywordprg = true, + }, +} + +--- @param v integer? +--- @return integer +local function resolve_win(v) + return assert((not v or v == 0) and api.nvim_get_current_win() or v) +end + +--- @type table +local all_func_opts = {} +for _, v in pairs(func_opts) do + for k in pairs(v) do + all_func_opts[k] = true + end +end + +--- @type table +vim._func_opts = setmetatable({}, { __mode = 'v' }) + +--- If the option name is a function option convert it to a string +--- format and store the function value. +--- @param name string +--- @param value any +--- @param info vim.api.keyset.get_option_info +--- @param opts vim.api.keyset.option +local function apply_func_opt(name, value, info, opts) + local idxs --- @type any[] + + -- Find a table keep a references to the function value for an option. + -- We store a strong reference to the function in vim.g/b/w so it can be correctly garbage + -- collected when the buffer/window is closed. However because `v:lua` does not support + -- indexing with [], we need to store a weak reference to the function in another table under a + -- single string key. + if info.scope == 'global' or info.global_local and opts.scope == 'global' then + idxs = { 'g', '_func_opts' } + elseif info.scope == 'buf' then + idxs = { 'b', vim._resolve_bufnr(opts.buf), '_func_opts' } + else + assert(info.scope == 'win') + idxs = { 'w', resolve_win(opts.win), '_func_opts' } + if opts.scope == 'local' then + idxs[#idxs + 1] = vim._resolve_bufnr(opts.buf) + end + end + + local fvalue = type(value) == 'function' and value or nil + + -- If we have a function value, ensure the table for the strong references exists + if fvalue then + local t = vim --- @type table + for _, k in ipairs(idxs) do + t[k] = t[k] or {} + t = t[k] + end + end + + --- @type table + local t = vim.tbl_get(vim, unpack(idxs)) + if t then + -- Note fvalue as nil is used to GC + t[name] = fvalue + end + + if not fvalue then + return value + end + + local vlua_key_parts = {} --- @type string[] + for _, k in ipairs(idxs) do + vlua_key_parts[#vlua_key_parts + 1] = k ~= '_func_opts' and k or nil + end + vlua_key_parts[#vlua_key_parts + 1] = name + + local vlua_key = table.concat(vlua_key_parts, '_') + + vim._func_opts[vlua_key] = fvalue + + local expr_pfx = ( + func_opts.pct_bang_expr[name] and '%!' + or func_opts.ex_cmd_expr[name] and ':call ' + or '' + ) + + local call_sfx = func_opts.func[name] and '' or '()' + + return ('%sv:lua.vim._func_opts.%s%s'):format(expr_pfx, vlua_key, call_sfx) +end + +--- @param name string +--- @param value any +--- @param opts vim.api.keyset.option +local function set_option_value(name, value, opts) + local info = api.nvim_get_option_info2(name, {}) + + -- Resolve name if it is a short name + name = info.name + + if all_func_opts[name] then + value = apply_func_opt(name, value, info, opts) + end + + api.nvim_set_option_value(name, value, opts) +end + --- Environment variables defined in the editor session. --- See |expand-env| and |:let-environment| for the Vimscript behavior. --- Invalid or unset key returns `nil`. @@ -158,6 +297,7 @@ vim.env = setmetatable({}, { end, }) +--- @param bufnr? integer local function new_buf_opt_accessor(bufnr) return setmetatable({}, { __index = function(_, k) @@ -167,8 +307,10 @@ local function new_buf_opt_accessor(bufnr) return api.nvim_get_option_value(k, { buf = bufnr or 0 }) end, + --- @param k string + --- @param v any __newindex = function(_, k, v) - return api.nvim_set_option_value(k, v, { buf = bufnr or 0 }) + return set_option_value(k, v, { buf = bufnr or 0 }) end, }) end @@ -196,7 +338,7 @@ local function new_win_opt_accessor(winid, bufnr) end, __newindex = function(_, k, v) - return api.nvim_set_option_value(k, v, { + return set_option_value(k, v, { scope = bufnr and 'local' or nil, win = winid or 0, }) @@ -227,6 +369,14 @@ end --- window-scoped options. Note that this must NOT be confused with --- |local-options| and |:setlocal|. There is also |vim.go| that only accesses the --- global value of a |global-local| option, see |:setglobal|. +--- +--- Unlike |nvim_set_option_value()|, |vim.o|, |vim.go|, |vim.bo| +--- and |vim.wo| can be assigned function values. +--- +--- Example: >lua +--- vim.bo.tagfunc = vim.lsp.tagfunc +--- vim.wo[1000].foldexpr = vim.treesitter.foldexpr +--- < --- --- Get or set |options|. Like `:set`. Invalid key is an error. @@ -245,8 +395,10 @@ vim.o = setmetatable({}, { __index = function(_, k) return api.nvim_get_option_value(k, {}) end, + --- @param k string + --- @param v any __newindex = function(_, k, v) - return api.nvim_set_option_value(k, v, {}) + return set_option_value(k, v, {}) end, }) @@ -268,8 +420,10 @@ vim.go = setmetatable({}, { __index = function(_, k) return api.nvim_get_option_value(k, { scope = 'global' }) end, + --- @param k string + --- @param v any __newindex = function(_, k, v) - return api.nvim_set_option_value(k, v, { scope = 'global' }) + return set_option_value(k, v, { scope = 'global' }) end, }) diff --git a/runtime/lua/vim/lsp.lua b/runtime/lua/vim/lsp.lua index 5a93da4298..b0fee1e230 100644 --- a/runtime/lua/vim/lsp.lua +++ b/runtime/lua/vim/lsp.lua @@ -1309,8 +1309,13 @@ end --- --- Currently only supports a single client. This can be set via --- `setlocal formatexpr=v:lua.vim.lsp.formatexpr()` or (more typically) in `on_attach` ---- via `vim.bo[bufnr].formatexpr = 'v:lua.vim.lsp.formatexpr(#{timeout_ms:250})'`. +--- via: --- +--- ```lua +--- vim.bo[bufnr].formatexpr = function() +--- return vim.lsp.formatexpr({ timeout_ms = 250 }) +--- end +--- ``` ---@param opts? vim.lsp.formatexpr.Opts function lsp.formatexpr(opts) opts = opts or {} diff --git a/test/functional/lua/option_spec.lua b/test/functional/lua/option_spec.lua new file mode 100644 index 0000000000..b09d3547f9 --- /dev/null +++ b/test/functional/lua/option_spec.lua @@ -0,0 +1,1251 @@ +local t = require('test.testutil') +local n = require('test.functional.testnvim')() +local Screen = require('test.functional.ui.screen') + +local NIL = vim.NIL +local feed = n.feed +local command = n.command +local clear = n.clear +local api = n.api +local eq = t.eq +local eval = n.eval +local exec = n.exec +local exec_lua = n.exec_lua +local fn = n.fn +local matches = t.matches +local mkdir_p = n.mkdir_p +local pcall_err = t.pcall_err +local rmdir = n.rmdir +local write_file = t.write_file + +local function eq_exec_lua(expected, f) + eq(expected, exec_lua(f)) +end + +describe('lua stdlib', function() + before_each(clear) + + describe('variables', function() + it('vim.g', function() + exec_lua(function() + vim.api.nvim_set_var('testing', 'hi') + vim.api.nvim_set_var('other', 123) + vim.api.nvim_set_var('floaty', 5120.1) + vim.api.nvim_set_var('nullvar', vim.NIL) + vim.api.nvim_set_var('to_delete', { hello = 'world' }) + end) + + eq('hi', fn.luaeval 'vim.g.testing') + eq(123, fn.luaeval 'vim.g.other') + eq(5120.1, fn.luaeval 'vim.g.floaty') + eq(NIL, fn.luaeval 'vim.g.nonexistent') + eq(NIL, fn.luaeval 'vim.g.nullvar') + -- lost over RPC, so test locally: + eq_exec_lua({ false, true }, function() + return { vim.g.nonexistent == vim.NIL, vim.g.nullvar == vim.NIL } + end) + + eq({ hello = 'world' }, fn.luaeval 'vim.g.to_delete') + exec_lua [[ + vim.g.to_delete = nil + ]] + eq(NIL, fn.luaeval 'vim.g.to_delete') + + matches([[attempt to index .* nil value]], pcall_err(exec_lua, 'return vim.g[0].testing')) + + exec_lua(function() + local counter = 0 + local function add_counter() + counter = counter + 1 + end + local function get_counter() + return counter + end + vim.g.AddCounter = add_counter + vim.g.GetCounter = get_counter + vim.g.fn = { add = add_counter, get = get_counter } + vim.g.AddParens = function(s) + return '(' .. s .. ')' + end + end) + + eq(0, eval('g:GetCounter()')) + eval('g:AddCounter()') + eq(1, eval('g:GetCounter()')) + eval('g:AddCounter()') + eq(2, eval('g:GetCounter()')) + exec_lua([[vim.g.AddCounter()]]) + eq(3, exec_lua([[return vim.g.GetCounter()]])) + exec_lua([[vim.api.nvim_get_var('AddCounter')()]]) + eq(4, exec_lua([[return vim.api.nvim_get_var('GetCounter')()]])) + exec_lua([[vim.g.fn.add()]]) + eq(5, exec_lua([[return vim.g.fn.get()]])) + exec_lua([[vim.api.nvim_get_var('fn').add()]]) + eq(6, exec_lua([[return vim.api.nvim_get_var('fn').get()]])) + eq('((foo))', eval([['foo'->AddParens()->AddParens()]])) + + exec_lua(function() + local counter = 0 + local function add_counter() + counter = counter + 1 + end + local function get_counter() + return counter + end + vim.api.nvim_set_var('AddCounter', add_counter) + vim.api.nvim_set_var('GetCounter', get_counter) + vim.api.nvim_set_var('fn', { add = add_counter, get = get_counter }) + vim.api.nvim_set_var('AddParens', function(s) + return '(' .. s .. ')' + end) + end) + + eq(0, eval('g:GetCounter()')) + eval('g:AddCounter()') + eq(1, eval('g:GetCounter()')) + eval('g:AddCounter()') + eq(2, eval('g:GetCounter()')) + exec_lua([[vim.g.AddCounter()]]) + eq(3, exec_lua([[return vim.g.GetCounter()]])) + exec_lua([[vim.api.nvim_get_var('AddCounter')()]]) + eq(4, exec_lua([[return vim.api.nvim_get_var('GetCounter')()]])) + exec_lua([[vim.g.fn.add()]]) + eq(5, exec_lua([[return vim.g.fn.get()]])) + exec_lua([[vim.api.nvim_get_var('fn').add()]]) + eq(6, exec_lua([[return vim.api.nvim_get_var('fn').get()]])) + eq('((foo))', eval([['foo'->AddParens()->AddParens()]])) + + exec([[ + function Test() + endfunction + function s:Test() + endfunction + let g:Unknown_func = function('Test') + let g:Unknown_script_func = function('s:Test') + ]]) + eq(NIL, exec_lua([[return vim.g.Unknown_func]])) + eq(NIL, exec_lua([[return vim.g.Unknown_script_func]])) + + -- Check if autoload works properly + local pathsep = n.get_pathsep() + local xconfig = 'Xhome' .. pathsep .. 'Xconfig' + local xdata = 'Xhome' .. pathsep .. 'Xdata' + local autoload_folder = table.concat({ xconfig, 'nvim', 'autoload' }, pathsep) + local autoload_file = table.concat({ autoload_folder, 'testload.vim' }, pathsep) + mkdir_p(autoload_folder) + write_file(autoload_file, [[let testload#value = 2]]) + + clear { args_rm = { '-u' }, env = { XDG_CONFIG_HOME = xconfig, XDG_DATA_HOME = xdata } } + + eq(2, exec_lua("return vim.g['testload#value']")) + rmdir('Xhome') + end) + + it('vim.b', function() + exec_lua(function() + vim.api.nvim_buf_set_var(0, 'testing', 'hi') + vim.api.nvim_buf_set_var(0, 'other', 123) + vim.api.nvim_buf_set_var(0, 'floaty', 5120.1) + vim.api.nvim_buf_set_var(0, 'nullvar', vim.NIL) + vim.api.nvim_buf_set_var(0, 'to_delete', { hello = 'world' }) + _G.BUF = vim.api.nvim_create_buf(false, true) + vim.api.nvim_buf_set_var(_G.BUF, 'testing', 'bye') + end) + + eq('hi', fn.luaeval 'vim.b.testing') + eq('bye', fn.luaeval 'vim.b[BUF].testing') + eq(123, fn.luaeval 'vim.b.other') + eq(5120.1, fn.luaeval 'vim.b.floaty') + eq(NIL, fn.luaeval 'vim.b.nonexistent') + eq(NIL, fn.luaeval 'vim.b[BUF].nonexistent') + eq(NIL, fn.luaeval 'vim.b.nullvar') + -- lost over RPC, so test locally: + eq_exec_lua({ false, true }, function() + return { vim.b.nonexistent == vim.NIL, vim.b.nullvar == vim.NIL } + end) + + matches( + [[attempt to index .* nil value]], + pcall_err(exec_lua, 'return vim.b[BUF][0].testing') + ) + + eq({ hello = 'world' }, fn.luaeval 'vim.b.to_delete') + exec_lua [[ + vim.b.to_delete = nil + ]] + eq(NIL, fn.luaeval 'vim.b.to_delete') + + exec_lua(function() + local counter = 0 + local function add_counter() + counter = counter + 1 + end + local function get_counter() + return counter + end + vim.b.AddCounter = add_counter + vim.b.GetCounter = get_counter + vim.b.fn = { add = add_counter, get = get_counter } + vim.b.AddParens = function(s) + return '(' .. s .. ')' + end + end) + + eq(0, eval('b:GetCounter()')) + eval('b:AddCounter()') + eq(1, eval('b:GetCounter()')) + eval('b:AddCounter()') + eq(2, eval('b:GetCounter()')) + exec_lua([[vim.b.AddCounter()]]) + eq(3, exec_lua([[return vim.b.GetCounter()]])) + exec_lua([[vim.api.nvim_buf_get_var(0, 'AddCounter')()]]) + eq(4, exec_lua([[return vim.api.nvim_buf_get_var(0, 'GetCounter')()]])) + exec_lua([[vim.b.fn.add()]]) + eq(5, exec_lua([[return vim.b.fn.get()]])) + exec_lua([[vim.api.nvim_buf_get_var(0, 'fn').add()]]) + eq(6, exec_lua([[return vim.api.nvim_buf_get_var(0, 'fn').get()]])) + eq('((foo))', eval([['foo'->b:AddParens()->b:AddParens()]])) + + exec_lua(function() + local counter = 0 + local function add_counter() + counter = counter + 1 + end + local function get_counter() + return counter + end + vim.api.nvim_buf_set_var(0, 'AddCounter', add_counter) + vim.api.nvim_buf_set_var(0, 'GetCounter', get_counter) + vim.api.nvim_buf_set_var(0, 'fn', { add = add_counter, get = get_counter }) + vim.api.nvim_buf_set_var(0, 'AddParens', function(s) + return '(' .. s .. ')' + end) + end) + + eq(0, eval('b:GetCounter()')) + eval('b:AddCounter()') + eq(1, eval('b:GetCounter()')) + eval('b:AddCounter()') + eq(2, eval('b:GetCounter()')) + exec_lua([[vim.b.AddCounter()]]) + eq(3, exec_lua([[return vim.b.GetCounter()]])) + exec_lua([[vim.api.nvim_buf_get_var(0, 'AddCounter')()]]) + eq(4, exec_lua([[return vim.api.nvim_buf_get_var(0, 'GetCounter')()]])) + exec_lua([[vim.b.fn.add()]]) + eq(5, exec_lua([[return vim.b.fn.get()]])) + exec_lua([[vim.api.nvim_buf_get_var(0, 'fn').add()]]) + eq(6, exec_lua([[return vim.api.nvim_buf_get_var(0, 'fn').get()]])) + eq('((foo))', eval([['foo'->b:AddParens()->b:AddParens()]])) + + exec([[ + function Test() + endfunction + function s:Test() + endfunction + let b:Unknown_func = function('Test') + let b:Unknown_script_func = function('s:Test') + ]]) + eq(NIL, exec_lua([[return vim.b.Unknown_func]])) + eq(NIL, exec_lua([[return vim.b.Unknown_script_func]])) + + exec_lua [[ + vim.cmd "vnew" + ]] + + eq(NIL, fn.luaeval 'vim.b.testing') + eq(NIL, fn.luaeval 'vim.b.other') + eq(NIL, fn.luaeval 'vim.b.nonexistent') + end) + + it('vim.w', function() + exec_lua [[ + vim.api.nvim_win_set_var(0, "testing", "hi") + vim.api.nvim_win_set_var(0, "other", 123) + vim.api.nvim_win_set_var(0, "to_delete", {hello="world"}) + BUF = vim.api.nvim_create_buf(false, true) + WIN = vim.api.nvim_open_win(BUF, false, { + width=10, height=10, + relative='win', row=0, col=0 + }) + vim.api.nvim_win_set_var(WIN, "testing", "bye") + ]] + + eq('hi', fn.luaeval 'vim.w.testing') + eq('bye', fn.luaeval 'vim.w[WIN].testing') + eq(123, fn.luaeval 'vim.w.other') + eq(NIL, fn.luaeval 'vim.w.nonexistent') + eq(NIL, fn.luaeval 'vim.w[WIN].nonexistent') + + matches( + [[attempt to index .* nil value]], + pcall_err(exec_lua, 'return vim.w[WIN][0].testing') + ) + + eq({ hello = 'world' }, fn.luaeval 'vim.w.to_delete') + exec_lua [[ + vim.w.to_delete = nil + ]] + eq(NIL, fn.luaeval 'vim.w.to_delete') + + exec_lua [[ + local counter = 0 + local function add_counter() counter = counter + 1 end + local function get_counter() return counter end + vim.w.AddCounter = add_counter + vim.w.GetCounter = get_counter + vim.w.fn = {add = add_counter, get = get_counter} + vim.w.AddParens = function(s) return '(' .. s .. ')' end + ]] + + eq(0, eval('w:GetCounter()')) + eval('w:AddCounter()') + eq(1, eval('w:GetCounter()')) + eval('w:AddCounter()') + eq(2, eval('w:GetCounter()')) + exec_lua([[vim.w.AddCounter()]]) + eq(3, exec_lua([[return vim.w.GetCounter()]])) + exec_lua([[vim.api.nvim_win_get_var(0, 'AddCounter')()]]) + eq(4, exec_lua([[return vim.api.nvim_win_get_var(0, 'GetCounter')()]])) + exec_lua([[vim.w.fn.add()]]) + eq(5, exec_lua([[return vim.w.fn.get()]])) + exec_lua([[vim.api.nvim_win_get_var(0, 'fn').add()]]) + eq(6, exec_lua([[return vim.api.nvim_win_get_var(0, 'fn').get()]])) + eq('((foo))', eval([['foo'->w:AddParens()->w:AddParens()]])) + + exec_lua [[ + local counter = 0 + local function add_counter() counter = counter + 1 end + local function get_counter() return counter end + vim.api.nvim_win_set_var(0, 'AddCounter', add_counter) + vim.api.nvim_win_set_var(0, 'GetCounter', get_counter) + vim.api.nvim_win_set_var(0, 'fn', {add = add_counter, get = get_counter}) + vim.api.nvim_win_set_var(0, 'AddParens', function(s) return '(' .. s .. ')' end) + ]] + + eq(0, eval('w:GetCounter()')) + eval('w:AddCounter()') + eq(1, eval('w:GetCounter()')) + eval('w:AddCounter()') + eq(2, eval('w:GetCounter()')) + exec_lua([[vim.w.AddCounter()]]) + eq(3, exec_lua([[return vim.w.GetCounter()]])) + exec_lua([[vim.api.nvim_win_get_var(0, 'AddCounter')()]]) + eq(4, exec_lua([[return vim.api.nvim_win_get_var(0, 'GetCounter')()]])) + exec_lua([[vim.w.fn.add()]]) + eq(5, exec_lua([[return vim.w.fn.get()]])) + exec_lua([[vim.api.nvim_win_get_var(0, 'fn').add()]]) + eq(6, exec_lua([[return vim.api.nvim_win_get_var(0, 'fn').get()]])) + eq('((foo))', eval([['foo'->w:AddParens()->w:AddParens()]])) + + exec([[ + function Test() + endfunction + function s:Test() + endfunction + let w:Unknown_func = function('Test') + let w:Unknown_script_func = function('s:Test') + ]]) + eq(NIL, exec_lua([[return vim.w.Unknown_func]])) + eq(NIL, exec_lua([[return vim.w.Unknown_script_func]])) + + exec_lua [[ + vim.cmd "vnew" + ]] + + eq(NIL, fn.luaeval 'vim.w.testing') + eq(NIL, fn.luaeval 'vim.w.other') + eq(NIL, fn.luaeval 'vim.w.nonexistent') + end) + + it('vim.t', function() + exec_lua [[ + vim.api.nvim_tabpage_set_var(0, "testing", "hi") + vim.api.nvim_tabpage_set_var(0, "other", 123) + vim.api.nvim_tabpage_set_var(0, "to_delete", {hello="world"}) + ]] + + eq('hi', fn.luaeval 'vim.t.testing') + eq(123, fn.luaeval 'vim.t.other') + eq(NIL, fn.luaeval 'vim.t.nonexistent') + eq('hi', fn.luaeval 'vim.t[0].testing') + eq(123, fn.luaeval 'vim.t[0].other') + eq(NIL, fn.luaeval 'vim.t[0].nonexistent') + + matches([[attempt to index .* nil value]], pcall_err(exec_lua, 'return vim.t[0][0].testing')) + + eq({ hello = 'world' }, fn.luaeval 'vim.t.to_delete') + exec_lua [[ + vim.t.to_delete = nil + ]] + eq(NIL, fn.luaeval 'vim.t.to_delete') + + exec_lua [[ + local counter = 0 + local function add_counter() counter = counter + 1 end + local function get_counter() return counter end + vim.t.AddCounter = add_counter + vim.t.GetCounter = get_counter + vim.t.fn = {add = add_counter, get = get_counter} + vim.t.AddParens = function(s) return '(' .. s .. ')' end + ]] + + eq(0, eval('t:GetCounter()')) + eval('t:AddCounter()') + eq(1, eval('t:GetCounter()')) + eval('t:AddCounter()') + eq(2, eval('t:GetCounter()')) + exec_lua([[vim.t.AddCounter()]]) + eq(3, exec_lua([[return vim.t.GetCounter()]])) + exec_lua([[vim.api.nvim_tabpage_get_var(0, 'AddCounter')()]]) + eq(4, exec_lua([[return vim.api.nvim_tabpage_get_var(0, 'GetCounter')()]])) + exec_lua([[vim.t.fn.add()]]) + eq(5, exec_lua([[return vim.t.fn.get()]])) + exec_lua([[vim.api.nvim_tabpage_get_var(0, 'fn').add()]]) + eq(6, exec_lua([[return vim.api.nvim_tabpage_get_var(0, 'fn').get()]])) + eq('((foo))', eval([['foo'->t:AddParens()->t:AddParens()]])) + + exec_lua [[ + local counter = 0 + local function add_counter() counter = counter + 1 end + local function get_counter() return counter end + vim.api.nvim_tabpage_set_var(0, 'AddCounter', add_counter) + vim.api.nvim_tabpage_set_var(0, 'GetCounter', get_counter) + vim.api.nvim_tabpage_set_var(0, 'fn', {add = add_counter, get = get_counter}) + vim.api.nvim_tabpage_set_var(0, 'AddParens', function(s) return '(' .. s .. ')' end) + ]] + + eq(0, eval('t:GetCounter()')) + eval('t:AddCounter()') + eq(1, eval('t:GetCounter()')) + eval('t:AddCounter()') + eq(2, eval('t:GetCounter()')) + exec_lua([[vim.t.AddCounter()]]) + eq(3, exec_lua([[return vim.t.GetCounter()]])) + exec_lua([[vim.api.nvim_tabpage_get_var(0, 'AddCounter')()]]) + eq(4, exec_lua([[return vim.api.nvim_tabpage_get_var(0, 'GetCounter')()]])) + exec_lua([[vim.t.fn.add()]]) + eq(5, exec_lua([[return vim.t.fn.get()]])) + exec_lua([[vim.api.nvim_tabpage_get_var(0, 'fn').add()]]) + eq(6, exec_lua([[return vim.api.nvim_tabpage_get_var(0, 'fn').get()]])) + eq('((foo))', eval([['foo'->t:AddParens()->t:AddParens()]])) + + exec_lua [[ + vim.cmd "tabnew" + ]] + + eq(NIL, fn.luaeval 'vim.t.testing') + eq(NIL, fn.luaeval 'vim.t.other') + eq(NIL, fn.luaeval 'vim.t.nonexistent') + end) + + it('vim.env', function() + exec_lua([[vim.fn.setenv('A', 123)]]) + eq('123', fn.luaeval('vim.env.A')) + exec_lua([[vim.env.A = 456]]) + eq('456', fn.luaeval('vim.env.A')) + exec_lua([[vim.env.A = nil]]) + eq(NIL, fn.luaeval('vim.env.A')) + + eq(true, fn.luaeval('vim.env.B == nil')) + + command([[let $HOME = 'foo']]) + eq('foo', fn.expand('~')) + eq('foo', fn.luaeval('vim.env.HOME')) + exec_lua([[vim.env.HOME = nil]]) + eq('foo', fn.expand('~')) + exec_lua([[vim.env.HOME = 'bar']]) + eq('bar', fn.expand('~')) + eq('bar', fn.luaeval('vim.env.HOME')) + end) + + it('vim.v', function() + eq(fn.luaeval "vim.api.nvim_get_vvar('progpath')", fn.luaeval 'vim.v.progpath') + eq(false, fn.luaeval "vim.v['false']") + eq(NIL, fn.luaeval 'vim.v.null') + matches([[attempt to index .* nil value]], pcall_err(exec_lua, 'return vim.v[0].progpath')) + eq('Key is read-only: count', pcall_err(exec_lua, [[vim.v.count = 42]])) + eq('Dict is locked', pcall_err(exec_lua, [[vim.v.nosuchvar = 42]])) + eq('Key is fixed: errmsg', pcall_err(exec_lua, [[vim.v.errmsg = nil]])) + exec_lua([[vim.v.errmsg = 'set by Lua']]) + eq('set by Lua', eval('v:errmsg')) + exec_lua([[vim.v.errmsg = 42]]) + eq('42', eval('v:errmsg')) + exec_lua([[vim.v.oldfiles = { 'one', 'two' }]]) + eq({ 'one', 'two' }, eval('v:oldfiles')) + exec_lua([[vim.v.oldfiles = {}]]) + eq({}, eval('v:oldfiles')) + eq( + 'Setting v:oldfiles to value with wrong type', + pcall_err(exec_lua, [[vim.v.oldfiles = 'a']]) + ) + eq({}, eval('v:oldfiles')) + + feed('i foo foo foo0/foo') + eq({ 1, 1 }, api.nvim_win_get_cursor(0)) + eq(1, eval('v:searchforward')) + feed('n') + eq({ 1, 5 }, api.nvim_win_get_cursor(0)) + exec_lua([[vim.v.searchforward = 0]]) + eq(0, eval('v:searchforward')) + feed('n') + eq({ 1, 1 }, api.nvim_win_get_cursor(0)) + exec_lua([[vim.v.searchforward = 1]]) + eq(1, eval('v:searchforward')) + feed('n') + eq({ 1, 5 }, api.nvim_win_get_cursor(0)) + + local screen = Screen.new(60, 3) + eq(1, eval('v:hlsearch')) + screen:expect { + grid = [[ + {10:foo} {10:^foo} {10:foo} | + {1:~ }| + | + ]], + } + exec_lua([[vim.v.hlsearch = 0]]) + eq(0, eval('v:hlsearch')) + screen:expect { + grid = [[ + foo ^foo foo | + {1:~ }| + | + ]], + } + exec_lua([[vim.v.hlsearch = 1]]) + eq(1, eval('v:hlsearch')) + screen:expect { + grid = [[ + {10:foo} {10:^foo} {10:foo} | + {1:~ }| + | + ]], + } + end) + end) + + describe('options', function() + describe('vim.bo', function() + it('can get and set options', function() + eq('', fn.luaeval 'vim.bo.filetype') + exec_lua(function() + _G.BUF = vim.api.nvim_create_buf(false, true) + vim.api.nvim_set_option_value('filetype', 'markdown', {}) + vim.api.nvim_set_option_value('modifiable', false, { buf = _G.BUF }) + end) + eq(false, fn.luaeval 'vim.bo.modified') + eq('markdown', fn.luaeval 'vim.bo.filetype') + eq(false, fn.luaeval 'vim.bo[BUF].modifiable') + exec_lua(function() + vim.bo.filetype = '' + vim.bo[_G.BUF].modifiable = true + end) + eq('', fn.luaeval 'vim.bo.filetype') + eq(true, fn.luaeval 'vim.bo[BUF].modifiable') + end) + + it('errors', function() + matches("Unknown option 'nosuchopt'$", pcall_err(exec_lua, 'return vim.bo.nosuchopt')) + matches('Expected Lua string$', pcall_err(exec_lua, 'return vim.bo[0][0].autoread')) + matches('Invalid buffer id: %-1$', pcall_err(exec_lua, 'return vim.bo[-1].filetype')) + end) + + it('can set function values', function() + eq_exec_lua('v:lua.vim._func_opts.b_1_tagfunc', function() + vim.bo.tagfunc = function() end + return vim.bo.tagfunc + end) + end) + end) + + describe('vim.wo', function() + it('can get and set options', function() + exec_lua(function() + vim.api.nvim_set_option_value('cole', 2, {}) + vim.cmd 'split' + vim.api.nvim_set_option_value('cole', 2, {}) + end) + eq(2, fn.luaeval 'vim.wo.cole') + exec_lua(function() + vim.wo.conceallevel = 0 + end) + eq(0, fn.luaeval 'vim.wo.cole') + eq(0, fn.luaeval 'vim.wo[0].cole') + eq(0, fn.luaeval 'vim.wo[1001].cole') + matches("Unknown option 'notanopt'$", pcall_err(exec_lua, 'return vim.wo.notanopt')) + matches('Invalid window id: %-1$', pcall_err(exec_lua, 'return vim.wo[-1].list')) + eq(2, fn.luaeval 'vim.wo[1000].cole') + exec_lua(function() + vim.wo[1000].cole = 0 + end) + eq(0, fn.luaeval 'vim.wo[1000].cole') + + -- Can handle global-local values + exec_lua [[vim.o.scrolloff = 100]] + exec_lua [[vim.wo.scrolloff = 200]] + eq(200, fn.luaeval 'vim.wo.scrolloff') + exec_lua [[vim.wo.scrolloff = -1]] + eq(100, fn.luaeval 'vim.wo.scrolloff') + exec_lua(function() + vim.wo[0][0].scrolloff = 200 + vim.cmd 'enew' + end) + eq(100, fn.luaeval 'vim.wo.scrolloff') + end) + + it('errors', function() + matches('only bufnr=0 is supported', pcall_err(exec_lua, 'vim.wo[0][10].signcolumn = "no"')) + matches( + 'only bufnr=0 is supported', + pcall_err(exec_lua, 'local a = vim.wo[0][10].signcolumn') + ) + end) + + it('can set function values', function() + eq_exec_lua({ '%!v:lua.vim._func_opts.w_1000_statusline()', 'HELLO' }, function() + vim.wo.statusline = function() + return 'HELLO' + end + return { vim.wo.statusline, vim.api.nvim_eval_statusline(vim.wo.statusline, {}).str } + end) + + eq_exec_lua('v:lua.vim._func_opts.w_1000_1_foldexpr()', function() + vim.wo[0][0].foldexpr = function() + return 'HELLO' + end + return vim.wo[0].foldexpr + end) + end) + end) + + describe('vim.opt', function() + -- TODO: We still need to write some tests for optlocal, opt and then getting the options + -- Probably could also do some stuff with getting things from viml side as well to confirm behavior is the same. + + it('allows setting number values', function() + local scrolloff = exec_lua [[ + vim.opt.scrolloff = 10 + return vim.o.scrolloff + ]] + eq(10, scrolloff) + end) + + pending('handles STUPID window things', function() + eq_exec_lua({}, function() + return { + vim.api.nvim_get_option_value('scrolloff', { scope = 'global' }), + vim.api.nvim_get_option_value('scrolloff', { win = 0 }), + } + end) + end) + + it('allows setting tables', function() + eq_exec_lua('hello,world', function() + vim.opt.wildignore = { 'hello', 'world' } + return vim.o.wildignore + end) + end) + + it('allows setting tables with shortnames', function() + eq_exec_lua('hello,world', function() + vim.opt.wig = { 'hello', 'world' } + return vim.o.wildignore + end) + end) + + it('errors when you attempt to set string values to numeric options', function() + eq_exec_lua(false, function() + return ({ + pcall(function() + vim.opt.textwidth = 'hello world' + end), + })[1] + end) + end) + + it('errors when you attempt to setlocal a global value', function() + eq_exec_lua(false, function() + return pcall(function() + vim.opt_local.clipboard = 'hello' + end) + end) + end) + + it('allows you to set boolean values', function() + eq_exec_lua({ true, false, true }, function() + local results = {} + + vim.opt.autoindent = true + table.insert(results, vim.bo.autoindent) + + vim.opt.autoindent = false + table.insert(results, vim.bo.autoindent) + + vim.opt.autoindent = not vim.opt.autoindent:get() + table.insert(results, vim.bo.autoindent) + + return results + end) + end) + + it('changes current buffer values and defaults for global local values', function() + local result = exec_lua(function() + local result = {} + + vim.opt.makeprg = 'global-local' + table.insert(result, vim.go.makeprg) + table.insert(result, vim.api.nvim_get_option_value('makeprg', { buf = 0 })) + + vim.opt_local.mp = 'only-local' + table.insert(result, vim.go.makeprg) + table.insert(result, vim.api.nvim_get_option_value('makeprg', { buf = 0 })) + + vim.opt_global.makeprg = 'only-global' + table.insert(result, vim.go.makeprg) + table.insert(result, vim.api.nvim_get_option_value('makeprg', { buf = 0 })) + + vim.opt.makeprg = 'global-local' + table.insert(result, vim.go.makeprg) + table.insert(result, vim.api.nvim_get_option_value('makeprg', { buf = 0 })) + return result + end) + + -- Set -> global & local + eq('global-local', result[1]) + eq('', result[2]) + + -- Setlocal -> only local + eq('global-local', result[3]) + eq('only-local', result[4]) + + -- Setglobal -> only global + eq('only-global', result[5]) + eq('only-local', result[6]) + + -- Set -> sets global value and resets local value + eq('global-local', result[7]) + eq('', result[8]) + end) + + it('allows you to retrieve window opts even if they have not been set', function() + eq_exec_lua({ false, false, true, true }, function() + local result = {} + table.insert(result, vim.opt.number:get()) + table.insert(result, vim.opt_local.number:get()) + + vim.opt_local.number = true + table.insert(result, vim.opt.number:get()) + table.insert(result, vim.opt_local.number:get()) + + return result + end) + end) + + it('allows all sorts of string manipulation', function() + eq_exec_lua({ 'hello', 'hello world', 'start hello world' }, function() + local results = {} + + vim.opt.makeprg = 'hello' + table.insert(results, vim.o.makeprg) + + vim.opt.makeprg = vim.opt.makeprg + ' world' + table.insert(results, vim.o.makeprg) + + vim.opt.makeprg = vim.opt.makeprg ^ 'start ' + table.insert(results, vim.o.makeprg) + + return results + end) + end) + + describe('option:get()', function() + it('works for boolean values', function() + eq_exec_lua(false, function() + vim.opt.number = false + return vim.opt.number:get() + end) + end) + + it('works for number values', function() + eq_exec_lua(10, function() + vim.opt.tabstop = 10 + return vim.opt.tabstop:get() + end) + end) + + it('works for string values', function() + eq_exec_lua('hello world', function() + vim.opt.makeprg = 'hello world' + return vim.opt.makeprg:get() + end) + end) + + it('works for set type flaglists', function() + local formatoptions = exec_lua(function() + vim.opt.formatoptions = 'tcro' + return vim.opt.formatoptions:get() + end) + + eq(true, formatoptions.t) + eq(true, not formatoptions.q) + end) + + it('works for set type flaglists', function() + local formatoptions = exec_lua(function() + vim.opt.formatoptions = { t = true, c = true, r = true, o = true } + return vim.opt.formatoptions:get() + end) + + eq(true, formatoptions.t) + eq(true, not formatoptions.q) + end) + + it('works for array list type options', function() + local wildignore = exec_lua(function() + vim.opt.wildignore = '*.c,*.o,__pycache__' + return vim.opt.wildignore:get() + end) + + eq(3, #wildignore) + eq('*.c', wildignore[1]) + end) + + it('works for options that are both commalist and flaglist', function() + eq_exec_lua({ b = true, s = true }, function() + vim.opt.whichwrap = 'b,s' + return vim.opt.whichwrap:get() + end) + + eq_exec_lua({ b = true, h = true }, function() + vim.opt.whichwrap = { b = true, s = false, h = true } + return vim.opt.whichwrap:get() + end) + end) + + it('works for key-value pair options', function() + eq_exec_lua({ tab = '> ', space = '_' }, function() + vim.opt.listchars = 'tab:> ,space:_' + return vim.opt.listchars:get() + end) + end) + + it('allows you to add numeric options', function() + eq_exec_lua(16, function() + vim.opt.tabstop = 12 + vim.opt.tabstop = vim.opt.tabstop + 4 + return vim.bo.tabstop + end) + end) + + it('allows you to subtract numeric options', function() + eq_exec_lua(2, function() + vim.opt.tabstop = 4 + vim.opt.tabstop = vim.opt.tabstop - 2 + return vim.bo.tabstop + end) + end) + end) + + describe('key:value style options', function() + it('handles dict style', function() + eq_exec_lua('eol:~,space:.', function() + vim.opt.listchars = { eol = '~', space = '.' } + return vim.o.listchars + end) + end) + + it('allows adding dict style', function() + eq_exec_lua('eol:~,space:-', function() + vim.opt.listchars = { eol = '~', space = '.' } + vim.opt.listchars = vim.opt.listchars + { space = '-' } + return vim.o.listchars + end) + end) + + it('allows adding dict style', function() + eq_exec_lua('eol:~,space:_', function() + vim.opt.listchars = { eol = '~', space = '.' } + vim.opt.listchars = vim.opt.listchars + { space = '-' } + { space = '_' } + return vim.o.listchars + end) + end) + + it('allows completely new keys', function() + eq_exec_lua('eol:~,space:.,tab:>>>', function() + vim.opt.listchars = { eol = '~', space = '.' } + vim.opt.listchars = vim.opt.listchars + { tab = '>>>' } + return vim.o.listchars + end) + end) + + it('allows subtracting dict style', function() + eq_exec_lua('eol:~', function() + vim.opt.listchars = { eol = '~', space = '.' } + vim.opt.listchars = vim.opt.listchars - 'space' + return vim.o.listchars + end) + end) + + it('allows subtracting dict style', function() + eq_exec_lua('', function() + vim.opt.listchars = { eol = '~', space = '.' } + vim.opt.listchars = vim.opt.listchars - 'space' - 'eol' + return vim.o.listchars + end) + end) + + it('allows subtracting dict style multiple times', function() + eq_exec_lua('eol:~', function() + vim.opt.listchars = { eol = '~', space = '.' } + vim.opt.listchars = vim.opt.listchars - 'space' - 'space' + return vim.o.listchars + end) + end) + + it('allows adding a key:value string to a listchars', function() + eq_exec_lua('eol:~,space:.,tab:>~', function() + vim.opt.listchars = { eol = '~', space = '.' } + vim.opt.listchars = vim.opt.listchars + 'tab:>~' + return vim.o.listchars + end) + end) + + it('allows prepending a key:value string to a listchars', function() + eq_exec_lua('eol:~,space:.,tab:>~', function() + vim.opt.listchars = { eol = '~', space = '.' } + vim.opt.listchars = vim.opt.listchars ^ 'tab:>~' + return vim.o.listchars + end) + end) + end) + + it('automatically sets when calling remove', function() + eq_exec_lua('foo,baz', function() + vim.opt.wildignore = 'foo,bar,baz' + vim.opt.wildignore:remove('bar') + return vim.o.wildignore + end) + end) + + it('automatically sets when calling remove with a table', function() + eq_exec_lua('foo', function() + vim.opt.wildignore = 'foo,bar,baz' + vim.opt.wildignore:remove { 'bar', 'baz' } + return vim.o.wildignore + end) + end) + + it('automatically sets when calling append', function() + eq_exec_lua('foo,bar,baz,bing', function() + vim.opt.wildignore = 'foo,bar,baz' + vim.opt.wildignore:append('bing') + return vim.o.wildignore + end) + end) + + it('automatically sets when calling append with a table', function() + eq_exec_lua('foo,bar,baz,bing,zap', function() + vim.opt.wildignore = 'foo,bar,baz' + vim.opt.wildignore:append { 'bing', 'zap' } + return vim.o.wildignore + end) + end) + + it('allows adding tables', function() + eq_exec_lua('foo', function() + vim.opt.wildignore = 'foo' + return vim.o.wildignore + end) + + eq_exec_lua('foo,bar,baz', function() + vim.opt.wildignore = vim.opt.wildignore + { 'bar', 'baz' } + return vim.o.wildignore + end) + end) + + it('handles adding duplicates', function() + eq_exec_lua('foo', function() + vim.opt.wildignore = 'foo' + return vim.o.wildignore + end) + + eq_exec_lua('foo,bar,baz', function() + vim.opt.wildignore = vim.opt.wildignore + { 'bar', 'baz' } + return vim.o.wildignore + end) + + eq_exec_lua('foo,bar,baz', function() + vim.opt.wildignore = vim.opt.wildignore + { 'bar', 'baz' } + return vim.o.wildignore + end) + end) + + it('allows adding multiple times', function() + eq_exec_lua('foo,bar,baz', function() + vim.opt.wildignore = 'foo' + vim.opt.wildignore = vim.opt.wildignore + 'bar' + 'baz' + return vim.o.wildignore + end) + end) + + it('removes values when you use minus', function() + eq_exec_lua('foo', function() + vim.opt.wildignore = 'foo' + return vim.o.wildignore + end) + + eq_exec_lua('foo,bar,baz', function() + vim.opt.wildignore = vim.opt.wildignore + { 'bar', 'baz' } + return vim.o.wildignore + end) + + eq_exec_lua('foo,baz', function() + vim.opt.wildignore = vim.opt.wildignore - 'bar' + return vim.o.wildignore + end) + end) + + it('prepends values when using ^', function() + eq_exec_lua('first,foo', function() + vim.opt.wildignore = 'foo' + vim.opt.wildignore = vim.opt.wildignore ^ 'first' + return vim.o.wildignore + end) + + eq_exec_lua('super_first,first,foo', function() + vim.opt.wildignore = vim.opt.wildignore ^ 'super_first' + return vim.o.wildignore + end) + end) + + it('does not remove duplicates from wildmode: #14708', function() + eq_exec_lua('full,list,full', function() + vim.opt.wildmode = { 'full', 'list', 'full' } + return vim.o.wildmode + end) + end) + + describe('option types', function() + it('allows to set option with numeric value', function() + eq_exec_lua(4, function() + vim.opt.tabstop = 4 + return vim.bo.tabstop + end) + + matches( + "Invalid option type 'string' for 'tabstop'", + pcall_err(exec_lua, [[vim.opt.tabstop = '4']]) + ) + matches( + "Invalid option type 'boolean' for 'tabstop'", + pcall_err(exec_lua, [[vim.opt.tabstop = true]]) + ) + matches( + "Invalid option type 'table' for 'tabstop'", + pcall_err(exec_lua, [[vim.opt.tabstop = {4, 2}]]) + ) + matches( + "Invalid option type 'function' for 'tabstop'", + pcall_err(exec_lua, [[vim.opt.tabstop = function() return 4 end]]) + ) + end) + + it('allows to set option with boolean value', function() + eq_exec_lua(true, function() + vim.opt.undofile = true + return vim.bo.undofile + end) + + matches( + "Invalid option type 'number' for 'undofile'", + pcall_err(exec_lua, [[vim.opt.undofile = 0]]) + ) + matches( + "Invalid option type 'table' for 'undofile'", + pcall_err(exec_lua, [[vim.opt.undofile = {true}]]) + ) + matches( + "Invalid option type 'string' for 'undofile'", + pcall_err(exec_lua, [[vim.opt.undofile = 'true']]) + ) + matches( + "Invalid option type 'function' for 'undofile'", + pcall_err(exec_lua, [[vim.opt.undofile = function() return true end]]) + ) + end) + + it('allows to set option with array or string value', function() + eq_exec_lua('indent,eol,start', function() + vim.opt.backspace = { 'indent', 'eol', 'start' } + return vim.go.backspace + end) + + eq_exec_lua('indent,eol,start', function() + vim.opt.backspace = 'indent,eol,start' + return vim.go.backspace + end) + + matches( + "Invalid option type 'boolean' for 'backspace'", + pcall_err(exec_lua, [[vim.opt.backspace = true]]) + ) + matches( + "Invalid option type 'number' for 'backspace'", + pcall_err(exec_lua, [[vim.opt.backspace = 2]]) + ) + matches( + "Invalid option type 'function' for 'backspace'", + pcall_err(exec_lua, [[vim.opt.backspace = function() return 'indent,eol,start' end]]) + ) + end) + + it('allows set option with map or string value', function() + eq_exec_lua('eol:~,space:.', function() + vim.opt.listchars = { eol = '~', space = '.' } + return vim.o.listchars + end) + + eq_exec_lua('eol:~,space:.,tab:>~', function() + vim.opt.listchars = 'eol:~,space:.,tab:>~' + return vim.o.listchars + end) + + matches( + "Invalid option type 'boolean' for 'listchars'", + pcall_err(exec_lua, [[vim.opt.listchars = true]]) + ) + matches( + "Invalid option type 'number' for 'listchars'", + pcall_err(exec_lua, [[vim.opt.listchars = 2]]) + ) + matches( + "Invalid option type 'function' for 'listchars'", + pcall_err( + exec_lua, + [[vim.opt.listchars = function() return "eol:~,space:.,tab:>~" end]] + ) + ) + end) + + it('allows set option with set or string value', function() + eq_exec_lua('b,s', function() + vim.opt.whichwrap = { b = true, s = 1 } + return vim.go.whichwrap + end) + + eq_exec_lua('b,s,<,>,[,]', function() + vim.opt.whichwrap = 'b,s,<,>,[,]' + return vim.go.whichwrap + end) + + matches( + "Invalid option type 'boolean' for 'whichwrap'", + pcall_err(exec_lua, [[vim.opt.whichwrap = true]]) + ) + matches( + "Invalid option type 'number' for 'whichwrap'", + pcall_err(exec_lua, [[vim.opt.whichwrap = 2]]) + ) + matches( + "Invalid option type 'function' for 'whichwrap'", + pcall_err(exec_lua, [[vim.opt.whichwrap = function() return "b,s,<,>,[,]" end]]) + ) + end) + end) + + -- isfname=a,b,c,,,d,e,f + it('can handle isfname ,,,', function() + eq_exec_lua({ { ',', 'a', 'b', 'c' }, 'a,b,,,c' }, function() + vim.opt.isfname = 'a,b,,,c' + return { vim.opt.isfname:get(), vim.go.isfname } + end) + end) + + -- isfname=a,b,c,^,,def + it('can handle isfname ,^,,', function() + eq_exec_lua({ { '^,', 'a', 'b', 'c' }, 'a,b,^,,c' }, function() + vim.opt.isfname = 'a,b,^,,c' + return { vim.opt.isfname:get(), vim.go.isfname } + end) + end) + + describe('https://github.com/neovim/neovim/issues/14828', function() + it('gives empty list when item is empty:array', function() + eq_exec_lua({}, function() + vim.cmd('set wildignore=') + return vim.opt.wildignore:get() + end) + + eq_exec_lua({}, function() + vim.opt.wildignore = {} + return vim.opt.wildignore:get() + end) + end) + + it('gives empty list when item is empty:set', function() + eq_exec_lua({}, function() + vim.cmd('set formatoptions=') + return vim.opt.formatoptions:get() + end) + + eq_exec_lua({}, function() + vim.opt.formatoptions = {} + return vim.opt.formatoptions:get() + end) + end) + + it('does not append to empty item', function() + eq_exec_lua({ '*.foo', '*.bar' }, function() + vim.opt.wildignore = {} + vim.opt.wildignore:append { '*.foo', '*.bar' } + return vim.opt.wildignore:get() + end) + end) + + it('does not prepend to empty item', function() + eq_exec_lua({ '*.foo', '*.bar' }, function() + vim.opt.wildignore = {} + vim.opt.wildignore:prepend { '*.foo', '*.bar' } + return vim.opt.wildignore:get() + end) + end) + + it('append to empty set', function() + eq_exec_lua({ t = true }, function() + vim.opt.formatoptions = {} + vim.opt.formatoptions:append('t') + return vim.opt.formatoptions:get() + end) + end) + + it('prepend to empty set', function() + eq_exec_lua({ t = true }, function() + vim.opt.formatoptions = {} + vim.opt.formatoptions:prepend('t') + return vim.opt.formatoptions:get() + end) + end) + end) + end) -- vim.opt + + describe('vim.opt_local', function() + it('appends into global value when changing local option value', function() + eq_exec_lua('foo,bar,baz,qux', function() + vim.opt.tags = 'foo,bar' + vim.opt_local.tags:append('baz') + vim.opt_local.tags:append('qux') + return vim.bo.tags + end) + end) + end) + + describe('vim.opt_global', function() + it('gets current global option value', function() + eq_exec_lua({ 'yes' }, function() + vim.cmd 'setglobal signcolumn=yes' + return { vim.opt_global.signcolumn:get() } + end) + end) + end) + end) +end) diff --git a/test/functional/lua/vim_spec.lua b/test/functional/lua/vim_spec.lua index 9e75861aa0..893178b18a 100644 --- a/test/functional/lua/vim_spec.lua +++ b/test/functional/lua/vim_spec.lua @@ -23,9 +23,6 @@ local NIL = vim.NIL local retry = t.retry local next_msg = n.next_msg local remove_trace = t.remove_trace -local mkdir_p = n.mkdir_p -local rmdir = n.rmdir -local write_file = t.write_file local poke_eventloop = n.poke_eventloop local assert_alive = n.assert_alive local expect = n.expect @@ -1686,1449 +1683,6 @@ describe('lua stdlib', function() eq(false, exec_lua('return vim.is_callable({})')) end) - it('vim.g', function() - exec_lua [[ - vim.api.nvim_set_var("testing", "hi") - vim.api.nvim_set_var("other", 123) - vim.api.nvim_set_var("floaty", 5120.1) - vim.api.nvim_set_var("nullvar", vim.NIL) - vim.api.nvim_set_var("to_delete", {hello="world"}) - ]] - - eq('hi', fn.luaeval 'vim.g.testing') - eq(123, fn.luaeval 'vim.g.other') - eq(5120.1, fn.luaeval 'vim.g.floaty') - eq(NIL, fn.luaeval 'vim.g.nonexistent') - eq(NIL, fn.luaeval 'vim.g.nullvar') - -- lost over RPC, so test locally: - eq( - { false, true }, - exec_lua [[ - return {vim.g.nonexistent == vim.NIL, vim.g.nullvar == vim.NIL} - ]] - ) - - eq({ hello = 'world' }, fn.luaeval 'vim.g.to_delete') - exec_lua [[ - vim.g.to_delete = nil - ]] - eq(NIL, fn.luaeval 'vim.g.to_delete') - - matches([[attempt to index .* nil value]], pcall_err(exec_lua, 'return vim.g[0].testing')) - - exec_lua [[ - local counter = 0 - local function add_counter() counter = counter + 1 end - local function get_counter() return counter end - vim.g.AddCounter = add_counter - vim.g.GetCounter = get_counter - vim.g.fn = {add = add_counter, get = get_counter} - vim.g.AddParens = function(s) return '(' .. s .. ')' end - ]] - - eq(0, eval('g:GetCounter()')) - eval('g:AddCounter()') - eq(1, eval('g:GetCounter()')) - eval('g:AddCounter()') - eq(2, eval('g:GetCounter()')) - exec_lua([[vim.g.AddCounter()]]) - eq(3, exec_lua([[return vim.g.GetCounter()]])) - exec_lua([[vim.api.nvim_get_var('AddCounter')()]]) - eq(4, exec_lua([[return vim.api.nvim_get_var('GetCounter')()]])) - exec_lua([[vim.g.fn.add()]]) - eq(5, exec_lua([[return vim.g.fn.get()]])) - exec_lua([[vim.api.nvim_get_var('fn').add()]]) - eq(6, exec_lua([[return vim.api.nvim_get_var('fn').get()]])) - eq('((foo))', eval([['foo'->AddParens()->AddParens()]])) - - exec_lua [[ - local counter = 0 - local function add_counter() counter = counter + 1 end - local function get_counter() return counter end - vim.api.nvim_set_var('AddCounter', add_counter) - vim.api.nvim_set_var('GetCounter', get_counter) - vim.api.nvim_set_var('fn', {add = add_counter, get = get_counter}) - vim.api.nvim_set_var('AddParens', function(s) return '(' .. s .. ')' end) - ]] - - eq(0, eval('g:GetCounter()')) - eval('g:AddCounter()') - eq(1, eval('g:GetCounter()')) - eval('g:AddCounter()') - eq(2, eval('g:GetCounter()')) - exec_lua([[vim.g.AddCounter()]]) - eq(3, exec_lua([[return vim.g.GetCounter()]])) - exec_lua([[vim.api.nvim_get_var('AddCounter')()]]) - eq(4, exec_lua([[return vim.api.nvim_get_var('GetCounter')()]])) - exec_lua([[vim.g.fn.add()]]) - eq(5, exec_lua([[return vim.g.fn.get()]])) - exec_lua([[vim.api.nvim_get_var('fn').add()]]) - eq(6, exec_lua([[return vim.api.nvim_get_var('fn').get()]])) - eq('((foo))', eval([['foo'->AddParens()->AddParens()]])) - - exec([[ - function Test() - endfunction - function s:Test() - endfunction - let g:Unknown_func = function('Test') - let g:Unknown_script_func = function('s:Test') - ]]) - eq(NIL, exec_lua([[return vim.g.Unknown_func]])) - eq(NIL, exec_lua([[return vim.g.Unknown_script_func]])) - - -- Check if autoload works properly - local pathsep = n.get_pathsep() - local xconfig = 'Xhome' .. pathsep .. 'Xconfig' - local xdata = 'Xhome' .. pathsep .. 'Xdata' - local autoload_folder = table.concat({ xconfig, 'nvim', 'autoload' }, pathsep) - local autoload_file = table.concat({ autoload_folder, 'testload.vim' }, pathsep) - mkdir_p(autoload_folder) - write_file(autoload_file, [[let testload#value = 2]]) - - clear { args_rm = { '-u' }, env = { XDG_CONFIG_HOME = xconfig, XDG_DATA_HOME = xdata } } - - eq(2, exec_lua("return vim.g['testload#value']")) - rmdir('Xhome') - end) - - it('vim.b', function() - exec_lua [[ - vim.api.nvim_buf_set_var(0, "testing", "hi") - vim.api.nvim_buf_set_var(0, "other", 123) - vim.api.nvim_buf_set_var(0, "floaty", 5120.1) - vim.api.nvim_buf_set_var(0, "nullvar", vim.NIL) - vim.api.nvim_buf_set_var(0, "to_delete", {hello="world"}) - BUF = vim.api.nvim_create_buf(false, true) - vim.api.nvim_buf_set_var(BUF, "testing", "bye") - ]] - - eq('hi', fn.luaeval 'vim.b.testing') - eq('bye', fn.luaeval 'vim.b[BUF].testing') - eq(123, fn.luaeval 'vim.b.other') - eq(5120.1, fn.luaeval 'vim.b.floaty') - eq(NIL, fn.luaeval 'vim.b.nonexistent') - eq(NIL, fn.luaeval 'vim.b[BUF].nonexistent') - eq(NIL, fn.luaeval 'vim.b.nullvar') - -- lost over RPC, so test locally: - eq( - { false, true }, - exec_lua [[ - return {vim.b.nonexistent == vim.NIL, vim.b.nullvar == vim.NIL} - ]] - ) - - matches([[attempt to index .* nil value]], pcall_err(exec_lua, 'return vim.b[BUF][0].testing')) - - eq({ hello = 'world' }, fn.luaeval 'vim.b.to_delete') - exec_lua [[ - vim.b.to_delete = nil - ]] - eq(NIL, fn.luaeval 'vim.b.to_delete') - - exec_lua [[ - local counter = 0 - local function add_counter() counter = counter + 1 end - local function get_counter() return counter end - vim.b.AddCounter = add_counter - vim.b.GetCounter = get_counter - vim.b.fn = {add = add_counter, get = get_counter} - vim.b.AddParens = function(s) return '(' .. s .. ')' end - ]] - - eq(0, eval('b:GetCounter()')) - eval('b:AddCounter()') - eq(1, eval('b:GetCounter()')) - eval('b:AddCounter()') - eq(2, eval('b:GetCounter()')) - exec_lua([[vim.b.AddCounter()]]) - eq(3, exec_lua([[return vim.b.GetCounter()]])) - exec_lua([[vim.api.nvim_buf_get_var(0, 'AddCounter')()]]) - eq(4, exec_lua([[return vim.api.nvim_buf_get_var(0, 'GetCounter')()]])) - exec_lua([[vim.b.fn.add()]]) - eq(5, exec_lua([[return vim.b.fn.get()]])) - exec_lua([[vim.api.nvim_buf_get_var(0, 'fn').add()]]) - eq(6, exec_lua([[return vim.api.nvim_buf_get_var(0, 'fn').get()]])) - eq('((foo))', eval([['foo'->b:AddParens()->b:AddParens()]])) - - exec_lua [[ - local counter = 0 - local function add_counter() counter = counter + 1 end - local function get_counter() return counter end - vim.api.nvim_buf_set_var(0, 'AddCounter', add_counter) - vim.api.nvim_buf_set_var(0, 'GetCounter', get_counter) - vim.api.nvim_buf_set_var(0, 'fn', {add = add_counter, get = get_counter}) - vim.api.nvim_buf_set_var(0, 'AddParens', function(s) return '(' .. s .. ')' end) - ]] - - eq(0, eval('b:GetCounter()')) - eval('b:AddCounter()') - eq(1, eval('b:GetCounter()')) - eval('b:AddCounter()') - eq(2, eval('b:GetCounter()')) - exec_lua([[vim.b.AddCounter()]]) - eq(3, exec_lua([[return vim.b.GetCounter()]])) - exec_lua([[vim.api.nvim_buf_get_var(0, 'AddCounter')()]]) - eq(4, exec_lua([[return vim.api.nvim_buf_get_var(0, 'GetCounter')()]])) - exec_lua([[vim.b.fn.add()]]) - eq(5, exec_lua([[return vim.b.fn.get()]])) - exec_lua([[vim.api.nvim_buf_get_var(0, 'fn').add()]]) - eq(6, exec_lua([[return vim.api.nvim_buf_get_var(0, 'fn').get()]])) - eq('((foo))', eval([['foo'->b:AddParens()->b:AddParens()]])) - - exec([[ - function Test() - endfunction - function s:Test() - endfunction - let b:Unknown_func = function('Test') - let b:Unknown_script_func = function('s:Test') - ]]) - eq(NIL, exec_lua([[return vim.b.Unknown_func]])) - eq(NIL, exec_lua([[return vim.b.Unknown_script_func]])) - - exec_lua [[ - vim.cmd "vnew" - ]] - - eq(NIL, fn.luaeval 'vim.b.testing') - eq(NIL, fn.luaeval 'vim.b.other') - eq(NIL, fn.luaeval 'vim.b.nonexistent') - end) - - it('vim.w', function() - exec_lua [[ - vim.api.nvim_win_set_var(0, "testing", "hi") - vim.api.nvim_win_set_var(0, "other", 123) - vim.api.nvim_win_set_var(0, "to_delete", {hello="world"}) - BUF = vim.api.nvim_create_buf(false, true) - WIN = vim.api.nvim_open_win(BUF, false, { - width=10, height=10, - relative='win', row=0, col=0 - }) - vim.api.nvim_win_set_var(WIN, "testing", "bye") - ]] - - eq('hi', fn.luaeval 'vim.w.testing') - eq('bye', fn.luaeval 'vim.w[WIN].testing') - eq(123, fn.luaeval 'vim.w.other') - eq(NIL, fn.luaeval 'vim.w.nonexistent') - eq(NIL, fn.luaeval 'vim.w[WIN].nonexistent') - - matches([[attempt to index .* nil value]], pcall_err(exec_lua, 'return vim.w[WIN][0].testing')) - - eq({ hello = 'world' }, fn.luaeval 'vim.w.to_delete') - exec_lua [[ - vim.w.to_delete = nil - ]] - eq(NIL, fn.luaeval 'vim.w.to_delete') - - exec_lua [[ - local counter = 0 - local function add_counter() counter = counter + 1 end - local function get_counter() return counter end - vim.w.AddCounter = add_counter - vim.w.GetCounter = get_counter - vim.w.fn = {add = add_counter, get = get_counter} - vim.w.AddParens = function(s) return '(' .. s .. ')' end - ]] - - eq(0, eval('w:GetCounter()')) - eval('w:AddCounter()') - eq(1, eval('w:GetCounter()')) - eval('w:AddCounter()') - eq(2, eval('w:GetCounter()')) - exec_lua([[vim.w.AddCounter()]]) - eq(3, exec_lua([[return vim.w.GetCounter()]])) - exec_lua([[vim.api.nvim_win_get_var(0, 'AddCounter')()]]) - eq(4, exec_lua([[return vim.api.nvim_win_get_var(0, 'GetCounter')()]])) - exec_lua([[vim.w.fn.add()]]) - eq(5, exec_lua([[return vim.w.fn.get()]])) - exec_lua([[vim.api.nvim_win_get_var(0, 'fn').add()]]) - eq(6, exec_lua([[return vim.api.nvim_win_get_var(0, 'fn').get()]])) - eq('((foo))', eval([['foo'->w:AddParens()->w:AddParens()]])) - - exec_lua [[ - local counter = 0 - local function add_counter() counter = counter + 1 end - local function get_counter() return counter end - vim.api.nvim_win_set_var(0, 'AddCounter', add_counter) - vim.api.nvim_win_set_var(0, 'GetCounter', get_counter) - vim.api.nvim_win_set_var(0, 'fn', {add = add_counter, get = get_counter}) - vim.api.nvim_win_set_var(0, 'AddParens', function(s) return '(' .. s .. ')' end) - ]] - - eq(0, eval('w:GetCounter()')) - eval('w:AddCounter()') - eq(1, eval('w:GetCounter()')) - eval('w:AddCounter()') - eq(2, eval('w:GetCounter()')) - exec_lua([[vim.w.AddCounter()]]) - eq(3, exec_lua([[return vim.w.GetCounter()]])) - exec_lua([[vim.api.nvim_win_get_var(0, 'AddCounter')()]]) - eq(4, exec_lua([[return vim.api.nvim_win_get_var(0, 'GetCounter')()]])) - exec_lua([[vim.w.fn.add()]]) - eq(5, exec_lua([[return vim.w.fn.get()]])) - exec_lua([[vim.api.nvim_win_get_var(0, 'fn').add()]]) - eq(6, exec_lua([[return vim.api.nvim_win_get_var(0, 'fn').get()]])) - eq('((foo))', eval([['foo'->w:AddParens()->w:AddParens()]])) - - exec([[ - function Test() - endfunction - function s:Test() - endfunction - let w:Unknown_func = function('Test') - let w:Unknown_script_func = function('s:Test') - ]]) - eq(NIL, exec_lua([[return vim.w.Unknown_func]])) - eq(NIL, exec_lua([[return vim.w.Unknown_script_func]])) - - exec_lua [[ - vim.cmd "vnew" - ]] - - eq(NIL, fn.luaeval 'vim.w.testing') - eq(NIL, fn.luaeval 'vim.w.other') - eq(NIL, fn.luaeval 'vim.w.nonexistent') - end) - - it('vim.t', function() - exec_lua [[ - vim.api.nvim_tabpage_set_var(0, "testing", "hi") - vim.api.nvim_tabpage_set_var(0, "other", 123) - vim.api.nvim_tabpage_set_var(0, "to_delete", {hello="world"}) - ]] - - eq('hi', fn.luaeval 'vim.t.testing') - eq(123, fn.luaeval 'vim.t.other') - eq(NIL, fn.luaeval 'vim.t.nonexistent') - eq('hi', fn.luaeval 'vim.t[0].testing') - eq(123, fn.luaeval 'vim.t[0].other') - eq(NIL, fn.luaeval 'vim.t[0].nonexistent') - - matches([[attempt to index .* nil value]], pcall_err(exec_lua, 'return vim.t[0][0].testing')) - - eq({ hello = 'world' }, fn.luaeval 'vim.t.to_delete') - exec_lua [[ - vim.t.to_delete = nil - ]] - eq(NIL, fn.luaeval 'vim.t.to_delete') - - exec_lua [[ - local counter = 0 - local function add_counter() counter = counter + 1 end - local function get_counter() return counter end - vim.t.AddCounter = add_counter - vim.t.GetCounter = get_counter - vim.t.fn = {add = add_counter, get = get_counter} - vim.t.AddParens = function(s) return '(' .. s .. ')' end - ]] - - eq(0, eval('t:GetCounter()')) - eval('t:AddCounter()') - eq(1, eval('t:GetCounter()')) - eval('t:AddCounter()') - eq(2, eval('t:GetCounter()')) - exec_lua([[vim.t.AddCounter()]]) - eq(3, exec_lua([[return vim.t.GetCounter()]])) - exec_lua([[vim.api.nvim_tabpage_get_var(0, 'AddCounter')()]]) - eq(4, exec_lua([[return vim.api.nvim_tabpage_get_var(0, 'GetCounter')()]])) - exec_lua([[vim.t.fn.add()]]) - eq(5, exec_lua([[return vim.t.fn.get()]])) - exec_lua([[vim.api.nvim_tabpage_get_var(0, 'fn').add()]]) - eq(6, exec_lua([[return vim.api.nvim_tabpage_get_var(0, 'fn').get()]])) - eq('((foo))', eval([['foo'->t:AddParens()->t:AddParens()]])) - - exec_lua [[ - local counter = 0 - local function add_counter() counter = counter + 1 end - local function get_counter() return counter end - vim.api.nvim_tabpage_set_var(0, 'AddCounter', add_counter) - vim.api.nvim_tabpage_set_var(0, 'GetCounter', get_counter) - vim.api.nvim_tabpage_set_var(0, 'fn', {add = add_counter, get = get_counter}) - vim.api.nvim_tabpage_set_var(0, 'AddParens', function(s) return '(' .. s .. ')' end) - ]] - - eq(0, eval('t:GetCounter()')) - eval('t:AddCounter()') - eq(1, eval('t:GetCounter()')) - eval('t:AddCounter()') - eq(2, eval('t:GetCounter()')) - exec_lua([[vim.t.AddCounter()]]) - eq(3, exec_lua([[return vim.t.GetCounter()]])) - exec_lua([[vim.api.nvim_tabpage_get_var(0, 'AddCounter')()]]) - eq(4, exec_lua([[return vim.api.nvim_tabpage_get_var(0, 'GetCounter')()]])) - exec_lua([[vim.t.fn.add()]]) - eq(5, exec_lua([[return vim.t.fn.get()]])) - exec_lua([[vim.api.nvim_tabpage_get_var(0, 'fn').add()]]) - eq(6, exec_lua([[return vim.api.nvim_tabpage_get_var(0, 'fn').get()]])) - eq('((foo))', eval([['foo'->t:AddParens()->t:AddParens()]])) - - exec_lua [[ - vim.cmd "tabnew" - ]] - - eq(NIL, fn.luaeval 'vim.t.testing') - eq(NIL, fn.luaeval 'vim.t.other') - eq(NIL, fn.luaeval 'vim.t.nonexistent') - end) - - it('vim.env', function() - exec_lua([[vim.fn.setenv('A', 123)]]) - eq('123', fn.luaeval('vim.env.A')) - exec_lua([[vim.env.A = 456]]) - eq('456', fn.luaeval('vim.env.A')) - exec_lua([[vim.env.A = nil]]) - eq(NIL, fn.luaeval('vim.env.A')) - - eq(true, fn.luaeval('vim.env.B == nil')) - - command([[let $HOME = 'foo']]) - eq('foo', fn.expand('~')) - eq('foo', fn.luaeval('vim.env.HOME')) - exec_lua([[vim.env.HOME = nil]]) - eq('foo', fn.expand('~')) - exec_lua([[vim.env.HOME = 'bar']]) - eq('bar', fn.expand('~')) - eq('bar', fn.luaeval('vim.env.HOME')) - end) - - it('vim.v', function() - eq(fn.luaeval "vim.api.nvim_get_vvar('progpath')", fn.luaeval 'vim.v.progpath') - eq(false, fn.luaeval "vim.v['false']") - eq(NIL, fn.luaeval 'vim.v.null') - matches([[attempt to index .* nil value]], pcall_err(exec_lua, 'return vim.v[0].progpath')) - eq('Key is read-only: count', pcall_err(exec_lua, [[vim.v.count = 42]])) - eq('Dict is locked', pcall_err(exec_lua, [[vim.v.nosuchvar = 42]])) - eq('Key is fixed: errmsg', pcall_err(exec_lua, [[vim.v.errmsg = nil]])) - exec_lua([[vim.v.errmsg = 'set by Lua']]) - eq('set by Lua', eval('v:errmsg')) - exec_lua([[vim.v.errmsg = 42]]) - eq('42', eval('v:errmsg')) - exec_lua([[vim.v.oldfiles = { 'one', 'two' }]]) - eq({ 'one', 'two' }, eval('v:oldfiles')) - exec_lua([[vim.v.oldfiles = {}]]) - eq({}, eval('v:oldfiles')) - eq('Setting v:oldfiles to value with wrong type', pcall_err(exec_lua, [[vim.v.oldfiles = 'a']])) - eq({}, eval('v:oldfiles')) - - feed('i foo foo foo0/foo') - eq({ 1, 1 }, api.nvim_win_get_cursor(0)) - eq(1, eval('v:searchforward')) - feed('n') - eq({ 1, 5 }, api.nvim_win_get_cursor(0)) - exec_lua([[vim.v.searchforward = 0]]) - eq(0, eval('v:searchforward')) - feed('n') - eq({ 1, 1 }, api.nvim_win_get_cursor(0)) - exec_lua([[vim.v.searchforward = 1]]) - eq(1, eval('v:searchforward')) - feed('n') - eq({ 1, 5 }, api.nvim_win_get_cursor(0)) - - local screen = Screen.new(60, 3) - eq(1, eval('v:hlsearch')) - screen:expect { - grid = [[ - {10:foo} {10:^foo} {10:foo} | - {1:~ }| - | - ]], - } - exec_lua([[vim.v.hlsearch = 0]]) - eq(0, eval('v:hlsearch')) - screen:expect { - grid = [[ - foo ^foo foo | - {1:~ }| - | - ]], - } - exec_lua([[vim.v.hlsearch = 1]]) - eq(1, eval('v:hlsearch')) - screen:expect { - grid = [[ - {10:foo} {10:^foo} {10:foo} | - {1:~ }| - | - ]], - } - end) - - it('vim.bo', function() - eq('', fn.luaeval 'vim.bo.filetype') - exec_lua [[ - vim.api.nvim_set_option_value("filetype", "markdown", {}) - BUF = vim.api.nvim_create_buf(false, true) - vim.api.nvim_set_option_value("modifiable", false, {buf = BUF}) - ]] - eq(false, fn.luaeval 'vim.bo.modified') - eq('markdown', fn.luaeval 'vim.bo.filetype') - eq(false, fn.luaeval 'vim.bo[BUF].modifiable') - exec_lua [[ - vim.bo.filetype = '' - vim.bo[BUF].modifiable = true - ]] - eq('', fn.luaeval 'vim.bo.filetype') - eq(true, fn.luaeval 'vim.bo[BUF].modifiable') - matches("Unknown option 'nosuchopt'$", pcall_err(exec_lua, 'return vim.bo.nosuchopt')) - matches('Expected Lua string$', pcall_err(exec_lua, 'return vim.bo[0][0].autoread')) - matches('Invalid buffer id: %-1$', pcall_err(exec_lua, 'return vim.bo[-1].filetype')) - end) - - it('vim.wo', function() - exec_lua [[ - vim.api.nvim_set_option_value("cole", 2, {}) - vim.cmd "split" - vim.api.nvim_set_option_value("cole", 2, {}) - ]] - eq(2, fn.luaeval 'vim.wo.cole') - exec_lua [[ - vim.wo.conceallevel = 0 - ]] - eq(0, fn.luaeval 'vim.wo.cole') - eq(0, fn.luaeval 'vim.wo[0].cole') - eq(0, fn.luaeval 'vim.wo[1001].cole') - matches("Unknown option 'notanopt'$", pcall_err(exec_lua, 'return vim.wo.notanopt')) - matches('Invalid window id: %-1$', pcall_err(exec_lua, 'return vim.wo[-1].list')) - eq(2, fn.luaeval 'vim.wo[1000].cole') - exec_lua [[ - vim.wo[1000].cole = 0 - ]] - eq(0, fn.luaeval 'vim.wo[1000].cole') - - -- Can handle global-local values - exec_lua [[vim.o.scrolloff = 100]] - exec_lua [[vim.wo.scrolloff = 200]] - eq(200, fn.luaeval 'vim.wo.scrolloff') - exec_lua [[vim.wo.scrolloff = -1]] - eq(100, fn.luaeval 'vim.wo.scrolloff') - exec_lua [[ - vim.wo[0][0].scrolloff = 200 - vim.cmd "enew" - ]] - eq(100, fn.luaeval 'vim.wo.scrolloff') - - matches('only bufnr=0 is supported', pcall_err(exec_lua, 'vim.wo[0][10].signcolumn = "no"')) - - matches('only bufnr=0 is supported', pcall_err(exec_lua, 'local a = vim.wo[0][10].signcolumn')) - end) - - describe('vim.opt', function() - -- TODO: We still need to write some tests for optlocal, opt and then getting the options - -- Probably could also do some stuff with getting things from viml side as well to confirm behavior is the same. - - it('allows setting number values', function() - local scrolloff = exec_lua [[ - vim.opt.scrolloff = 10 - return vim.o.scrolloff - ]] - eq(10, scrolloff) - end) - - pending('handles STUPID window things', function() - local result = exec_lua [[ - local result = {} - - table.insert(result, vim.api.nvim_get_option_value('scrolloff', {scope='global'})) - table.insert(result, vim.api.nvim_get_option_value('scrolloff', {win=0})) - - return result - ]] - - eq({}, result) - end) - - it('allows setting tables', function() - local wildignore = exec_lua [[ - vim.opt.wildignore = { 'hello', 'world' } - return vim.o.wildignore - ]] - eq('hello,world', wildignore) - end) - - it('allows setting tables with shortnames', function() - local wildignore = exec_lua [[ - vim.opt.wig = { 'hello', 'world' } - return vim.o.wildignore - ]] - eq('hello,world', wildignore) - end) - - it('errors when you attempt to set string values to numeric options', function() - local result = exec_lua [[ - return { - pcall(function() vim.opt.textwidth = 'hello world' end) - } - ]] - - eq(false, result[1]) - end) - - it('errors when you attempt to setlocal a global value', function() - local result = exec_lua [[ - return pcall(function() vim.opt_local.clipboard = "hello" end) - ]] - - eq(false, result) - end) - - it('allows you to set boolean values', function() - eq( - { true, false, true }, - exec_lua [[ - local results = {} - - vim.opt.autoindent = true - table.insert(results, vim.bo.autoindent) - - vim.opt.autoindent = false - table.insert(results, vim.bo.autoindent) - - vim.opt.autoindent = not vim.opt.autoindent:get() - table.insert(results, vim.bo.autoindent) - - return results - ]] - ) - end) - - it('changes current buffer values and defaults for global local values', function() - local result = exec_lua [[ - local result = {} - - vim.opt.makeprg = "global-local" - table.insert(result, vim.go.makeprg) - table.insert(result, vim.api.nvim_get_option_value('makeprg', {buf=0})) - - vim.opt_local.mp = "only-local" - table.insert(result, vim.go.makeprg) - table.insert(result, vim.api.nvim_get_option_value('makeprg', {buf=0})) - - vim.opt_global.makeprg = "only-global" - table.insert(result, vim.go.makeprg) - table.insert(result, vim.api.nvim_get_option_value('makeprg', {buf=0})) - - vim.opt.makeprg = "global-local" - table.insert(result, vim.go.makeprg) - table.insert(result, vim.api.nvim_get_option_value('makeprg', {buf=0})) - return result - ]] - - -- Set -> global & local - eq('global-local', result[1]) - eq('', result[2]) - - -- Setlocal -> only local - eq('global-local', result[3]) - eq('only-local', result[4]) - - -- Setglobal -> only global - eq('only-global', result[5]) - eq('only-local', result[6]) - - -- Set -> sets global value and resets local value - eq('global-local', result[7]) - eq('', result[8]) - end) - - it('allows you to retrieve window opts even if they have not been set', function() - local result = exec_lua [[ - local result = {} - table.insert(result, vim.opt.number:get()) - table.insert(result, vim.opt_local.number:get()) - - vim.opt_local.number = true - table.insert(result, vim.opt.number:get()) - table.insert(result, vim.opt_local.number:get()) - - return result - ]] - eq({ false, false, true, true }, result) - end) - - it('allows all sorts of string manipulation', function() - eq( - { 'hello', 'hello world', 'start hello world' }, - exec_lua [[ - local results = {} - - vim.opt.makeprg = "hello" - table.insert(results, vim.o.makeprg) - - vim.opt.makeprg = vim.opt.makeprg + " world" - table.insert(results, vim.o.makeprg) - - vim.opt.makeprg = vim.opt.makeprg ^ "start " - table.insert(results, vim.o.makeprg) - - return results - ]] - ) - end) - - describe('option:get()', function() - it('works for boolean values', function() - eq( - false, - exec_lua [[ - vim.opt.number = false - return vim.opt.number:get() - ]] - ) - end) - - it('works for number values', function() - local tabstop = exec_lua [[ - vim.opt.tabstop = 10 - return vim.opt.tabstop:get() - ]] - - eq(10, tabstop) - end) - - it('works for string values', function() - eq( - 'hello world', - exec_lua [[ - vim.opt.makeprg = "hello world" - return vim.opt.makeprg:get() - ]] - ) - end) - - it('works for set type flaglists', function() - local formatoptions = exec_lua [[ - vim.opt.formatoptions = 'tcro' - return vim.opt.formatoptions:get() - ]] - - eq(true, formatoptions.t) - eq(true, not formatoptions.q) - end) - - it('works for set type flaglists', function() - local formatoptions = exec_lua [[ - vim.opt.formatoptions = { t = true, c = true, r = true, o = true } - return vim.opt.formatoptions:get() - ]] - - eq(true, formatoptions.t) - eq(true, not formatoptions.q) - end) - - it('works for array list type options', function() - local wildignore = exec_lua [[ - vim.opt.wildignore = "*.c,*.o,__pycache__" - return vim.opt.wildignore:get() - ]] - - eq(3, #wildignore) - eq('*.c', wildignore[1]) - end) - - it('works for options that are both commalist and flaglist', function() - local result = exec_lua [[ - vim.opt.whichwrap = "b,s" - return vim.opt.whichwrap:get() - ]] - - eq({ b = true, s = true }, result) - - result = exec_lua [[ - vim.opt.whichwrap = { b = true, s = false, h = true } - return vim.opt.whichwrap:get() - ]] - - eq({ b = true, h = true }, result) - end) - - it('works for key-value pair options', function() - local listchars = exec_lua [[ - vim.opt.listchars = "tab:> ,space:_" - return vim.opt.listchars:get() - ]] - - eq({ - tab = '> ', - space = '_', - }, listchars) - end) - - it('allows you to add numeric options', function() - eq( - 16, - exec_lua [[ - vim.opt.tabstop = 12 - vim.opt.tabstop = vim.opt.tabstop + 4 - return vim.bo.tabstop - ]] - ) - end) - - it('allows you to subtract numeric options', function() - eq( - 2, - exec_lua [[ - vim.opt.tabstop = 4 - vim.opt.tabstop = vim.opt.tabstop - 2 - return vim.bo.tabstop - ]] - ) - end) - end) - - describe('key:value style options', function() - it('handles dict style', function() - local listchars = exec_lua [[ - vim.opt.listchars = { - eol = "~", - space = ".", - } - - return vim.o.listchars - ]] - eq('eol:~,space:.', listchars) - end) - - it('allows adding dict style', function() - local listchars = exec_lua [[ - vim.opt.listchars = { - eol = "~", - space = ".", - } - - vim.opt.listchars = vim.opt.listchars + { space = "-" } - - return vim.o.listchars - ]] - - eq('eol:~,space:-', listchars) - end) - - it('allows adding dict style', function() - local listchars = exec_lua [[ - vim.opt.listchars = { - eol = "~", - space = ".", - } - vim.opt.listchars = vim.opt.listchars + { space = "-" } + { space = "_" } - - return vim.o.listchars - ]] - - eq('eol:~,space:_', listchars) - end) - - it('allows completely new keys', function() - local listchars = exec_lua [[ - vim.opt.listchars = { - eol = "~", - space = ".", - } - vim.opt.listchars = vim.opt.listchars + { tab = ">>>" } - - return vim.o.listchars - ]] - - eq('eol:~,space:.,tab:>>>', listchars) - end) - - it('allows subtracting dict style', function() - local listchars = exec_lua [[ - vim.opt.listchars = { - eol = "~", - space = ".", - } - vim.opt.listchars = vim.opt.listchars - "space" - - return vim.o.listchars - ]] - - eq('eol:~', listchars) - end) - - it('allows subtracting dict style', function() - local listchars = exec_lua [[ - vim.opt.listchars = { - eol = "~", - space = ".", - } - vim.opt.listchars = vim.opt.listchars - "space" - "eol" - - return vim.o.listchars - ]] - - eq('', listchars) - end) - - it('allows subtracting dict style multiple times', function() - local listchars = exec_lua [[ - vim.opt.listchars = { - eol = "~", - space = ".", - } - vim.opt.listchars = vim.opt.listchars - "space" - "space" - - return vim.o.listchars - ]] - - eq('eol:~', listchars) - end) - - it('allows adding a key:value string to a listchars', function() - local listchars = exec_lua [[ - vim.opt.listchars = { - eol = "~", - space = ".", - } - vim.opt.listchars = vim.opt.listchars + "tab:>~" - - return vim.o.listchars - ]] - - eq('eol:~,space:.,tab:>~', listchars) - end) - - it('allows prepending a key:value string to a listchars', function() - local listchars = exec_lua [[ - vim.opt.listchars = { - eol = "~", - space = ".", - } - vim.opt.listchars = vim.opt.listchars ^ "tab:>~" - - return vim.o.listchars - ]] - - eq('eol:~,space:.,tab:>~', listchars) - end) - end) - - it('automatically sets when calling remove', function() - eq( - 'foo,baz', - exec_lua [[ - vim.opt.wildignore = "foo,bar,baz" - vim.opt.wildignore:remove("bar") - - return vim.o.wildignore - ]] - ) - end) - - it('automatically sets when calling remove with a table', function() - eq( - 'foo', - exec_lua [[ - vim.opt.wildignore = "foo,bar,baz" - vim.opt.wildignore:remove { "bar", "baz" } - - return vim.o.wildignore - ]] - ) - end) - - it('automatically sets when calling append', function() - eq( - 'foo,bar,baz,bing', - exec_lua [[ - vim.opt.wildignore = "foo,bar,baz" - vim.opt.wildignore:append("bing") - - return vim.o.wildignore - ]] - ) - end) - - it('automatically sets when calling append with a table', function() - eq( - 'foo,bar,baz,bing,zap', - exec_lua [[ - vim.opt.wildignore = "foo,bar,baz" - vim.opt.wildignore:append { "bing", "zap" } - - return vim.o.wildignore - ]] - ) - end) - - it('allows adding tables', function() - local wildignore = exec_lua [[ - vim.opt.wildignore = 'foo' - return vim.o.wildignore - ]] - eq('foo', wildignore) - - wildignore = exec_lua [[ - vim.opt.wildignore = vim.opt.wildignore + { 'bar', 'baz' } - return vim.o.wildignore - ]] - eq('foo,bar,baz', wildignore) - end) - - it('handles adding duplicates', function() - local wildignore = exec_lua [[ - vim.opt.wildignore = 'foo' - return vim.o.wildignore - ]] - eq('foo', wildignore) - - wildignore = exec_lua [[ - vim.opt.wildignore = vim.opt.wildignore + { 'bar', 'baz' } - return vim.o.wildignore - ]] - eq('foo,bar,baz', wildignore) - - wildignore = exec_lua [[ - vim.opt.wildignore = vim.opt.wildignore + { 'bar', 'baz' } - return vim.o.wildignore - ]] - eq('foo,bar,baz', wildignore) - end) - - it('allows adding multiple times', function() - local wildignore = exec_lua [[ - vim.opt.wildignore = 'foo' - vim.opt.wildignore = vim.opt.wildignore + 'bar' + 'baz' - return vim.o.wildignore - ]] - eq('foo,bar,baz', wildignore) - end) - - it('removes values when you use minus', function() - local wildignore = exec_lua [[ - vim.opt.wildignore = 'foo' - return vim.o.wildignore - ]] - eq('foo', wildignore) - - wildignore = exec_lua [[ - vim.opt.wildignore = vim.opt.wildignore + { 'bar', 'baz' } - return vim.o.wildignore - ]] - eq('foo,bar,baz', wildignore) - - wildignore = exec_lua [[ - vim.opt.wildignore = vim.opt.wildignore - 'bar' - return vim.o.wildignore - ]] - eq('foo,baz', wildignore) - end) - - it('prepends values when using ^', function() - local wildignore = exec_lua [[ - vim.opt.wildignore = 'foo' - vim.opt.wildignore = vim.opt.wildignore ^ 'first' - return vim.o.wildignore - ]] - eq('first,foo', wildignore) - - wildignore = exec_lua [[ - vim.opt.wildignore = vim.opt.wildignore ^ 'super_first' - return vim.o.wildignore - ]] - eq('super_first,first,foo', wildignore) - end) - - it('does not remove duplicates from wildmode: #14708', function() - local wildmode = exec_lua [[ - vim.opt.wildmode = {"full", "list", "full"} - return vim.o.wildmode - ]] - - eq('full,list,full', wildmode) - end) - - describe('option types', function() - it('allows to set option with numeric value', function() - eq( - 4, - exec_lua [[ - vim.opt.tabstop = 4 - return vim.bo.tabstop - ]] - ) - - matches( - "Invalid option type 'string' for 'tabstop'", - pcall_err( - exec_lua, - [[ - vim.opt.tabstop = '4' - ]] - ) - ) - matches( - "Invalid option type 'boolean' for 'tabstop'", - pcall_err( - exec_lua, - [[ - vim.opt.tabstop = true - ]] - ) - ) - matches( - "Invalid option type 'table' for 'tabstop'", - pcall_err( - exec_lua, - [[ - vim.opt.tabstop = {4, 2} - ]] - ) - ) - matches( - "Invalid option type 'function' for 'tabstop'", - pcall_err( - exec_lua, - [[ - vim.opt.tabstop = function() - return 4 - end - ]] - ) - ) - end) - - it('allows to set option with boolean value', function() - eq( - true, - exec_lua [[ - vim.opt.undofile = true - return vim.bo.undofile - ]] - ) - - matches( - "Invalid option type 'number' for 'undofile'", - pcall_err( - exec_lua, - [[ - vim.opt.undofile = 0 - ]] - ) - ) - matches( - "Invalid option type 'table' for 'undofile'", - pcall_err( - exec_lua, - [[ - vim.opt.undofile = {true} - ]] - ) - ) - matches( - "Invalid option type 'string' for 'undofile'", - pcall_err( - exec_lua, - [[ - vim.opt.undofile = 'true' - ]] - ) - ) - matches( - "Invalid option type 'function' for 'undofile'", - pcall_err( - exec_lua, - [[ - vim.opt.undofile = function() - return true - end - ]] - ) - ) - end) - - it('allows to set option with array or string value', function() - eq( - 'indent,eol,start', - exec_lua [[ - vim.opt.backspace = {'indent','eol','start'} - return vim.go.backspace - ]] - ) - eq( - 'indent,eol,start', - exec_lua [[ - vim.opt.backspace = 'indent,eol,start' - return vim.go.backspace - ]] - ) - - matches( - "Invalid option type 'boolean' for 'backspace'", - pcall_err( - exec_lua, - [[ - vim.opt.backspace = true - ]] - ) - ) - matches( - "Invalid option type 'number' for 'backspace'", - pcall_err( - exec_lua, - [[ - vim.opt.backspace = 2 - ]] - ) - ) - matches( - "Invalid option type 'function' for 'backspace'", - pcall_err( - exec_lua, - [[ - vim.opt.backspace = function() - return 'indent,eol,start' - end - ]] - ) - ) - end) - - it('allows set option with map or string value', function() - eq( - 'eol:~,space:.', - exec_lua [[ - vim.opt.listchars = { - eol = "~", - space = ".", - } - return vim.o.listchars - ]] - ) - eq( - 'eol:~,space:.,tab:>~', - exec_lua [[ - vim.opt.listchars = "eol:~,space:.,tab:>~" - return vim.o.listchars - ]] - ) - - matches( - "Invalid option type 'boolean' for 'listchars'", - pcall_err( - exec_lua, - [[ - vim.opt.listchars = true - ]] - ) - ) - matches( - "Invalid option type 'number' for 'listchars'", - pcall_err( - exec_lua, - [[ - vim.opt.listchars = 2 - ]] - ) - ) - matches( - "Invalid option type 'function' for 'listchars'", - pcall_err( - exec_lua, - [[ - vim.opt.listchars = function() - return "eol:~,space:.,tab:>~" - end - ]] - ) - ) - end) - - it('allows set option with set or string value', function() - local ww = exec_lua [[ - vim.opt.whichwrap = { - b = true, - s = 1, - } - return vim.go.whichwrap - ]] - - eq('b,s', ww) - eq( - 'b,s,<,>,[,]', - exec_lua [[ - vim.opt.whichwrap = "b,s,<,>,[,]" - return vim.go.whichwrap - ]] - ) - - matches( - "Invalid option type 'boolean' for 'whichwrap'", - pcall_err( - exec_lua, - [[ - vim.opt.whichwrap = true - ]] - ) - ) - matches( - "Invalid option type 'number' for 'whichwrap'", - pcall_err( - exec_lua, - [[ - vim.opt.whichwrap = 2 - ]] - ) - ) - matches( - "Invalid option type 'function' for 'whichwrap'", - pcall_err( - exec_lua, - [[ - vim.opt.whichwrap = function() - return "b,s,<,>,[,]" - end - ]] - ) - ) - end) - end) - - -- isfname=a,b,c,,,d,e,f - it('can handle isfname ,,,', function() - local result = exec_lua [[ - vim.opt.isfname = "a,b,,,c" - return { vim.opt.isfname:get(), vim.go.isfname } - ]] - - eq({ { ',', 'a', 'b', 'c' }, 'a,b,,,c' }, result) - end) - - -- isfname=a,b,c,^,,def - it('can handle isfname ,^,,', function() - local result = exec_lua [[ - vim.opt.isfname = "a,b,^,,c" - return { vim.opt.isfname:get(), vim.go.isfname } - ]] - - eq({ { '^,', 'a', 'b', 'c' }, 'a,b,^,,c' }, result) - end) - - describe('https://github.com/neovim/neovim/issues/14828', function() - it('gives empty list when item is empty:array', function() - eq( - {}, - exec_lua [[ - vim.cmd("set wildignore=") - return vim.opt.wildignore:get() - ]] - ) - - eq( - {}, - exec_lua [[ - vim.opt.wildignore = {} - return vim.opt.wildignore:get() - ]] - ) - end) - - it('gives empty list when item is empty:set', function() - eq( - {}, - exec_lua [[ - vim.cmd("set formatoptions=") - return vim.opt.formatoptions:get() - ]] - ) - - eq( - {}, - exec_lua [[ - vim.opt.formatoptions = {} - return vim.opt.formatoptions:get() - ]] - ) - end) - - it('does not append to empty item', function() - eq( - { '*.foo', '*.bar' }, - exec_lua [[ - vim.opt.wildignore = {} - vim.opt.wildignore:append { "*.foo", "*.bar" } - - return vim.opt.wildignore:get() - ]] - ) - end) - - it('does not prepend to empty item', function() - eq( - { '*.foo', '*.bar' }, - exec_lua [[ - vim.opt.wildignore = {} - vim.opt.wildignore:prepend { "*.foo", "*.bar" } - - return vim.opt.wildignore:get() - ]] - ) - end) - - it('append to empty set', function() - eq( - { t = true }, - exec_lua [[ - vim.opt.formatoptions = {} - vim.opt.formatoptions:append("t") - - return vim.opt.formatoptions:get() - ]] - ) - end) - - it('prepend to empty set', function() - eq( - { t = true }, - exec_lua [[ - vim.opt.formatoptions = {} - vim.opt.formatoptions:prepend("t") - - return vim.opt.formatoptions:get() - ]] - ) - end) - end) - end) -- vim.opt - - describe('vim.opt_local', function() - it('appends into global value when changing local option value', function() - eq( - { 'foo,bar,baz,qux' }, - exec_lua [[ - local result = {} - - vim.opt.tags = "foo,bar" - vim.opt_local.tags:append("baz") - vim.opt_local.tags:append("qux") - - table.insert(result, vim.bo.tags) - - return result - ]] - ) - end) - end) - - describe('vim.opt_global', function() - it('gets current global option value', function() - eq( - { 'yes' }, - exec_lua [[ - local result = {} - - vim.cmd "setglobal signcolumn=yes" - table.insert(result, vim.opt_global.signcolumn:get()) - - return result - ]] - ) - end) - end) - it('vim.cmd', function() exec_lua [[ vim.cmd "autocmd BufNew * ++once lua BUF = vim.fn.expand('')" diff --git a/test/functional/testnvim/exec_lua.lua b/test/functional/testnvim/exec_lua.lua index ddd9905ce7..4a55cb2eb3 100644 --- a/test/functional/testnvim/exec_lua.lua +++ b/test/functional/testnvim/exec_lua.lua @@ -86,7 +86,7 @@ function M.handler(bytecode, upvalues, ...) set_upvalues(f, upvalues) -- Run in pcall so we can return any print messages - local ret = { pcall(f, ...) } --- @type any[] + local ret = { xpcall(f, debug.traceback, ...) } --- @type any[] _G.print = orig_print