mirror of
https://github.com/neovim/neovim.git
synced 2026-08-04 22:58:50 +00:00
fix(options): ":set foo?" for Lua callbacks #41100
Problem:
`:set foo=<tab>` and `:set foo?` for func/expr options set to a Lua
function, always displays "v:lua".
The existing "<Lua N: file:line>" form was avoided because the ref id
N changes on every read of the same option.
Solution:
- Don't attempt to tab-complete Lua functions.
- Show "<Lua file:line>" (or "<Lua>" if source file is unknown).
Example:
:set operatorfunc?
operatorfunc=<Lua ~/.config/nvim/init.lua:42>
This commit is contained in:
@@ -1898,7 +1898,7 @@ void callback_copy(Callback *dest, Callback *src)
|
||||
char *callback_to_string(Callback *cb, Arena *arena)
|
||||
{
|
||||
if (cb->type == kCallbackLua) {
|
||||
return nlua_funcref_str(cb->data.luaref, arena);
|
||||
return nlua_funcref_str(cb->data.luaref, arena, true);
|
||||
}
|
||||
|
||||
const size_t msglen = 100;
|
||||
|
||||
@@ -3303,7 +3303,7 @@ typval_T opt_to_tv(Object value, bool numbool)
|
||||
case kObjectTypeLuaRef:
|
||||
// Lua callback option (e.g. 'operatorfunc'): show a human-readable hint (same as `:map` does).
|
||||
rettv.v_type = VAR_STRING;
|
||||
rettv.vval.v_string = nlua_funcref_str(value.data.luaref, NULL);
|
||||
rettv.vval.v_string = nlua_funcref_str(value.data.luaref, NULL, true);
|
||||
break;
|
||||
default:
|
||||
abort(); // Should never happen.
|
||||
|
||||
@@ -2453,7 +2453,7 @@ int nlua_do_ucmd(ucmd_T *cmd, exarg_T *eap, bool preview)
|
||||
/// String representation of a Lua function reference
|
||||
///
|
||||
/// @return Allocated string
|
||||
char *nlua_funcref_str(LuaRef ref, Arena *arena)
|
||||
char *nlua_funcref_str(LuaRef ref, Arena *arena, bool show_ref)
|
||||
{
|
||||
lua_State *const lstate = global_lstate;
|
||||
|
||||
@@ -2469,13 +2469,14 @@ char *nlua_funcref_str(LuaRef ref, Arena *arena)
|
||||
lua_Debug ar;
|
||||
if (lua_getinfo(lstate, ">S", &ar) && *ar.source == '@' && ar.linedefined >= 0) {
|
||||
char *src = home_replace_save(NULL, ar.source + 1);
|
||||
String str = arena_printf(arena, "<Lua %d: %s:%d>", ref, src, ar.linedefined);
|
||||
String str = show_ref ? arena_printf(arena, "<Lua %d: %s:%d>", ref, src, ar.linedefined)
|
||||
: arena_printf(arena, "<Lua %s:%d>", src, ar.linedefined);
|
||||
xfree(src);
|
||||
return str.data;
|
||||
}
|
||||
|
||||
plain: {}
|
||||
return arena_printf(arena, "<Lua %d>", ref).data;
|
||||
return show_ref ? arena_printf(arena, "<Lua %d>", ref).data : arena_printf(arena, "<Lua>").data;
|
||||
}
|
||||
|
||||
/// Execute the vim._core.defaults module to set up default mappings and autocommands
|
||||
|
||||
@@ -255,7 +255,7 @@ static void showmap(mapblock_T *mp, bool local)
|
||||
// Use false below if we only want things like <Up> to show up as such on
|
||||
// the rhs, and not M-x etc, true gets both -- webb
|
||||
if (mp->m_luaref != LUA_NOREF) {
|
||||
char *str = nlua_funcref_str(mp->m_luaref, NULL);
|
||||
char *str = nlua_funcref_str(mp->m_luaref, NULL, true);
|
||||
msg_puts_hl(str, HLF_8, false);
|
||||
xfree(str);
|
||||
} else if (mp->m_str[0] == NUL) {
|
||||
@@ -2206,7 +2206,7 @@ static void get_maparg(typval_T *argvars, typval_T *rettv, int exact)
|
||||
rettv->vval.v_string = str2special_save(rhs, false, false);
|
||||
}
|
||||
} else if (rhs_lua != LUA_NOREF) {
|
||||
rettv->vval.v_string = nlua_funcref_str(mp->m_luaref, NULL);
|
||||
rettv->vval.v_string = nlua_funcref_str(mp->m_luaref, NULL, true);
|
||||
}
|
||||
} else {
|
||||
// Return a dictionary.
|
||||
|
||||
@@ -212,7 +212,7 @@ void mpack_object_inner(Object *current, Object *container, size_t container_idx
|
||||
// see nlua_pop_Object.
|
||||
|
||||
// Human-readable hint string (`:map`-style "<Lua …>" string).
|
||||
char *repr = nlua_funcref_str(current->data.luaref, NULL);
|
||||
char *repr = nlua_funcref_str(current->data.luaref, NULL, true);
|
||||
api_free_luaref(current->data.luaref);
|
||||
current->data.luaref = LUA_NOREF;
|
||||
mpack_str(cstr_as_string(repr), packer);
|
||||
|
||||
@@ -3448,8 +3448,8 @@ static ObjectType optval_type(Object o)
|
||||
}
|
||||
}
|
||||
|
||||
/// True for a "callback option" (varp stores `Callback`): func option (kOptFlagFunc, e.g.
|
||||
/// 'operatorfunc') or expr option (kOptFlagExpr, e.g. 'foldexpr').
|
||||
/// True for a "callback option" (varp stores `Callback`): func option (e.g. 'operatorfunc') or expr
|
||||
/// option (e.g. 'foldexpr').
|
||||
static bool is_callback_option(OptIndex opt_idx)
|
||||
{
|
||||
return (options[opt_idx].flags & (kOptFlagFunc | kOptFlagExpr)) != 0;
|
||||
@@ -3472,7 +3472,7 @@ Object optval_own(OptIndex opt_idx, Object value)
|
||||
static Object optval_snapshot(Object value)
|
||||
{
|
||||
if (value.type == kObjectTypeLuaRef) {
|
||||
return CSTR_AS_OBJ(nlua_funcref_str(value.data.luaref, NULL));
|
||||
return CSTR_AS_OBJ(nlua_funcref_str(value.data.luaref, NULL, true));
|
||||
}
|
||||
return copy_object(value, NULL);
|
||||
}
|
||||
@@ -3486,14 +3486,14 @@ static Object optval_snapshot_varp(OptIndex opt_idx, void *varp)
|
||||
return snapshot;
|
||||
}
|
||||
|
||||
/// Converts a callback option's stored Callback to an option value Object.
|
||||
/// Always OWNED (a copied name/expr string, or a fresh LuaRef): free with optval_free().
|
||||
/// Converts a callback option's stored Callback to an optval Object.
|
||||
/// Always OWNED (a copied name/expr string, or a new LuaRef): free with optval_free().
|
||||
///
|
||||
/// - Lua callback => `LuaRef`
|
||||
/// - named funcref => name
|
||||
/// - partial => name
|
||||
/// - expr option => expression string
|
||||
/// - an unset callback => empty string
|
||||
/// - Lua callback => `LuaRef`
|
||||
/// - named funcref => name
|
||||
/// - partial => name
|
||||
/// - expr option => expression string
|
||||
/// - unset callback => empty string
|
||||
static Object opt_from_callback(Callback *cb)
|
||||
{
|
||||
switch (cb->type) {
|
||||
@@ -3520,10 +3520,10 @@ static Object opt_from_callback(Callback *cb)
|
||||
/// Builds a callback option's Callback from an option value Object. Borrows `value` (copies its
|
||||
/// string/ref; the caller retains ownership).
|
||||
///
|
||||
/// - `LuaRef` ("func" or "expr" option) => Lua callback
|
||||
/// - func option with `is_expr=false` => string parsed as a function name or lambda
|
||||
/// - expr option with non-empty string => expression string (`<SID>`/`s:` resolves to `<SNR>`)
|
||||
/// - anything else (empty string, `Unset`) => `CALLBACK_NONE`
|
||||
/// - `LuaRef` ("func"/"expr" options) => Lua callback
|
||||
/// - func option with `is_expr=false` => string parsed as a function name or lambda
|
||||
/// - expr option with non-empty string => expression string (`<SID>`/`s:` resolves to `<SNR>`)
|
||||
/// - other (empty string, `Unset`) => `CALLBACK_NONE`
|
||||
static Callback opt_to_callback(Object value, bool is_expr)
|
||||
{
|
||||
Callback cb = CALLBACK_NONE;
|
||||
@@ -3653,7 +3653,7 @@ static char *optval_to_cstr(Object o, bool quote)
|
||||
return buf;
|
||||
}
|
||||
case kObjectTypeLuaRef:
|
||||
return xstrdup("v:lua"); // A Lua callback has no name; show a stable handle.
|
||||
return nlua_funcref_str(o.data.luaref, NULL, false); // show_ref=false: ref changes every time.
|
||||
default:
|
||||
abort(); // Should not happen.
|
||||
}
|
||||
@@ -6467,7 +6467,7 @@ int ExpandOldSetting(int *numMatches, char ***matches)
|
||||
char *var = NULL;
|
||||
|
||||
*numMatches = 0;
|
||||
*matches = xmalloc(sizeof(char *));
|
||||
*matches = NULL;
|
||||
|
||||
// For a terminal key code expand_option_idx is kOptInvalid.
|
||||
if (expand_option_idx == kOptInvalid) {
|
||||
@@ -6475,6 +6475,14 @@ int ExpandOldSetting(int *numMatches, char ***matches)
|
||||
}
|
||||
|
||||
if (expand_option_idx != kOptInvalid) {
|
||||
Object o = opt_from_varp(expand_option_idx,
|
||||
get_varp_scope(&options[expand_option_idx], expand_option_flags));
|
||||
bool is_luafn = o.type == kObjectTypeLuaRef;
|
||||
optval_free_read(expand_option_idx, o);
|
||||
if (is_luafn) {
|
||||
// Special case: ":set x=<tab>" cannot meaningfully complete a Lua function.
|
||||
return OK;
|
||||
}
|
||||
// Put string of option value in NameBuff.
|
||||
optval_fmt(expand_option_idx, expand_option_flags);
|
||||
var = NameBuff;
|
||||
@@ -6484,6 +6492,7 @@ int ExpandOldSetting(int *numMatches, char ***matches)
|
||||
|
||||
char *buf = escape_option_str_cmdline(var);
|
||||
|
||||
*matches = xmalloc(sizeof(char *));
|
||||
(*matches)[0] = buf;
|
||||
*numMatches = 1;
|
||||
return OK;
|
||||
|
||||
@@ -613,7 +613,7 @@ static void uc_list(char *name, size_t name_len)
|
||||
msg_outtrans(IObuff, 0, false);
|
||||
|
||||
if (cmd->uc_luaref != LUA_NOREF) {
|
||||
char *fn = nlua_funcref_str(cmd->uc_luaref, NULL);
|
||||
char *fn = nlua_funcref_str(cmd->uc_luaref, NULL, true);
|
||||
msg_puts_hl(fn, HLF_8, false);
|
||||
xfree(fn);
|
||||
// put the description on a new line
|
||||
|
||||
@@ -218,7 +218,7 @@ describe('callback (func/expr) options', function()
|
||||
eq('GoodFunc', eval('&operatorfunc'))
|
||||
end)
|
||||
|
||||
it('with ":set all"', function()
|
||||
it(':set', function()
|
||||
command('set operatorfunc=SomeFunc')
|
||||
command('set quickfixtextfunc={\\ d\\ ->\\ []\\ }')
|
||||
exec_lua(function()
|
||||
@@ -227,6 +227,20 @@ describe('callback (func/expr) options', function()
|
||||
end)
|
||||
command('set all')
|
||||
n.assert_alive()
|
||||
|
||||
-- ":set opt?" shows funcref/lambda name, or "<Lua …>".
|
||||
local function set_q(opt)
|
||||
return n.api.nvim_exec2(('set %s?'):format(opt), { output = true }).output
|
||||
end
|
||||
eq(' operatorfunc=SomeFunc', set_q('operatorfunc'))
|
||||
matches("^ quickfixtextfunc=function%('<lambda>%d+'%)$", set_q('quickfixtextfunc'))
|
||||
matches('^ omnifunc=<Lua .+:%d+>$', set_q('omnifunc'))
|
||||
matches('^ foldexpr=<Lua .+:%d+>$', set_q('foldexpr'))
|
||||
eq(set_q('omnifunc'), set_q('omnifunc')) -- Stable: no per-read ref id.
|
||||
|
||||
-- ":set opt=<Tab>" completes a funcref value, but not a Lua function.
|
||||
eq({ 'SomeFunc' }, n.fn.getcompletion('set operatorfunc=', 'cmdline'))
|
||||
eq({}, n.fn.getcompletion('set omnifunc=', 'cmdline'))
|
||||
end)
|
||||
|
||||
it("expr option ('foldexpr') accepts string, reads back string", function()
|
||||
|
||||
Reference in New Issue
Block a user