feat(options): lua closure/function options

Problem:
Cannot assign Lua functions/closures to "func" ('completefunc',
'tagfun', …) or "expr" ('foldexpr', 'indentexpr', …) options.

Solution:
- Store "func"/"expr" options as `Callback` instead of string.
- Delete oceans of copy-pasted code.
- BREAKING: LuaRef returned via RPC/Vimscript is now represented as
  `"<Lua N: file:line>"` (like what `:map` shows) instead of `nil`.
- Note: `man.vim` still uses `v:lua` string, bc it's a vimscript ftplugin.

Helped-by: Lewis Russell <lewis6991@gmail.com>
This commit is contained in:
Justin M. Keyes
2026-07-29 19:09:48 +02:00
parent 917232b508
commit 726d1a92d2
55 changed files with 778 additions and 572 deletions

View File

@@ -768,7 +768,7 @@ function M.open_page(count, smods, args)
end
local buf = api.nvim_get_current_buf()
local save_tfu = vim.bo[buf].tagfunc
vim.bo[buf].tagfunc = "v:lua.require'man'.goto_tag"
vim.bo[buf].tagfunc = M.goto_tag
local target = ('%s(%s)'):format(name, sect)

View File

@@ -221,7 +221,7 @@ local function operator(mode)
-- Used without arguments as part of expression mapping. Otherwise it is
-- called as 'operatorfunc'.
if mode == nil then
vim.o.operatorfunc = "v:lua.require'vim._comment'.operator"
vim.o.operatorfunc = operator
return 'g@'
end

View File

@@ -446,14 +446,12 @@ do
-- Add empty lines
vim.keymap.set('n', '[<Space>', function()
-- TODO: update once it is possible to assign a Lua function to options #25672
vim.go.operatorfunc = "v:lua.require'vim._core.util'.space_above"
vim.go.operatorfunc = require('vim._core.util').space_above
return 'g@l'
end, { expr = true, desc = 'Add empty line above cursor' })
vim.keymap.set('n', ']<Space>', function()
-- TODO: update once it is possible to assign a Lua function to options #25672
vim.go.operatorfunc = "v:lua.require'vim._core.util'.space_below"
vim.go.operatorfunc = require('vim._core.util').space_below
return 'g@l'
end, { expr = true, desc = 'Add empty line below cursor' })
end

View File

@@ -3,7 +3,6 @@
local M = {}
--- Adds one or more blank lines above or below the cursor.
-- TODO: move to _core/defaults.lua once it is possible to assign a Lua function to options #25672
--- @param above? boolean Place blank line(s) above the cursor
local function add_blank(above)
local offset = above and 1 or 0
@@ -12,12 +11,10 @@ local function add_blank(above)
vim.api.nvim_buf_set_lines(0, linenr - offset, linenr - offset, true, repeated)
end
-- TODO: move to _core/defaults.lua once it is possible to assign a Lua function to options #25672
function M.space_above()
add_blank(true)
end
-- TODO: move to _core/defaults.lua once it is possible to assign a Lua function to options #25672
function M.space_below()
add_blank()
end

View File

