Merge pull request #33667 from glepnir/vim-9.1.1341

vim-patch: 9.1.{1341,1344}
This commit is contained in:
zeertzjq
2025-04-29 14:59:32 +08:00
committed by GitHub
16 changed files with 427 additions and 1 deletions

View File

@ -1263,6 +1263,56 @@ complete_info([{what}]) *complete_info()*
Return: ~
(`table`)
complete_match([{lnum}, {col}]) *complete_match()*
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
triggers, returns the column of the first character.
- trigger_text: the matching trigger string from 'isexpand',
or empty string if no match was found or when using the
default 'iskeyword' pattern.
When 'isexpand' is empty, uses the 'iskeyword' pattern
"\k\+$" to find the start of the current keyword.
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
endfunc
inoremap <Tab> <Cmd>call CustomComplete()<CR>
<
Parameters: ~
• {lnum} (`integer?`)
• {col} (`integer?`)
Return: ~
(`table`)
confirm({msg} [, {choices} [, {default} [, {type}]]]) *confirm()*
confirm() offers the user a dialog, from which a choice can be
made. It returns the number of the choice. For the first

View File

@ -3543,6 +3543,23 @@ A jump table for the options with a short description can be found at |Q_op|.
and there is a letter before it, the completed part is made uppercase.
With 'noinfercase' the match is used as-is.
*'isexpand'* *'ise'*
'isexpand' 'ise' string (default "")
global or local to buffer |global-local|
Defines characters and patterns for completion in insert mode. Used by
the |complete_match()| function to determine the starting position for
completion. This is a comma-separated list of triggers. Each trigger
can be:
- A single character like "." or "/"
- A sequence of characters like "->", "/*", or "/**"
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,/,\,.,-,_,+,,,#,$,%,{,},[,],@-@,!,~,="

View File

@ -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

View File

@ -3438,6 +3438,31 @@ vim.o.inf = vim.o.infercase
vim.bo.infercase = vim.o.infercase
vim.bo.inf = vim.bo.infercase
--- Defines characters and patterns for completion in insert mode. Used by
--- the `complete_match()` function to determine the starting position for
--- completion. This is a comma-separated list of triggers. Each trigger
--- can be:
--- - A single character like "." or "/"
--- - A sequence of characters like "->", "/*", or "/**"
---
--- 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
vim.bo.isexpand = vim.o.isexpand
vim.bo.ise = vim.bo.isexpand
vim.go.isexpand = vim.o.isexpand
vim.go.ise = vim.go.isexpand
--- The characters specified by this option are included in file names and
--- path names. Filenames are used for commands like "gf", "[i" and in
--- the tags file. It is also used for "\f" in a `pattern`.

View File

@ -1109,6 +1109,53 @@ function vim.fn.complete_check() end
--- @return table
function vim.fn.complete_info(what) end
--- 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
--- triggers, returns the column of the first character.
--- - trigger_text: the matching trigger string from 'isexpand',
--- or empty string if no match was found or when using the
--- default 'iskeyword' pattern.
---
--- When 'isexpand' is empty, uses the 'iskeyword' pattern
--- "\k\+$" to find the start of the current keyword.
---
--- 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
--- endfunc
--- inoremap <Tab> <Cmd>call CustomComplete()<CR>
--- <
---
--- @param lnum? integer
--- @param col? integer
--- @return table
function vim.fn.complete_match(lnum, col) end
--- confirm() offers the user a dialog, from which a choice can be
--- made. It returns the number of the choice. For the first
--- choice this is 1.

View File

