aboutsummaryrefslogtreecommitdiff
path: root/runtime/lua/vim/_editor.lua
diff options
context:
space:
mode:
authorJongwook Choi <wookayin@gmail.com>2024-10-04 09:48:31 -0400
committerGitHub <noreply@github.com>2024-10-04 06:48:31 -0700
commitd5ae5c84e94a2b15374ee0c7e2f4444c161a8a63 (patch)
treec37bd8000fe2e27199c2263fc9d9c0357d3f2067 /runtime/lua/vim/_editor.lua
parent86c5c8724bd85154c4e94474d1d9a0b01e296028 (diff)
downloadrneovim-d5ae5c84e94a2b15374ee0c7e2f4444c161a8a63.tar.gz
rneovim-d5ae5c84e94a2b15374ee0c7e2f4444c161a8a63.tar.bz2
rneovim-d5ae5c84e94a2b15374ee0c7e2f4444c161a8a63.zip
feat(lua): completion for vim.fn, vim.v, vim.o #30472
Problem: Lua accessors for - global, local, and special variables (`vim.{g,t,w,b,v}.*`), and - options (`vim.{o,bo,wo,opt,opt_local,opt_global}.*`), do not have command-line completion, unlike their vimscript counterparts (e.g., `g:`, `b:`, `:set`, `:setlocal`, `:call <fn>`, etc.). Completion for vimscript functions (`vim.fn.*`) is incomplete and does not list all the available functions. Solution: Implement completion for vimscript function, variable and option accessors in `vim._expand_pat` through: - `getcompletion()` for variable and vimscript function accessors, and - `nvim_get_all_options_info()` for option accessors. Note/Remark: - Short names for options are yet to be implemented. - Completions for accessors with handles (e.g. `vim.b[0]`, `vim.wo[0]`) are also yet to be implemented, and are left as future work, which involves some refactoring of options. - For performance reasons, we may want to introduce caching for completing options, but this is not considered at this time since the number of the available options is not very big (only ~350) and Lua completion for option accessors appears to be pretty fast. - Can we have a more "general" framework for customizing completions? In the future, we may want to improve the implementation by moving the core logic for generating completion candidates to each accessor (or its metatable) or through some central interface, rather than writing all the accessor-specific completion implementations in a single function: `vim._expand_pat`.
Diffstat (limited to 'runtime/lua/vim/_editor.lua')
-rw-r--r--runtime/lua/vim/_editor.lua64
1 files changed, 62 insertions, 2 deletions
diff --git a/runtime/lua/vim/_editor.lua b/runtime/lua/vim/_editor.lua
index bdca97cfb8..2e829578a7 100644
--- a/runtime/lua/vim/_editor.lua
+++ b/runtime/lua/vim/_editor.lua
@@ -787,7 +787,7 @@ function vim._expand_pat(pat, 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]
+ field = vim[key] --- @type any
end
end
final_env = field
@@ -798,14 +798,24 @@ function vim._expand_pat(pat, env)
end
local keys = {} --- @type table<string,true>
+
--- @param obj table<any,any>
local function insert_keys(obj)
for k, _ in pairs(obj) do
- if type(k) == 'string' and string.sub(k, 1, string.len(match_part)) == match_part then
+ if
+ type(k) == 'string'
+ and string.sub(k, 1, string.len(match_part)) == match_part
+ and k:match('^[_%w]+$') ~= nil -- filter out invalid identifiers for field, e.g. 'foo#bar'
+ then
keys[k] = true
end
end
end
+ ---@param acc table<string,any>
+ local function _fold_to_map(acc, k, v)
+ acc[k] = (v or true)
+ return acc
+ end
if type(final_env) == 'table' then
insert_keys(final_env)
@@ -814,11 +824,61 @@ function vim._expand_pat(pat, 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(
+ vim.fn == final_env and { '', 'function' }
+ or vim.g == final_env and { 'g:', 'var' }
+ or vim.t == final_env and { 't:', 'var' }
+ or vim.w == final_env and { 'w:', 'var' }
+ or vim.b == final_env and { 'b:', 'var' }
+ or vim.v == final_env and { 'v:', 'var' }
+ or { nil, nil }
+ )
+ assert(prefix, "Can't resolve final_env")
+ local vars = vim.fn.getcompletion(prefix .. match_part, type) --- @type string[]
+ insert_keys(vim
+ .iter(vars)
+ :map(function(s) ---@param s string
+ s = s:gsub('[()]+$', '') -- strip '(' and ')' for function completions
+ return s:sub(#prefix + 1) -- strip the prefix, e.g., 'g:foo' => 'foo'
+ end)
+ :fold({}, _fold_to_map))
+ end
+
+ -- Completion for option accessors (full names only)
+ if
+ mt
+ and vim.tbl_contains(
+ { vim.o, vim.go, vim.bo, vim.wo, vim.opt, vim.opt_local, vim.opt_global },
+ final_env
+ )
+ then
+ --- @type fun(option_name: string, option: vim.api.keyset.get_option_info): boolean
+ local filter = function(_, _)
+ return true
+ end
+ if vim.bo == final_env then
+ filter = function(_, option)
+ return option.scope == 'buf'
+ end
+ elseif vim.wo == final_env then
+ filter = function(_, option)
+ return option.scope == 'win'
+ end
+ end
+
+ --- @type table<string, vim.api.keyset.get_option_info>
+ local options = vim.api.nvim_get_all_options_info()
+ insert_keys(vim.iter(options):filter(filter):fold({}, _fold_to_map))
+ end
+
keys = vim.tbl_keys(keys)
table.sort(keys)