Compare commits

...

5 Commits

Author SHA1 Message Date
Lewis Russell
62226566c6
Merge 6fb4d0050a into b03e790cdd 2024-12-17 12:52:43 +01:00
Lewis Russell
6fb4d0050a 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.
2024-12-10 11:46:07 +00:00
Lewis Russell
d27bbebf8e test: add more structure to vim.bo/wo tests 2024-12-10 11:18:58 +00:00
Lewis Russell
c966654faa test: move lua option/variable tests to a separate file 2024-12-10 11:18:58 +00:00
Lewis Russell
e986eeaaae refactor: option tests 2024-12-10 11:18:58 +00:00
7 changed files with 1429 additions and 1454 deletions

View File

@ -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:

View File

@ -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*

View File

@ -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,
})

View File

@ -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 {}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -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