mirror of
https://github.com/neovim/neovim.git
synced 2024-12-19 18:55:14 -07:00
d5ae5c84e9
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`.
335 lines
8.8 KiB
Lua
335 lines
8.8 KiB
Lua
local t = require('test.testutil')
|
|
local n = require('test.functional.testnvim')()
|
|
|
|
local clear = n.clear
|
|
local eq = t.eq
|
|
local exec_lua = n.exec_lua
|
|
|
|
--- @return { [1]: string[], [2]: integer }
|
|
local get_completions = function(input, env)
|
|
return exec_lua('return { vim._expand_pat(...) }', input, env)
|
|
end
|
|
|
|
--- @return { [1]: string[], [2]: integer }
|
|
local get_compl_parts = function(parts)
|
|
return exec_lua('return { vim._expand_pat_get_parts(...) }', parts)
|
|
end
|
|
|
|
before_each(clear)
|
|
|
|
describe('nlua_expand_pat', function()
|
|
it('should complete exact matches', function()
|
|
eq({ { 'exact' }, 0 }, get_completions('exact', { exact = true }))
|
|
end)
|
|
|
|
it('should return empty table when nothing matches', function()
|
|
eq({ {}, 0 }, get_completions('foo', { bar = true }))
|
|
end)
|
|
|
|
it('should return nice completions with function call prefix', function()
|
|
eq({ { 'FOO' }, 6 }, get_completions('print(F', { FOO = true, bawr = true }))
|
|
end)
|
|
|
|
it('should return keys for nested dictionaries', function()
|
|
eq(
|
|
{ {
|
|
'nvim_buf_set_lines',
|
|
}, 8 },
|
|
get_completions('vim.api.nvim_buf_', {
|
|
vim = {
|
|
api = {
|
|
nvim_buf_set_lines = true,
|
|
nvim_win_doesnt_match = true,
|
|
},
|
|
other_key = true,
|
|
},
|
|
})
|
|
)
|
|
end)
|
|
|
|
it('it should work with colons', function()
|
|
eq(
|
|
{ {
|
|
'bawr',
|
|
'baz',
|
|
}, 8 },
|
|
get_completions('MyClass:b', {
|
|
MyClass = {
|
|
baz = true,
|
|
bawr = true,
|
|
foo = false,
|
|
},
|
|
})
|
|
)
|
|
end)
|
|
|
|
it('should return keys for string reffed dictionaries', function()
|
|
eq(
|
|
{ {
|
|
'nvim_buf_set_lines',
|
|
}, 11 },
|
|
get_completions('vim["api"].nvim_buf_', {
|
|
vim = {
|
|
api = {
|
|
nvim_buf_set_lines = true,
|
|
nvim_win_doesnt_match = true,
|
|
},
|
|
other_key = true,
|
|
},
|
|
})
|
|
)
|
|
end)
|
|
|
|
it('should return keys for string reffed dictionaries', function()
|
|
eq(
|
|
{ {
|
|
'nvim_buf_set_lines',
|
|
}, 21 },
|
|
get_completions('vim["nested"]["api"].nvim_buf_', {
|
|
vim = {
|
|
nested = {
|
|
api = {
|
|
nvim_buf_set_lines = true,
|
|
nvim_win_doesnt_match = true,
|
|
},
|
|
},
|
|
other_key = true,
|
|
},
|
|
})
|
|
)
|
|
end)
|
|
|
|
it('should work with lazy submodules of "vim" global', function()
|
|
eq({ { 'inspect', 'inspect_pos' }, 4 }, get_completions('vim.inspec'))
|
|
|
|
eq({ { 'treesitter' }, 4 }, get_completions('vim.treesi'))
|
|
|
|
eq({ { 'set' }, 11 }, get_completions('vim.keymap.se'))
|
|
end)
|
|
|
|
it('should be able to interpolate globals', function()
|
|
eq(
|
|
{ {
|
|
'nvim_buf_set_lines',
|
|
}, 12 },
|
|
get_completions('vim[MY_VAR].nvim_buf_', {
|
|
MY_VAR = 'api',
|
|
vim = {
|
|
api = {
|
|
nvim_buf_set_lines = true,
|
|
nvim_win_doesnt_match = true,
|
|
},
|
|
other_key = true,
|
|
},
|
|
})
|
|
)
|
|
end)
|
|
|
|
describe('should complete vim.fn', function()
|
|
it('correctly works for simple completion', function()
|
|
local actual = get_completions('vim.fn.did')
|
|
local expected = {
|
|
{ 'did_filetype' },
|
|
#'vim.fn.',
|
|
}
|
|
eq(expected, actual)
|
|
end)
|
|
it('should not suggest items with #', function()
|
|
exec_lua [[
|
|
-- ensure remote#host#... functions exist
|
|
vim.cmd [=[
|
|
runtime! autoload/remote/host.vim
|
|
]=]
|
|
-- make a dummy call to ensure vim.fn contains an entry: remote#host#...
|
|
vim.fn['remote#host#IsRunning']('python3')
|
|
]]
|
|
local actual = get_completions('vim.fn.remo')
|
|
local expected = {
|
|
{ 'remove' }, -- there should be no completion "remote#host#..."
|
|
#'vim.fn.',
|
|
}
|
|
eq(expected, actual)
|
|
end)
|
|
end)
|
|
|
|
describe('should complete for variable accessors for', function()
|
|
it('vim.v', function()
|
|
local actual = get_completions('vim.v.t_')
|
|
local expected = {
|
|
{ 't_blob', 't_bool', 't_dict', 't_float', 't_func', 't_list', 't_number', 't_string' },
|
|
#'vim.v.',
|
|
}
|
|
eq(expected, actual)
|
|
end)
|
|
|
|
it('vim.g', function()
|
|
exec_lua [[
|
|
vim.cmd [=[
|
|
let g:nlua_foo = 'completion'
|
|
let g:nlua_foo_bar = 'completion'
|
|
let g:nlua_foo#bar = 'nocompletion' " should be excluded from lua completion
|
|
]=]
|
|
]]
|
|
local actual = get_completions('vim.g.nlua')
|
|
local expected = {
|
|
{ 'nlua_foo', 'nlua_foo_bar' },
|
|
#'vim.g.',
|
|
}
|
|
eq(expected, actual)
|
|
end)
|
|
|
|
it('vim.b', function()
|
|
exec_lua [[
|
|
vim.b.nlua_foo_buf = 'bar'
|
|
vim.b.some_other_vars = 'bar'
|
|
]]
|
|
local actual = get_completions('vim.b.nlua')
|
|
local expected = {
|
|
{ 'nlua_foo_buf' },
|
|
#'vim.b.',
|
|
}
|
|
eq(expected, actual)
|
|
end)
|
|
|
|
it('vim.w', function()
|
|
exec_lua [[
|
|
vim.w.nlua_win_var = 42
|
|
]]
|
|
local actual = get_completions('vim.w.nlua')
|
|
local expected = {
|
|
{ 'nlua_win_var' },
|
|
#'vim.w.',
|
|
}
|
|
eq(expected, actual)
|
|
end)
|
|
|
|
it('vim.t', function()
|
|
exec_lua [[
|
|
vim.t.nlua_tab_var = 42
|
|
]]
|
|
local actual = get_completions('vim.t.')
|
|
local expected = {
|
|
{ 'nlua_tab_var' },
|
|
#'vim.t.',
|
|
}
|
|
eq(expected, actual)
|
|
end)
|
|
end)
|
|
|
|
describe('should complete for option accessors for', function()
|
|
-- for { vim.o, vim.go, vim.opt, vim.opt_local, vim.opt_global }
|
|
local test_opt = function(accessor)
|
|
do
|
|
local actual = get_completions(accessor .. '.file')
|
|
local expected = {
|
|
'fileencoding',
|
|
'fileencodings',
|
|
'fileformat',
|
|
'fileformats',
|
|
'fileignorecase',
|
|
'filetype',
|
|
}
|
|
eq({ expected, #accessor + 1 }, actual, accessor .. '.file')
|
|
end
|
|
do
|
|
local actual = get_completions(accessor .. '.winh')
|
|
local expected = {
|
|
'winheight',
|
|
'winhighlight',
|
|
}
|
|
eq({ expected, #accessor + 1 }, actual, accessor .. '.winh')
|
|
end
|
|
end
|
|
|
|
test_opt('vim.o')
|
|
test_opt('vim.go')
|
|
test_opt('vim.opt')
|
|
test_opt('vim.opt_local')
|
|
test_opt('vim.opt_global')
|
|
|
|
it('vim.o, suggesting all the known options', function()
|
|
local completions = get_completions('vim.o.')[1] ---@type string[]
|
|
eq(
|
|
exec_lua [[
|
|
return vim.tbl_count(vim.api.nvim_get_all_options_info())
|
|
]],
|
|
#completions
|
|
)
|
|
end)
|
|
|
|
it('vim.bo', function()
|
|
do
|
|
local actual = get_completions('vim.bo.file')
|
|
local compls = {
|
|
-- should contain buffer options only
|
|
'fileencoding',
|
|
'fileformat',
|
|
'filetype',
|
|
}
|
|
eq({ compls, #'vim.bo.' }, actual)
|
|
end
|
|
do
|
|
local actual = get_completions('vim.bo.winh')
|
|
local compls = {}
|
|
eq({ compls, #'vim.bo.' }, actual)
|
|
end
|
|
end)
|
|
|
|
it('vim.wo', function()
|
|
do
|
|
local actual = get_completions('vim.wo.file')
|
|
local compls = {}
|
|
eq({ compls, #'vim.wo.' }, actual)
|
|
end
|
|
do
|
|
local actual = get_completions('vim.wo.winh')
|
|
-- should contain window options only
|
|
local compls = { 'winhighlight' }
|
|
eq({ compls, #'vim.wo.' }, actual)
|
|
end
|
|
end)
|
|
end)
|
|
|
|
it('should return everything if the input is of length 0', function()
|
|
eq({ { 'other', 'vim' }, 0 }, get_completions('', { vim = true, other = true }))
|
|
end)
|
|
|
|
describe('get_parts', function()
|
|
it('should return an empty list for no separators', function()
|
|
eq({ {}, 1 }, get_compl_parts('vim'))
|
|
end)
|
|
|
|
it('just the first item before a period', function()
|
|
eq({ { 'vim' }, 5 }, get_compl_parts('vim.ap'))
|
|
end)
|
|
|
|
it('should return multiple parts just for period', function()
|
|
eq({ { 'vim', 'api' }, 9 }, get_compl_parts('vim.api.nvim_buf'))
|
|
end)
|
|
|
|
it('should be OK with colons', function()
|
|
eq({ { 'vim', 'api' }, 9 }, get_compl_parts('vim:api.nvim_buf'))
|
|
end)
|
|
|
|
it('should work for just one string ref', function()
|
|
eq({ { 'vim', 'api' }, 12 }, get_compl_parts("vim['api'].nvim_buf"))
|
|
end)
|
|
|
|
it('should work for just one string ref, with double quote', function()
|
|
eq({ { 'vim', 'api' }, 12 }, get_compl_parts('vim["api"].nvim_buf'))
|
|
end)
|
|
|
|
it('should allows back-to-back string ref', function()
|
|
eq({ { 'vim', 'nested', 'api' }, 22 }, get_compl_parts('vim["nested"]["api"].nvim_buf'))
|
|
end)
|
|
|
|
it('should allows back-to-back string ref with spaces before and after', function()
|
|
eq({ { 'vim', 'nested', 'api' }, 25 }, get_compl_parts('vim[ "nested" ]["api"].nvim_buf'))
|
|
end)
|
|
|
|
it('should allow VAR style loolup', function()
|
|
eq({ { 'vim', { 'NESTED' }, 'api' }, 20 }, get_compl_parts('vim[NESTED]["api"].nvim_buf'))
|
|
end)
|
|
end)
|
|
end)
|