aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--runtime/lua/vim/_editor.lua57
-rw-r--r--test/functional/editor/completion_spec.lua7
-rw-r--r--test/functional/lua/command_line_completion_spec.lua37
3 files changed, 84 insertions, 17 deletions
diff --git a/runtime/lua/vim/_editor.lua b/runtime/lua/vim/_editor.lua
index 6a8b23ba40..f0b8587476 100644
--- a/runtime/lua/vim/_editor.lua
+++ b/runtime/lua/vim/_editor.lua
@@ -923,6 +923,39 @@ function vim._expand_pat(pat, 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
if type(final_env) ~= 'table' then
return {}, 0
@@ -953,16 +986,7 @@ function vim._expand_pat(pat, env)
key = result
end
- local field = rawget(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
+ final_env = safe_tbl_get(final_env, key)
if not final_env then
return {}, 0
@@ -992,17 +1016,20 @@ function vim._expand_pat(pat, env)
if type(final_env) == 'table' then
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
+
local mt = getmetatable(final_env)
if mt and type(mt.__index) == 'table' then
insert_keys(mt.__index)
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)
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(
diff --git a/test/functional/editor/completion_spec.lua b/test/functional/editor/completion_spec.lua
index 106e0df347..3e5d0e48e8 100644
--- a/test/functional/editor/completion_spec.lua
+++ b/test/functional/editor/completion_spec.lua
@@ -928,6 +928,13 @@ describe('completion', function()
command('set wildoptions+=fuzzy')
eq({ 'vim' }, fn.getcompletion('vi', 'lua'))
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)
it('cmdline completion supports various string options', function()
diff --git a/test/functional/lua/command_line_completion_spec.lua b/test/functional/lua/command_line_completion_spec.lua
index 7dac7448e9..ee3d0325e1 100644
--- a/test/functional/lua/command_line_completion_spec.lua
+++ b/test/functional/lua/command_line_completion_spec.lua
@@ -24,6 +24,23 @@ describe('nlua_expand_pat', function()
it('returns empty table when nothing matches', function()
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)
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()
eq({ { 'inspect', 'inspect_pos' }, 4 }, get_completions('vim.inspec'))
-
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'))
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()
eq(
{ { 'bar' }, 4 },