mirror of
https://github.com/neovim/neovim.git
synced 2024-12-19 02:34:59 -07:00
feat(options): allow setting function values to vim.o/go/bo
Problem: Assigning function values to options is tedious. Solution: Allow setting function values to options directly.
This commit is contained in:
parent
d27bbebf8e
commit
6fb4d0050a
@ -746,8 +746,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:
|
||||
|
@ -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*
|
||||
|
@ -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<string,boolean>
|
||||
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<string,function?>
|
||||
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<string,any>
|
||||
for _, k in ipairs(idxs) do
|
||||
t[k] = t[k] or {}
|
||||
t = t[k]
|
||||
end
|
||||
end
|
||||
|
||||
--- @type table<string,function?>
|
||||
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
|
||||
--- <
|
||||
--- </pre>
|
||||
|
||||
--- 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,
|
||||
})
|
||||
|
||||
|
@ -1066,8 +1066,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 {}
|
||||
|
@ -149,7 +149,7 @@ describe('lua stdlib', function()
|
||||
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(BUF, 'testing', 'bye')
|
||||
vim.api.nvim_buf_set_var(_G.BUF, 'testing', 'bye')
|
||||
end)
|
||||
|
||||
eq('hi', fn.luaeval 'vim.b.testing')
|
||||
@ -164,7 +164,10 @@ describe('lua stdlib', 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'))
|
||||
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 [[
|
||||
@ -174,12 +177,18 @@ describe('lua stdlib', function()
|
||||
|
||||
exec_lua(function()
|
||||
local counter = 0
|
||||
local function add_counter() counter = counter + 1 end
|
||||
local function get_counter() return counter end
|
||||
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
|
||||
vim.b.fn = { add = add_counter, get = get_counter }
|
||||
vim.b.AddParens = function(s)
|
||||
return '(' .. s .. ')'
|
||||
end
|
||||
end)
|
||||
|
||||
eq(0, eval('b:GetCounter()'))
|
||||
@ -199,12 +208,18 @@ describe('lua stdlib', function()
|
||||
|
||||
exec_lua(function()
|
||||
local counter = 0
|
||||
local function add_counter() counter = counter + 1 end
|
||||
local function get_counter() return counter end
|
||||
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)
|
||||
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()'))
|
||||
@ -261,7 +276,10 @@ describe('lua stdlib', function()
|
||||
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'))
|
||||
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 [[
|
||||
@ -456,7 +474,10 @@ describe('lua stdlib', function()
|
||||
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(
|
||||
'Setting v:oldfiles to value with wrong type',
|
||||
pcall_err(exec_lua, [[vim.v.oldfiles = 'a']])
|
||||
)
|
||||
eq({}, eval('v:oldfiles'))
|
||||
|
||||
feed('i foo foo foo<Esc>0/foo<CR>')
|
||||
@ -528,6 +549,13 @@ describe('lua stdlib', function()
|
||||
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()
|
||||
@ -567,7 +595,26 @@ describe('lua stdlib', function()
|
||||
|
||||
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'))
|
||||
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)
|
||||
|
||||
@ -1073,7 +1120,10 @@ describe('lua stdlib', function()
|
||||
)
|
||||
matches(
|
||||
"Invalid option type 'function' for 'listchars'",
|
||||
pcall_err(exec_lua, [[vim.opt.listchars = function() return "eol:~,space:.,tab:>~" end]])
|
||||
pcall_err(
|
||||
exec_lua,
|
||||
[[vim.opt.listchars = function() return "eol:~,space:.,tab:>~" end]]
|
||||
)
|
||||
)
|
||||
end)
|
||||
|
||||
@ -1198,5 +1248,4 @@ describe('lua stdlib', function()
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
|
||||
end)
|
||||
|
@ -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
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user