diff --git a/runtime/doc/builtin.txt b/runtime/doc/builtin.txt index 84f782ac4f..d59618a075 100644 --- a/runtime/doc/builtin.txt +++ b/runtime/doc/builtin.txt @@ -1264,8 +1264,11 @@ complete_info([{what}]) *complete_info()* (`table`) complete_match([{lnum}, {col}]) *complete_match()* - Returns a List of matches found according to the 'isexpand' - option. Each match is represented as a List containing + Searches backward from the given position and returns a List + of matches according to the 'isexpand' option. When no + arguments are provided, uses the current cursor position. + + Each match is represented as a List containing [startcol, trigger_text] where: - startcol: column position where completion should start, or -1 if no trigger position is found. For multi-character @@ -1277,40 +1280,35 @@ complete_match([{lnum}, {col}]) *complete_match()* When 'isexpand' is empty, uses the 'iskeyword' pattern "\k\+$" to find the start of the current keyword. - When no arguments are provided, uses the current cursor - position. - - Examples: > + Examples: >vim set isexpand=.,->,/,/*,abc func CustomComplete() - let res = complete_match() - if res->len() == 0 | return | endif - let [col, trigger] = res[0] - let items = [] - if trigger == '/*' - let items = ['/** */'] - elseif trigger == '/' - let items = ['/*! */', '// TODO:', '// fixme:'] - elseif trigger == '.' - let items = ['length()'] - elseif trigger =~ '^\->' - let items = ['map()', 'reduce()'] - elseif trigger =~ '^\abc' - let items = ['def', 'ghk'] - endif - if items->len() > 0 - let startcol = trigger =~ '^/' ? col : col + len(trigger) - call complete(startcol, items) - endif + let res = complete_match() + if res->len() == 0 | return | endif + let [col, trigger] = res[0] + let items = [] + if trigger == '/*' + let items = ['/** */'] + elseif trigger == '/' + let items = ['/*! */', '// TODO:', '// fixme:'] + elseif trigger == '.' + let items = ['length()'] + elseif trigger =~ '^\->' + let items = ['map()', 'reduce()'] + elseif trigger =~ '^\abc' + let items = ['def', 'ghk'] + endif + if items->len() > 0 + let startcol = trigger =~ '^/' ? col : col + len(trigger) + call complete(startcol, items) + endif endfunc inoremap call CustomComplete() < - Return type: list> - Parameters: ~ - • {lnum} (`integer??`) - • {col} (`integer??`) + • {lnum} (`integer?`) + • {col} (`integer?`) Return: ~ (`table`) diff --git a/runtime/doc/options.txt b/runtime/doc/options.txt index 9bad6ebeea..38f4ee5358 100644 --- a/runtime/doc/options.txt +++ b/runtime/doc/options.txt @@ -3550,6 +3550,10 @@ A jump table for the options with a short description can be found at |Q_op|. Note: Use "\\," to add a literal comma as trigger character, see |option-backslash|. + Examples: >vim + set isexpand=.,->,/*,\\, +< + *'isfname'* *'isf'* 'isfname' 'isf' string (default for Windows: "@,48-57,/,\,.,-,_,+,,,#,$,%,{,},[,],@-@,!,~,=" diff --git a/runtime/doc/usr_41.txt b/runtime/doc/usr_41.txt index 916fd9e2e2..cc37197a79 100644 --- a/runtime/doc/usr_41.txt +++ b/runtime/doc/usr_41.txt @@ -934,6 +934,8 @@ Insert mode completion: *completion-functions* complete_add() add to found matches complete_check() check if completion should be aborted complete_info() get current completion information + complete_match() get insert completion start match col and + trigger text pumvisible() check if the popup menu is displayed pum_getpos() position and size of popup menu if visible diff --git a/runtime/lua/vim/_meta/options.lua b/runtime/lua/vim/_meta/options.lua index 13dec50231..b17c2ca557 100644 --- a/runtime/lua/vim/_meta/options.lua +++ b/runtime/lua/vim/_meta/options.lua @@ -3439,6 +3439,13 @@ vim.bo.inf = vim.bo.infercase --- Note: Use "\\," to add a literal comma as trigger character, see --- `option-backslash`. --- +--- Examples: +--- +--- ```vim +--- set isexpand=.,->,/*,\\, +--- ``` +--- +--- --- @type string vim.o.isexpand = "" vim.o.ise = vim.o.isexpand diff --git a/runtime/lua/vim/_meta/vimfn.lua b/runtime/lua/vim/_meta/vimfn.lua index 14a078e5f4..30bd45c889 100644 --- a/runtime/lua/vim/_meta/vimfn.lua +++ b/runtime/lua/vim/_meta/vimfn.lua @@ -1109,8 +1109,11 @@ function vim.fn.complete_check() end --- @return table function vim.fn.complete_info(what) end ---- Returns a List of matches found according to the 'isexpand' ---- option. Each match is represented as a List containing +--- Searches backward from the given position and returns a List +--- of matches according to the 'isexpand' option. When no +--- arguments are provided, uses the current cursor position. +--- +--- Each match is represented as a List containing --- [startcol, trigger_text] where: --- - startcol: column position where completion should start, --- or -1 if no trigger position is found. For multi-character @@ -1122,39 +1125,34 @@ function vim.fn.complete_info(what) end --- When 'isexpand' is empty, uses the 'iskeyword' pattern --- "\k\+$" to find the start of the current keyword. --- ---- When no arguments are provided, uses the current cursor ---- position. ---- ---- Examples: > +--- Examples: >vim --- set isexpand=.,->,/,/*,abc --- func CustomComplete() ---- let res = complete_match() ---- if res->len() == 0 | return | endif ---- let [col, trigger] = res[0] ---- let items = [] ---- if trigger == '/*' ---- let items = ['/** */'] ---- elseif trigger == '/' ---- let items = ['/*! */', '// TODO:', '// fixme:'] ---- elseif trigger == '.' ---- let items = ['length()'] ---- elseif trigger =~ '^\->' ---- let items = ['map()', 'reduce()'] ---- elseif trigger =~ '^\abc' ---- let items = ['def', 'ghk'] ---- endif ---- if items->len() > 0 ---- let startcol = trigger =~ '^/' ? col : col + len(trigger) ---- call complete(startcol, items) ---- endif +--- let res = complete_match() +--- if res->len() == 0 | return | endif +--- let [col, trigger] = res[0] +--- let items = [] +--- if trigger == '/*' +--- let items = ['/** */'] +--- elseif trigger == '/' +--- let items = ['/*! */', '// TODO:', '// fixme:'] +--- elseif trigger == '.' +--- let items = ['length()'] +--- elseif trigger =~ '^\->' +--- let items = ['map()', 'reduce()'] +--- elseif trigger =~ '^\abc' +--- let items = ['def', 'ghk'] +--- endif +--- if items->len() > 0 +--- let startcol = trigger =~ '^/' ? col : col + len(trigger) +--- call complete(startcol, items) +--- endif --- endfunc --- inoremap call CustomComplete() --- < --- ---- Return type: list> ---- ---- @param lnum? integer? ---- @param col? integer? +--- @param lnum? integer +--- @param col? integer --- @return table function vim.fn.complete_match(lnum, col) end diff --git a/runtime/optwin.vim b/runtime/optwin.vim index 9af414d909..eed625a7c7 100644 --- a/runtime/optwin.vim +++ b/runtime/optwin.vim @@ -1,7 +1,7 @@ " These commands create the option window. " " Maintainer: The Vim Project -" Last Change: 2025 Apr 07 +" Last Change: 2025 Apr 24 " Former Maintainer: Bram Moolenaar " If there already is an option window, jump to that one. @@ -1102,6 +1102,8 @@ call AddOption("isfname", gettext("specifies the characters in a file name" call OptionG("isf", &isf) call AddOption("isident", gettext("specifies the characters in an identifier")) call OptionG("isi", &isi) +call AddOption("isexpand", gettext("defines trigger strings for complete_match()")) +call append("$", "\t" .. s:local_to_buffer) call AddOption("iskeyword", gettext("specifies the characters in a keyword")) call append("$", "\t" .. s:local_to_buffer) call OptionL("isk") diff --git a/src/nvim/buffer_defs.h b/src/nvim/buffer_defs.h index d178518892..5eca776a86 100644 --- a/src/nvim/buffer_defs.h +++ b/src/nvim/buffer_defs.h @@ -558,7 +558,7 @@ struct file_buffer { char *b_p_fo; ///< 'formatoptions' char *b_p_flp; ///< 'formatlistpat' int b_p_inf; ///< 'infercase' - char *b_p_ise; ///< 'isexpand' + char *b_p_ise; ///< 'isexpand' local value char *b_p_isk; ///< 'iskeyword' char *b_p_def; ///< 'define' local value char *b_p_inc; ///< 'include' diff --git a/src/nvim/eval.lua b/src/nvim/eval.lua index d27f98831f..eae54ca59a 100644 --- a/src/nvim/eval.lua +++ b/src/nvim/eval.lua @@ -1481,8 +1481,11 @@ M.funcs = { args = { 0, 2 }, base = 0, desc = [=[ - Returns a List of matches found according to the 'isexpand' - option. Each match is represented as a List containing + Searches backward from the given position and returns a List + of matches according to the 'isexpand' option. When no + arguments are provided, uses the current cursor position. + + Each match is represented as a List containing [startcol, trigger_text] where: - startcol: column position where completion should start, or -1 if no trigger position is found. For multi-character @@ -1494,39 +1497,34 @@ M.funcs = { When 'isexpand' is empty, uses the 'iskeyword' pattern "\k\+$" to find the start of the current keyword. - When no arguments are provided, uses the current cursor - position. - - Examples: > + Examples: >vim set isexpand=.,->,/,/*,abc func CustomComplete() - let res = complete_match() - if res->len() == 0 | return | endif - let [col, trigger] = res[0] - let items = [] - if trigger == '/*' - let items = ['/** */'] - elseif trigger == '/' - let items = ['/*! */', '// TODO:', '// fixme:'] - elseif trigger == '.' - let items = ['length()'] - elseif trigger =~ '^\->' - let items = ['map()', 'reduce()'] - elseif trigger =~ '^\abc' - let items = ['def', 'ghk'] - endif - if items->len() > 0 - let startcol = trigger =~ '^/' ? col : col + len(trigger) - call complete(startcol, items) - endif + let res = complete_match() + if res->len() == 0 | return | endif + let [col, trigger] = res[0] + let items = [] + if trigger == '/*' + let items = ['/** */'] + elseif trigger == '/' + let items = ['/*! */', '// TODO:', '// fixme:'] + elseif trigger == '.' + let items = ['length()'] + elseif trigger =~ '^\->' + let items = ['map()', 'reduce()'] + elseif trigger =~ '^\abc' + let items = ['def', 'ghk'] + endif + if items->len() > 0 + let startcol = trigger =~ '^/' ? col : col + len(trigger) + call complete(startcol, items) + endif endfunc inoremap call CustomComplete() -< - - Return type: list> + < ]=], name = 'complete_match', - params = { { 'lnum', 'integer?' }, { 'col', 'integer?' } }, + params = { { 'lnum', 'integer' }, { 'col', 'integer' } }, returns = 'table', signature = 'complete_match([{lnum}, {col}])', }, diff --git a/src/nvim/insexpand.c b/src/nvim/insexpand.c index 73bbfffe1c..b9070af02c 100644 --- a/src/nvim/insexpand.c +++ b/src/nvim/insexpand.c @@ -3082,24 +3082,18 @@ void f_complete_check(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) } /// Add match item to the return list. -/// Returns FAIL if out of memory, OK otherwise. -static int add_match_to_list(typval_T *rettv, char *str, int pos) +static void add_match_to_list(typval_T *rettv, char *str, int pos) { - list_T *match = tv_list_alloc(kListLenMayKnow); - if (match == NULL) { - return FAIL; - } - + list_T *match = tv_list_alloc(2); tv_list_append_number(match, pos + 1); tv_list_append_string(match, str, -1); tv_list_append_list(rettv->vval.v_list, match); - return OK; } /// "complete_match()" function void f_complete_match(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { - tv_list_alloc_ret(rettv, kListLenUnknown); + tv_list_alloc_ret(rettv, kListLenUnknown); char *ise = curbuf->b_p_ise[0] != NUL ? curbuf->b_p_ise : p_ise; @@ -3131,9 +3125,6 @@ void f_complete_match(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) } char *before_cursor = xstrnsave(line, (size_t)col); - if (before_cursor == NULL) { - return; - } if (ise == NULL || *ise == NUL) { regmatch_T regmatch; @@ -3141,19 +3132,9 @@ void f_complete_match(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) if (regmatch.regprog != NULL) { if (vim_regexec_nl(®match, before_cursor, (colnr_T)0)) { char *trig = xstrnsave(regmatch.startp[0], (size_t)(regmatch.endp[0] - regmatch.startp[0])); - if (trig == NULL) { - xfree(before_cursor); - return; - } - int bytepos = (int)(regmatch.startp[0] - before_cursor); - int ret = add_match_to_list(rettv, trig, bytepos); + add_match_to_list(rettv, trig, bytepos); xfree(trig); - if (ret == FAIL) { - xfree(before_cursor); - vim_regfree(regmatch.regprog); - return; - } } vim_regfree(regmatch.regprog); } @@ -3166,10 +3147,7 @@ void f_complete_match(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) if (len > 0 && (int)len <= col) { if (strncmp(cur_end - len, part, len) == 0) { int bytepos = col - (int)len; - if (add_match_to_list(rettv, part, bytepos) == FAIL) { - xfree(before_cursor); - return; - } + add_match_to_list(rettv, part, bytepos); } } } diff --git a/src/nvim/option.c b/src/nvim/option.c index 026e73938d..99c2318f89 100644 --- a/src/nvim/option.c +++ b/src/nvim/option.c @@ -4460,10 +4460,10 @@ void *get_varp_scope_from(vimoption_T *p, int opt_flags, buf_T *buf, win_T *win) return &(buf->b_p_def); case kOptInclude: return &(buf->b_p_inc); - case kOptIsexpand: - return &(buf->b_p_ise); case kOptCompleteopt: return &(buf->b_p_cot); + case kOptIsexpand: + return &(buf->b_p_ise); case kOptDictionary: return &(buf->b_p_dict); case kOptThesaurus: @@ -4547,10 +4547,10 @@ void *get_varp_from(vimoption_T *p, buf_T *buf, win_T *win) return *buf->b_p_def != NUL ? &(buf->b_p_def) : p->var; case kOptInclude: return *buf->b_p_inc != NUL ? &(buf->b_p_inc) : p->var; - case kOptIsexpand: - return *buf->b_p_ise != NUL ? &(buf->b_p_ise) : p->var; case kOptCompleteopt: return *buf->b_p_cot != NUL ? &(buf->b_p_cot) : p->var; + case kOptIsexpand: + return *buf->b_p_ise != NUL ? &(buf->b_p_ise) : p->var; case kOptDictionary: return *buf->b_p_dict != NUL ? &(buf->b_p_dict) : p->var; case kOptThesaurus: @@ -5242,6 +5242,7 @@ void buf_copy_options(buf_T *buf, int flags) buf->b_cot_flags = 0; buf->b_p_dict = empty_string_option; buf->b_p_tsr = empty_string_option; + buf->b_p_ise = empty_string_option; buf->b_p_tsrfu = empty_string_option; buf->b_p_qe = xstrdup(p_qe); COPY_OPT_SCTX(buf, kBufOptQuoteescape); diff --git a/src/nvim/option_vars.h b/src/nvim/option_vars.h index 9080469a09..16b009fa73 100644 --- a/src/nvim/option_vars.h +++ b/src/nvim/option_vars.h @@ -374,9 +374,9 @@ EXTERN int p_is; ///< 'incsearch' EXTERN char *p_inde; ///< 'indentexpr' EXTERN char *p_indk; ///< 'indentkeys' EXTERN char *p_icm; ///< 'inccommand' +EXTERN char *p_ise; ///< 'isexpand' EXTERN char *p_isf; ///< 'isfname' EXTERN char *p_isi; ///< 'isident' -EXTERN char *p_ise; ///< 'isexpand' EXTERN char *p_isk; ///< 'iskeyword' EXTERN char *p_isp; ///< 'isprint' EXTERN int p_js; ///< 'joinspaces' diff --git a/src/nvim/options.lua b/src/nvim/options.lua index 160e0f8078..d8094d5d35 100644 --- a/src/nvim/options.lua +++ b/src/nvim/options.lua @@ -4632,7 +4632,7 @@ local options = { abbreviation = 'ise', cb = 'did_set_isexpand', defaults = '', - deny_duplicates = false, + deny_duplicates = true, desc = [=[ Defines characters and patterns for completion in insert mode. Used by the |complete_match()| function to determine the starting position for @@ -4643,6 +4643,10 @@ local options = { Note: Use "\\," to add a literal comma as trigger character, see |option-backslash|. + + Examples: >vim + set isexpand=.,->,/*,\\, + < ]=], full_name = 'isexpand', list = 'onecomma', diff --git a/test/old/testdir/gen_opt_test.vim b/test/old/testdir/gen_opt_test.vim index 168a38e665..16e165cc13 100644 --- a/test/old/testdir/gen_opt_test.vim +++ b/test/old/testdir/gen_opt_test.vim @@ -261,6 +261,7 @@ let test_values = { "\ 'imactivatekey': [['', 'S-space'], ['xxx']], \ 'isfname': [['', '@', '@,48-52'], ['xxx', '@48']], \ 'isident': [['', '@', '@,48-52'], ['xxx', '@48']], + \ 'isexpand': [['', '.,->', '/,/*,\\,'], [',,', '\\,,']], \ 'iskeyword': [['', '@', '@,48-52'], ['xxx', '@48']], \ 'isprint': [['', '@', '@,48-52'], ['xxx', '@48']], \ 'jumpoptions': [['', 'stack'], ['xxx']],