@ -1,7 +1,7 @@
" These commands create the option window.
"
" Maintainer: The Vim Project <https://github.com/vim/vim>
" Last Change: 2025 Apr 07
" Last Change: 2025 Apr 24
" Former Maintainer: Bram Moolenaar <Bram@vim.org>
" If there already is an option window, jump to that one.
@ -1102,6 +1102,8 @@ call <SID>AddOption("isfname", gettext("specifies the characters in a file name"
call <SID>OptionG("isf", &isf)
call <SID>AddOption("isident", gettext("specifies the characters in an identifier"))
call <SID>OptionG("isi", &isi)
call <SID>AddOption("isexpand", gettext("defines trigger strings for complete_match()"))
call append("$", "\t" .. s:local_to_buffer)
call <SID>AddOption("iskeyword", gettext("specifies the characters in a keyword"))
call append("$", "\t" .. s:local_to_buffer)
call <SID>OptionL("isk")

View File

@ -2083,6 +2083,7 @@ void free_buf_options(buf_T *buf, bool free_p_ff)
clear_string_option(&buf->b_p_cinw);
clear_string_option(&buf->b_p_cot);
clear_string_option(&buf->b_p_cpt);
clear_string_option(&buf->b_p_ise);
clear_string_option(&buf->b_p_cfu);
callback_free(&buf->b_cfu_cb);
clear_string_option(&buf->b_p_ofu);

View File

@ -558,6 +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' local value
char *b_p_isk; ///< 'iskeyword'
char *b_p_def; ///< 'define' local value
char *b_p_inc; ///< 'include'

View File

@ -1477,6 +1477,57 @@ M.funcs = {
returns = 'table',
signature = 'complete_info([{what}])',
},
complete_match = {
args = { 0, 2 },
base = 0,
desc = [=[
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
triggers, returns the column of the first character.
- trigger_text: the matching trigger string from 'isexpand',
or empty string if no match was found or when using the
default 'iskeyword' pattern.
When 'isexpand' is empty, uses the 'iskeyword' pattern
"\k\+$" to find the start of the current keyword.
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
endfunc
inoremap <Tab> <Cmd>call CustomComplete()<CR>
<
]=],
name = 'complete_match',
params = { { 'lnum', 'integer' }, { 'col', 'integer' } },
returns = 'table',
signature = 'complete_match([{lnum}, {col}])',
},
confirm = {
args = { 1, 4 },
base = 1,

View File

@ -3081,6 +3081,81 @@ void f_complete_check(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
RedrawingDisabled = saved;
}
/// Add match item to the return list.
static void add_match_to_list(typval_T *rettv, char *str, int pos)
{
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);
}
/// "complete_match()" function
void f_complete_match(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
{
tv_list_alloc_ret(rettv, kListLenUnknown);
char *ise = curbuf->b_p_ise[0] != NUL ? curbuf->b_p_ise : p_ise;
linenr_T lnum = 0;
colnr_T col = 0;
char part[MAXPATHL];
if (argvars[0].v_type == VAR_UNKNOWN) {
lnum = curwin->w_cursor.lnum;
col = curwin->w_cursor.col;
} else if (argvars[1].v_type == VAR_UNKNOWN) {
emsg(_(e_invarg));
return;
} else {
lnum = (linenr_T)tv_get_number(&argvars[0]);
col = (colnr_T)tv_get_number(&argvars[1]);
if (lnum < 1 || lnum > curbuf->b_ml.ml_line_count) {
semsg(_(e_invalid_line_number_nr), lnum);
return;
}
if (col < 1 || col > ml_get_buf_len(curbuf, lnum)) {
semsg(_(e_invalid_column_number_nr), col + 1);
return;
}
}
char *line = ml_get_buf(curbuf, lnum);
if (line == NULL) {
return;
}
char *before_cursor = xstrnsave(line, (size_t)col);
if (ise == NULL || *ise == NUL) {
regmatch_T regmatch;
regmatch.regprog = vim_regcomp("\\k\\+$", RE_MAGIC);
if (regmatch.regprog != NULL) {
if (vim_regexec_nl(&regmatch, before_cursor, (colnr_T)0)) {
char *trig = xstrnsave(regmatch.startp[0], (size_t)(regmatch.endp[0] - regmatch.startp[0]));
int bytepos = (int)(regmatch.startp[0] - before_cursor);
add_match_to_list(rettv, trig, bytepos);
xfree(trig);
}
vim_regfree(regmatch.regprog);
}
} else {
char *p = ise;
char *cur_end = before_cursor + (int)strlen(before_cursor);
while (*p != NUL) {
size_t len = copy_option_part(&p, part, MAXPATHL, ",");
if (len > 0 && (int)len <= col) {
if (strncmp(cur_end - len, part, len) == 0) {
int bytepos = col - (int)len;
add_match_to_list(rettv, part, bytepos);
}
}
}
}
xfree(before_cursor);
}
/// Return Insert completion mode name string
static char *ins_compl_mode(void)
{

View File

@ -4462,6 +4462,8 @@ void *get_varp_scope_from(vimoption_T *p, int opt_flags, buf_T *buf, win_T *win)
return &(buf->b_p_inc);
case kOptCompleteopt:
return &(buf->b_p_cot);
case kOptIsexpand:
return &(buf->b_p_ise);
case kOptDictionary:
return &(buf->b_p_dict);
case kOptThesaurus:
@ -4547,6 +4549,8 @@ void *get_varp_from(vimoption_T *p, buf_T *buf, win_T *win)
return *buf->b_p_inc != NUL ? &(buf->b_p_inc) : 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:
@ -5238,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);

View File

@ -374,6 +374,7 @@ 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_isk; ///< 'iskeyword'

View File

@ -4634,6 +4634,33 @@ local options = {
type = 'boolean',
immutable = true,
},
{
abbreviation = 'ise',
cb = 'did_set_isexpand',
defaults = '',
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
completion. This is a comma-separated list of triggers. Each trigger
can be:
- A single character like "." or "/"
- A sequence of characters like "->", "/*", or "/**"
Note: Use "\\," to add a literal comma as trigger character, see
|option-backslash|.
Examples: >vim
set isexpand=.,->,/*,\\,
<
]=],
full_name = 'isexpand',
list = 'onecomma',
scope = { 'global', 'buf' },
short_desc = N_('Defines characters and patterns for completion in insert mode'),
type = 'string',
varname = 'p_ise',
},
{
abbreviation = 'isf',
cb = 'did_set_isopt',

View File

@ -85,6 +85,7 @@ void didset_string_options(void)
check_str_opt(kOptBackupcopy, NULL);
check_str_opt(kOptBelloff, NULL);
check_str_opt(kOptCompletefuzzycollect, NULL);
check_str_opt(kOptIsexpand, NULL);
check_str_opt(kOptCompleteopt, NULL);
check_str_opt(kOptSessionoptions, NULL);
check_str_opt(kOptViewoptions, NULL);
@ -1316,6 +1317,44 @@ const char *did_set_inccommand(optset_T *args FUNC_ATTR_UNUSED)
return did_set_str_generic(args);
}
/// The 'isexpand' option is changed.
const char *did_set_isexpand(optset_T *args)
{
char *ise = p_ise;
char *p;
bool last_was_comma = false;
if (args->os_flags & OPT_LOCAL) {
ise = curbuf->b_p_ise;
}
for (p = ise; *p != NUL;) {
if (*p == '\\' && p[1] == ',') {
p += 2;
last_was_comma = false;
continue;
}
if (*p == ',') {
if (last_was_comma) {
return e_invarg;
}
last_was_comma = true;
p++;
continue;
}
last_was_comma = false;
MB_PTR_ADV(p);
}
if (last_was_comma) {
return e_invarg;
}
return NULL;
}
/// The 'iskeyword' option is changed.
const char *did_set_iskeyword(optset_T *args)
{

View File

@ -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']],

View File

@ -3598,4 +3598,86 @@ func Test_nearest_cpt_option()
delfunc PrintMenuWords
endfunc
func Test_complete_match()
set isexpand=.,/,->,abc,/*,_
func TestComplete()
let res = complete_match()
if res->len() == 0
return
endif
let [startcol, expandchar] = res[0]
if startcol >= 0
let line = getline('.')
let items = []
if expandchar == '/*'
let items = ['/** */']
elseif expandchar =~ '^/'
let items = ['/*! */', '// TODO:', '// fixme:']
elseif expandchar =~ '^\.' && startcol < 4
let items = ['length()', 'push()', 'pop()', 'slice()']
elseif expandchar =~ '^\.' && startcol > 4
let items = ['map()', 'filter()', 'reduce()']
elseif expandchar =~ '^\abc'
let items = ['def', 'ghk']
elseif expandchar =~ '^\->'
let items = ['free()', 'xfree()']
else
let items = ['test1', 'test2', 'test3']
endif
call complete(expandchar =~ '^/' ? startcol : startcol + strlen(expandchar), items)
endif
endfunc
new
inoremap <buffer> <F5> <cmd>call TestComplete()<CR>
call feedkeys("S/*\<F5>\<C-Y>", 'tx')
call assert_equal('/** */', getline('.'))
call feedkeys("S/\<F5>\<C-N>\<C-Y>", 'tx')
call assert_equal('// TODO:', getline('.'))
call feedkeys("Swp.\<F5>\<C-N>\<C-Y>", 'tx')
call assert_equal('wp.push()', getline('.'))
call feedkeys("Swp.property.\<F5>\<C-N>\<C-Y>", 'tx')
call assert_equal('wp.property.filter()', getline('.'))
call feedkeys("Sp->\<F5>\<C-N>\<C-Y>", 'tx')
call assert_equal('p->xfree()', getline('.'))
call feedkeys("Swp->property.\<F5>\<C-Y>", 'tx')
call assert_equal('wp->property.map()', getline('.'))
call feedkeys("Sabc\<F5>\<C-Y>", 'tx')
call assert_equal('abcdef', getline('.'))
call feedkeys("S_\<F5>\<C-Y>", 'tx')
call assert_equal('_test1', getline('.'))
set ise&
call feedkeys("Sabc \<ESC>:let g:result=complete_match()\<CR>", 'tx')
call assert_equal([[1, 'abc']], g:result)
call assert_fails('call complete_match(99, 0)', 'E966:')
call assert_fails('call complete_match(1, 99)', 'E964:')
call assert_fails('call complete_match(1)', 'E474:')
set ise=你好,
call feedkeys("S你好 \<ESC>:let g:result=complete_match()\<CR>", 'tx')
call assert_equal([[1, '你好'], [4, '好']], g:result)
set ise=\\,,->
call feedkeys("Sabc, \<ESC>:let g:result=complete_match()\<CR>", 'tx')
call assert_equal([[4, ',']], g:result)
bw!
unlet g:result
set isexpand&
delfunc TestComplete
endfunc
" vim: shiftwidth=2 sts=2 expandtab nofoldenable