@@ -840,7 +840,7 @@ vim.bo.channel = vim.o.channel
--- Otherwise the expression is evaluated in the context of the script
--- where the option was set, thus script-local items are available.
---
--- @type string
--- @type string|function
vim.o.charconvert = ""
vim.o.ccv = vim.o.charconvert
vim.go.charconvert = vim.o.charconvert
@@ -1125,7 +1125,7 @@ vim.bo.cpt = vim.bo.complete
--- function, a `lambda` or a `Funcref`. See `option-value-function` for
--- more information.
---
--- @type string
--- @type string|function
vim.o.completefunc = ""
vim.o.cfu = vim.o.completefunc
vim.bo.completefunc = vim.o.completefunc
@@ -1791,7 +1791,7 @@ vim.go.dia = vim.go.diffanchors
--- Expression which is evaluated to obtain a diff file (either ed-style
--- or unified-style) from two versions of a file. See `diff-diffexpr`.
---
--- @type string
--- @type string|function
vim.o.diffexpr = ""
vim.o.dex = vim.o.diffexpr
vim.go.diffexpr = vim.o.diffexpr
@@ -2672,7 +2672,7 @@ vim.go.fcs = vim.go.fillchars
--- ```
---
---
--- @type string
--- @type string|function
vim.o.findfunc = ""
vim.o.ffu = vim.o.findfunc
vim.bo.findfunc = vim.o.findfunc
@@ -2745,7 +2745,7 @@ vim.wo.fen = vim.wo.foldenable
--- It is not allowed to change text or jump to another window while
--- evaluating 'foldexpr' `textlock`.
---
--- @type string
--- @type string|function
vim.o.foldexpr = "0"
vim.o.fde = vim.o.foldexpr
vim.wo.foldexpr = vim.o.foldexpr
@@ -2891,7 +2891,7 @@ vim.go.fdo = vim.go.foldopen
--- When set to an empty string, foldtext is disabled, and the line
--- is displayed normally with highlighting and no line wrapping.
---
--- @type string
--- @type string|function
vim.o.foldtext = "foldtext()"
vim.o.fdt = vim.o.foldtext
vim.wo.foldtext = vim.o.foldtext
@@ -2943,7 +2943,7 @@ vim.wo.fdt = vim.wo.foldtext
--- since changing the buffer text is not allowed.
--- This option cannot be set in a modeline when 'modelineexpr' is off.
---
--- @type string
--- @type string|function
vim.o.formatexpr = ""
vim.o.fex = vim.o.formatexpr
vim.bo.formatexpr = vim.o.formatexpr
@@ -3522,7 +3522,7 @@ vim.go.inc = vim.go.include
--- It is not allowed to change text or jump to another window while
--- evaluating 'includeexpr' `textlock`.
---
--- @type string
--- @type string|function
vim.o.includeexpr = ""
vim.o.inex = vim.o.includeexpr
vim.bo.includeexpr = vim.o.includeexpr
@@ -3617,7 +3617,7 @@ vim.go.is = vim.go.incsearch
--- It is not allowed to change text or jump to another window while
--- evaluating 'indentexpr' `textlock`.
---
--- @type string
--- @type string|function
vim.o.indentexpr = ""
vim.o.inde = vim.o.indentexpr
vim.bo.indentexpr = vim.o.indentexpr
@@ -4875,7 +4875,7 @@ vim.wo.nuw = vim.wo.numberwidth
--- This option is usually set by a filetype plugin:
--- `:filetype-plugin-on`
---
--- @type string
--- @type string|function
vim.o.omnifunc = ""
vim.o.ofu = vim.o.omnifunc
vim.bo.omnifunc = vim.o.omnifunc
@@ -4886,7 +4886,7 @@ vim.bo.ofu = vim.bo.omnifunc
--- the name of a function, a `lambda` or a `Funcref`. See
--- `option-value-function` for more information.
---
--- @type string
--- @type string|function
vim.o.operatorfunc = ""
vim.o.opfunc = vim.o.operatorfunc
vim.go.operatorfunc = vim.o.operatorfunc
@@ -4923,7 +4923,7 @@ vim.go.para = vim.go.paragraphs
--- Expression which is evaluated to apply a patch to a file and generate
--- the resulting new version of the file. See `diff-patchexpr`.
---
--- @type string
--- @type string|function
vim.o.patchexpr = ""
vim.o.pex = vim.o.patchexpr
vim.go.patchexpr = vim.o.patchexpr
@@ -5191,7 +5191,7 @@ vim.go.pyx = vim.go.pyxversion
--- It is not allowed to change text or jump to another window while
--- evaluating 'qftf' `textlock`.
---
--- @type string
--- @type string|function
vim.o.quickfixtextfunc = ""
vim.o.qftf = vim.o.quickfixtextfunc
vim.go.quickfixtextfunc = vim.o.quickfixtextfunc
@@ -7324,7 +7324,7 @@ vim.go.tc = vim.go.tagcase
--- `lambda` or a `Funcref`. See `option-value-function` for more
--- information.
---
--- @type string
--- @type string|function
vim.o.tagfunc = ""
vim.o.tfu = vim.o.tagfunc
vim.bo.tagfunc = vim.o.tagfunc
@@ -7492,7 +7492,7 @@ vim.go.tsr = vim.go.thesaurus
--- The value can be the name of a function, a `lambda` or a `Funcref`.
--- See `option-value-function` for more information.
---
--- @type string
--- @type string|function
vim.o.thesaurusfunc = ""
vim.o.tsrfu = vim.o.thesaurusfunc
vim.bo.thesaurusfunc = vim.o.thesaurusfunc

View File

@@ -846,12 +846,12 @@ function lsp._set_defaults(client, bufnr)
if
client:supports_method('textDocument/definition') and is_empty_or_default(bufnr, 'tagfunc')
then
vim.bo[bufnr].tagfunc = 'v:lua.vim.lsp.tagfunc'
vim.bo[bufnr].tagfunc = lsp.tagfunc
end
if
client:supports_method('textDocument/completion') and is_empty_or_default(bufnr, 'omnifunc')
then
vim.bo[bufnr].omnifunc = 'v:lua.vim.lsp.omnifunc'
vim.bo[bufnr].omnifunc = lsp.omnifunc
end
if
client:supports_method('textDocument/rangeFormatting')

View File

@@ -1448,10 +1448,10 @@ end
--- Reset defaults set by `set_defaults`.
--- Must only be called if the last client attached to a buffer exits.
local function reset_defaults(bufnr)
if vim.bo[bufnr].tagfunc == 'v:lua.vim.lsp.tagfunc' then
if vim.bo[bufnr].tagfunc == vim.lsp.tagfunc then
vim.bo[bufnr].tagfunc = nil
end
if vim.bo[bufnr].omnifunc == 'v:lua.vim.lsp.omnifunc' then
if vim.bo[bufnr].omnifunc == vim.lsp.omnifunc then
vim.bo[bufnr].omnifunc = nil
end
if vim.bo[bufnr].formatexpr == 'v:lua.vim.lsp.formatexpr()' then

View File

@@ -639,7 +639,7 @@ function M.edit_query(lang)
local query_buf = api.nvim_win_get_buf(query_win)
vim.b[buf].dev_edit = query_win
vim.bo[query_buf].omnifunc = 'v:lua.vim.treesitter.query.omnifunc'
vim.bo[query_buf].omnifunc = vim.treesitter.query.omnifunc
set_dev_options(query_win, query_buf)
-- Note that omnifunc guesses the language based on the containing folder,

View File

@@ -1131,7 +1131,7 @@ end
--- Use via
---
--- ```lua
--- vim.bo.omnifunc = 'v:lua.vim.treesitter.query.omnifunc'
--- vim.bo.omnifunc = vim.treesitter.query.omnifunc
--- ```
---
--- @param findstart 0|1