fix(lua): find length of completion prefix earlier (#29384)

Do the expansion right after setting the expand context, so that the
length of the completion prefix can be set, but don't do that directly
in set_one_cmd_context(), as that's also called by getcmdcompltype().
This commit is contained in:
zeertzjq 2024-06-18 09:47:10 +08:00 committed by GitHub
parent 9d200c78a5
commit 948f2beed4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 89 additions and 26 deletions

View File

@ -240,6 +240,9 @@ int nextwild(expand_T *xp, int type, int options, bool escape)
if (xp->xp_numfiles == -1) {
set_expand_context(xp);
if (xp->xp_context == EXPAND_LUA) {
nlua_expand_pat(xp, xp->xp_pattern);
}
cmd_showtail = expand_showtail(xp);
}
@ -287,11 +290,6 @@ int nextwild(expand_T *xp, int type, int options, bool escape)
use_options, type);
xfree(p1);
// xp->xp_pattern might have been modified by ExpandOne (for example,
// in lua completion), so recompute the pattern index and length
i = (int)(xp->xp_pattern - ccline->cmdbuff);
xp->xp_pattern_len = (size_t)ccline->cmdpos - (size_t)i;
// Longest match: make sure it is not shorter, happens with :help.
if (p2 != NULL && type == WILD_LONGEST) {
int j;
@ -1061,6 +1059,9 @@ int showmatches(expand_T *xp, bool wildmenu)
if (xp->xp_numfiles == -1) {
set_expand_context(xp);
if (xp->xp_context == EXPAND_LUA) {
nlua_expand_pat(xp, xp->xp_pattern);
}
int i = expand_cmdline(xp, ccline->cmdbuff, ccline->cmdpos,
&numMatches, &matches);
showtail = expand_showtail(xp);
@ -2797,8 +2798,7 @@ static int ExpandFromContext(expand_T *xp, char *pat, char ***matches, int *numM
}
if (xp->xp_context == EXPAND_LUA) {
ILOG("PAT %s", pat);
return nlua_expand_pat(xp, pat, numMatches, matches);
return nlua_expand_get_matches(numMatches, matches);
}
if (!fuzzy) {
@ -3610,7 +3610,10 @@ void f_getcompletion(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
}
theend:
;
if (xpc.xp_context == EXPAND_LUA) {
nlua_expand_pat(&xpc, xpc.xp_pattern);
xpc.xp_pattern_len = strlen(xpc.xp_pattern);
}
char *pat;
if (cmdline_fuzzy_completion_supported(&xpc)) {
// when fuzzy matching, don't modify the search string

View File

@ -43,6 +43,7 @@
#include "nvim/indent_c.h"
#include "nvim/insexpand.h"
#include "nvim/keycodes.h"
#include "nvim/lua/executor.h"
#include "nvim/macros_defs.h"
#include "nvim/mark_defs.h"
#include "nvim/mbyte.h"
@ -4144,6 +4145,9 @@ static int get_cmdline_compl_info(char *line, colnr_T curs_col)
compl_pattern = xstrnsave(line, (size_t)curs_col);
compl_patternlen = (size_t)curs_col;
set_cmd_context(&compl_xp, compl_pattern, (int)compl_patternlen, curs_col, false);
if (compl_xp.xp_context == EXPAND_LUA) {
nlua_expand_pat(&compl_xp, compl_xp.xp_pattern);
}
if (compl_xp.xp_context == EXPAND_UNSUCCESSFUL
|| compl_xp.xp_context == EXPAND_NOTHING) {
// No completion possible, use an empty pattern to get a

View File

@ -1924,10 +1924,14 @@ static void nlua_add_treesitter(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL
lua_setfield(lstate, -2, "_ts_get_minimum_language_version");
}
int nlua_expand_pat(expand_T *xp, char *pat, int *num_results, char ***results)
static garray_T expand_result_array = GA_EMPTY_INIT_VALUE;
/// Finds matches for Lua cmdline completion and advances xp->xp_pattern after prefix.
/// This should be called before xp->xp_pattern is first used.
void nlua_expand_pat(expand_T *xp, const char *pat)
{
lua_State *const lstate = global_lstate;
int ret = OK;
int status = OK;
// [ vim ]
lua_getglobal(lstate, "vim");
@ -1942,54 +1946,54 @@ int nlua_expand_pat(expand_T *xp, char *pat, int *num_results, char ***results)
if (nlua_pcall(lstate, 1, 2) != 0) {
nlua_error(lstate,
_("Error executing vim._expand_pat: %.*s"));
return FAIL;
return;
}
Error err = ERROR_INIT;
*num_results = 0;
*results = NULL;
Arena arena = ARENA_EMPTY;
int prefix_len = (int)nlua_pop_Integer(lstate, &arena, &err);
if (ERROR_SET(&err)) {
ret = FAIL;
status = FAIL;
goto cleanup;
}
Array completions = nlua_pop_Array(lstate, &arena, &err);
if (ERROR_SET(&err)) {
ret = FAIL;
status = FAIL;
goto cleanup_array;
}
garray_T result_array;
ga_init(&result_array, (int)sizeof(char *), 80);
ga_clear(&expand_result_array);
ga_init(&expand_result_array, (int)sizeof(char *), 80);
for (size_t i = 0; i < completions.size; i++) {
Object v = completions.items[i];
if (v.type != kObjectTypeString) {
ret = FAIL;
status = FAIL;
goto cleanup_array;
}
GA_APPEND(char *, &result_array, string_to_cstr(v.data.string));
GA_APPEND(char *, &expand_result_array, string_to_cstr(v.data.string));
}
xp->xp_pattern += prefix_len;
*results = result_array.ga_data;
*num_results = result_array.ga_len;
cleanup_array:
arena_mem_free(arena_finish(&arena));
cleanup:
if (ret == FAIL) {
ga_clear(&result_array);
if (status == FAIL) {
ga_clear(&expand_result_array);
}
}
return ret;
int nlua_expand_get_matches(int *num_results, char ***results)
{
*results = expand_result_array.ga_data;
*num_results = expand_result_array.ga_len;
expand_result_array = (garray_T)GA_EMPTY_INIT_VALUE;
return *num_results > 0;
}
static int nlua_is_thread(lua_State *lstate)

View File

@ -812,11 +812,63 @@ describe('completion', function()
}
end)
it('prefix is not included in completion for cmdline mode', function()
feed(':lua math.a<Tab>')
screen:expect([[
|
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
{100:abs}{3: acos asin atan atan2 }|
:lua math.abs^ |
]])
feed('<Tab>')
screen:expect([[
|
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
{3:abs }{100:acos}{3: asin atan atan2 }|
:lua math.acos^ |
]])
end)
it('prefix is not included in completion for i_CTRL-X_CTRL-V #19623', function()
feed('ilua math.a<C-X><C-V>')
screen:expect([[
lua math.abs^ |
{1:~ }{12: abs }{1: }|
{1:~ }{4: acos }{1: }|
{1:~ }{4: asin }{1: }|
{1:~ }{4: atan }{1: }|
{1:~ }{4: atan2 }{1: }|
{1:~ }|
{5:-- Command-line completion (^V^N^P) }{6:match 1 of 5} |
]])
feed('<C-V>')
screen:expect([[
lua math.acos^ |
{1:~ }{4: abs }{1: }|
{1:~ }{12: acos }{1: }|
{1:~ }{4: asin }{1: }|
{1:~ }{4: atan }{1: }|
{1:~ }{4: atan2 }{1: }|
{1:~ }|
{5:-- Command-line completion (^V^N^P) }{6:match 2 of 5} |
]])
end)
it('provides completion from `getcompletion()`', function()
eq({ 'vim' }, fn.getcompletion('vi', 'lua'))
eq({ 'api' }, fn.getcompletion('vim.ap', 'lua'))
eq({ 'tbl_filter' }, fn.getcompletion('vim.tbl_fil', 'lua'))
eq({ 'vim' }, fn.getcompletion('print(vi', 'lua'))
eq({ 'abs', 'acos', 'asin', 'atan', 'atan2' }, fn.getcompletion('math.a', 'lua'))
eq({ 'abs', 'acos', 'asin', 'atan', 'atan2' }, fn.getcompletion('lua math.a', 'cmdline'))
-- fuzzy completion is not supported, so the result should be the same
command('set wildoptions+=fuzzy')
eq({ 'vim' }, fn.getcompletion('vi', 'lua'))