mirror of
https://github.com/neovim/neovim
synced 2025-07-16 17:21:49 +00:00
fix(cmdline): cmdline completion of _defer_require() modules #33007
Problem:
`:lua vim.lsp.c<tab>` does not list vim.lsp.completion in the completion
list after 24cea4c7f7
.
Solution:
- Always include `vim.lsp._submodule` keys in candidates.
- Fixes `vim.lsp.c<tab>` -> `vim.lsp.completion`.
- Eager-load `vim.lsp.completion` to get its completion.
- Fixes `vim.lsp.completion.g<tab>` -> `vim.lsp.completion.get`.
This commit is contained in:
@ -923,6 +923,39 @@ function vim._expand_pat(pat, env)
|
|||||||
|
|
||||||
local final_env = env
|
local final_env = env
|
||||||
|
|
||||||
|
--- @private
|
||||||
|
---
|
||||||
|
--- Allows submodules to be defined on a `vim.<module>` table without eager-loading the module.
|
||||||
|
---
|
||||||
|
--- Cmdline completion (`:lua vim.lsp.c<tab>`) accesses `vim.lsp._submodules` when no other candidates.
|
||||||
|
--- Cmdline completion (`:lua vim.lsp.completion.g<tab>`) will eager-load the module anyway. #33007
|
||||||
|
---
|
||||||
|
--- @param m table
|
||||||
|
--- @param k string
|
||||||
|
--- @return any
|
||||||
|
local function safe_tbl_get(m, k)
|
||||||
|
local val = rawget(m, k)
|
||||||
|
if val ~= nil then
|
||||||
|
return val
|
||||||
|
end
|
||||||
|
|
||||||
|
local mt = getmetatable(m)
|
||||||
|
if not mt then
|
||||||
|
return m == vim and vim._extra[k] or nil
|
||||||
|
end
|
||||||
|
|
||||||
|
-- use mt.__index, _submodules as fallback
|
||||||
|
if type(mt.__index) == 'table' then
|
||||||
|
return rawget(mt.__index, k)
|
||||||
|
end
|
||||||
|
|
||||||
|
local sub = rawget(m, '_submodules')
|
||||||
|
if sub and type(sub) == 'table' and rawget(sub, k) then
|
||||||
|
-- Access the module to force _defer_require() to load the module.
|
||||||
|
return m[k]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
for _, part in ipairs(parts) do
|
for _, part in ipairs(parts) do
|
||||||
if type(final_env) ~= 'table' then
|
if type(final_env) ~= 'table' then
|
||||||
return {}, 0
|
return {}, 0
|
||||||
@ -953,16 +986,7 @@ function vim._expand_pat(pat, env)
|
|||||||
|
|
||||||
key = result
|
key = result
|
||||||
end
|
end
|
||||||
local field = rawget(final_env, key)
|
final_env = safe_tbl_get(final_env, key)
|
||||||
if field == nil then
|
|
||||||
local mt = getmetatable(final_env)
|
|
||||||
if mt and type(mt.__index) == 'table' then
|
|
||||||
field = rawget(mt.__index, key)
|
|
||||||
elseif final_env == vim and (vim._submodules[key] or vim._extra[key]) then
|
|
||||||
field = vim[key] --- @type any
|
|
||||||
end
|
|
||||||
end
|
|
||||||
final_env = field
|
|
||||||
|
|
||||||
if not final_env then
|
if not final_env then
|
||||||
return {}, 0
|
return {}, 0
|
||||||
@ -992,17 +1016,20 @@ function vim._expand_pat(pat, env)
|
|||||||
|
|
||||||
if type(final_env) == 'table' then
|
if type(final_env) == 'table' then
|
||||||
insert_keys(final_env)
|
insert_keys(final_env)
|
||||||
|
local sub = rawget(final_env, '_submodules')
|
||||||
|
if type(sub) == 'table' then
|
||||||
|
insert_keys(sub)
|
||||||
|
end
|
||||||
|
if final_env == vim then
|
||||||
|
insert_keys(vim._extra)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local mt = getmetatable(final_env)
|
local mt = getmetatable(final_env)
|
||||||
if mt and type(mt.__index) == 'table' then
|
if mt and type(mt.__index) == 'table' then
|
||||||
insert_keys(mt.__index)
|
insert_keys(mt.__index)
|
||||||
end
|
end
|
||||||
|
|
||||||
if final_env == vim then
|
|
||||||
insert_keys(vim._submodules)
|
|
||||||
insert_keys(vim._extra)
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Completion for dict accessors (special vim variables and vim.fn)
|
-- Completion for dict accessors (special vim variables and vim.fn)
|
||||||
if mt and vim.tbl_contains({ vim.g, vim.t, vim.w, vim.b, vim.v, vim.fn }, final_env) then
|
if mt and vim.tbl_contains({ vim.g, vim.t, vim.w, vim.b, vim.v, vim.fn }, final_env) then
|
||||||
local prefix, type = unpack(
|
local prefix, type = unpack(
|
||||||
|
@ -928,6 +928,13 @@ describe('completion', function()
|
|||||||
command('set wildoptions+=fuzzy')
|
command('set wildoptions+=fuzzy')
|
||||||
eq({ 'vim' }, fn.getcompletion('vi', 'lua'))
|
eq({ 'vim' }, fn.getcompletion('vi', 'lua'))
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
it('completes _defer_require() modules', function()
|
||||||
|
-- vim.lsp.c<tab> -> vim.lsp.completion
|
||||||
|
ok(vim.tbl_contains(fn.getcompletion('lua vim.lsp.c', 'cmdline'), 'completion'))
|
||||||
|
-- vim.lsp.completion.g<tab> -> vim.lsp.completion.get
|
||||||
|
ok(vim.tbl_contains(fn.getcompletion('lua vim.lsp.completion.g', 'cmdline'), 'get'))
|
||||||
|
end)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
it('cmdline completion supports various string options', function()
|
it('cmdline completion supports various string options', function()
|
||||||
|
@ -24,6 +24,23 @@ describe('nlua_expand_pat', function()
|
|||||||
|
|
||||||
it('returns empty table when nothing matches', function()
|
it('returns empty table when nothing matches', function()
|
||||||
eq({ {}, 0 }, get_completions('foo', { bar = true }))
|
eq({ {}, 0 }, get_completions('foo', { bar = true }))
|
||||||
|
|
||||||
|
-- can access non-exist field
|
||||||
|
for _, m in ipairs {
|
||||||
|
'vim.',
|
||||||
|
'vim.lsp.',
|
||||||
|
'vim.treesitter.',
|
||||||
|
'vim.deepcopy.',
|
||||||
|
'vim.fn.',
|
||||||
|
'vim.api.',
|
||||||
|
'vim.o.',
|
||||||
|
'vim.b.',
|
||||||
|
} do
|
||||||
|
eq({ {}, m:len() }, get_completions(m .. 'foo'))
|
||||||
|
eq({ {}, 0 }, get_completions(m .. 'foo.'))
|
||||||
|
eq({ {}, 0 }, get_completions(m .. 'foo.bar'))
|
||||||
|
eq({ {}, 0 }, get_completions(m .. 'foo.bar.'))
|
||||||
|
end
|
||||||
end)
|
end)
|
||||||
|
|
||||||
it('returns nice completions with function call prefix', function()
|
it('returns nice completions with function call prefix', function()
|
||||||
@ -99,12 +116,28 @@ describe('nlua_expand_pat', function()
|
|||||||
|
|
||||||
it('with lazy submodules of "vim" global', function()
|
it('with lazy submodules of "vim" global', function()
|
||||||
eq({ { 'inspect', 'inspect_pos' }, 4 }, get_completions('vim.inspec'))
|
eq({ { 'inspect', 'inspect_pos' }, 4 }, get_completions('vim.inspec'))
|
||||||
|
|
||||||
eq({ { 'treesitter' }, 4 }, get_completions('vim.treesi'))
|
eq({ { 'treesitter' }, 4 }, get_completions('vim.treesi'))
|
||||||
|
eq({ { 'dev' }, 15 }, get_completions('vim.treesitter.de'))
|
||||||
|
eq({ { 'edit_query' }, 19 }, get_completions('vim.treesitter.dev.edit_'))
|
||||||
eq({ { 'set' }, 11 }, get_completions('vim.keymap.se'))
|
eq({ { 'set' }, 11 }, get_completions('vim.keymap.se'))
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
it('include keys in mt.__index and ._submodules', function()
|
||||||
|
eq(
|
||||||
|
{ { 'bar1', 'bar2', 'bar3' }, 4 },
|
||||||
|
exec_lua(function() -- metatable cannot be serialized
|
||||||
|
return {
|
||||||
|
vim._expand_pat('foo.', {
|
||||||
|
foo = setmetatable(
|
||||||
|
{ bar1 = true, _submodules = { bar2 = true } },
|
||||||
|
{ __index = { bar3 = true } }
|
||||||
|
),
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
end)
|
||||||
|
)
|
||||||
|
end)
|
||||||
|
|
||||||
it('excludes private fields after "."', function()
|
it('excludes private fields after "."', function()
|
||||||
eq(
|
eq(
|
||||||
{ { 'bar' }, 4 },
|
{ { 'bar' }, 4 },
|
||||||
|
Reference in New Issue
Block a user