mirror of
https://github.com/vim/vim
synced 2025-07-15 08:41:54 +00:00
patch 9.1.1526: completion: search completion match may differ in case
Problem: completion: search completion match may differ in case (techntools) Solution: add "exacttext" to 'wildoptions' value (Girish Palya) This flag does the following: exacttext When this flag is present, search pattern completion (e.g., in |/|, |?|, |:s|, |:g|, |:v|, and |:vim|) shows exact buffer text as menu items, without preserving regex artifacts like position anchors (e.g., |/\<|). This provides more intuitive menu items that match the actual buffer text. However, searches may be less accurate since the pattern is not preserved exactly. By default, Vim preserves the typed pattern (with anchors) and appends the matched word. This preserves search correctness, especially when using regular expressions or with 'smartcase' enabled. However, the case of the appended matched word may not exactly match the case of the word in the buffer. fixes: #17654 closes: #17667 Signed-off-by: Girish Palya <girishji@gmail.com> Signed-off-by: Christian Brabandt <cb@256bit.org>
This commit is contained in:
committed by
Christian Brabandt
parent
b3eaae21b9
commit
93c2d5bf7f
@ -1,4 +1,4 @@
|
||||
*options.txt* For Vim version 9.1. Last change: 2025 Jul 05
|
||||
*options.txt* For Vim version 9.1. Last change: 2025 Jul 08
|
||||
|
||||
|
||||
VIM REFERENCE MANUAL by Bram Moolenaar
|
||||
@ -9759,6 +9759,7 @@ A jump table for the options with a short description can be found at |Q_op|.
|
||||
< 'wildchar' also enables completion in search pattern contexts such as
|
||||
|/|, |?|, |:s|, |:g|, |:v|, and |:vim|. To insert a literal <Tab>
|
||||
instead of triggering completion, type <C-V><Tab> or "\t".
|
||||
See also |'wildoptions'|.
|
||||
NOTE: This option is set to the Vi default value when 'compatible' is
|
||||
set and to the Vim default value when 'compatible' is reset.
|
||||
|
||||
@ -9926,6 +9927,20 @@ A jump table for the options with a short description can be found at |Q_op|.
|
||||
A list of words that change how |cmdline-completion| is done.
|
||||
|
||||
The following values are supported:
|
||||
exacttext When this flag is present, search pattern completion
|
||||
(e.g., in |/|, |?|, |:s|, |:g|, |:v|, and |:vim|)
|
||||
shows exact buffer text as menu items, without
|
||||
preserving regex artifacts like position
|
||||
anchors (e.g., |/\<|). This provides more intuitive
|
||||
menu items that match the actual buffer text.
|
||||
However, searches may be less accurate since the
|
||||
pattern is not preserved exactly.
|
||||
By default, Vim preserves the typed pattern (with
|
||||
anchors) and appends the matched word. This preserves
|
||||
search correctness, especially when using regular
|
||||
expressions or with 'smartcase' enabled. However, the
|
||||
case of the appended matched word may not exactly
|
||||
match the case of the word in the buffer.
|
||||
fuzzy Use |fuzzy-matching| to find completion matches. When
|
||||
this value is specified, wildcard expansion will not
|
||||
be used for completion. The matches will be sorted by
|
||||
|
@ -1,4 +1,4 @@
|
||||
*version9.txt* For Vim version 9.1. Last change: 2025 Jul 05
|
||||
*version9.txt* For Vim version 9.1. Last change: 2025 Jul 08
|
||||
|
||||
|
||||
VIM REFERENCE MANUAL by Bram Moolenaar
|
||||
@ -41623,6 +41623,8 @@ Completion: ~
|
||||
- improved commandline completion for the |:hi| command
|
||||
- New option value for 'wildmode':
|
||||
"noselect" - do not auto select an entry in the wildmenu
|
||||
"exacttext" - show exact matches in wildmenu with search
|
||||
completion
|
||||
- New flags for 'complete':
|
||||
"F{func}" - complete using given function
|
||||
"F" - complete using 'completefunc'
|
||||
|
@ -4631,6 +4631,7 @@ copy_substring_from_pos(pos_T *start, pos_T *end, char_u **match,
|
||||
int segment_len;
|
||||
linenr_T lnum;
|
||||
garray_T ga;
|
||||
int exacttext = vim_strchr(p_wop, WOP_EXACTTEXT) != NULL;
|
||||
|
||||
if (start->lnum > end->lnum
|
||||
|| (start->lnum == end->lnum && start->col >= end->col))
|
||||
@ -4646,12 +4647,17 @@ copy_substring_from_pos(pos_T *start, pos_T *end, char_u **match,
|
||||
|
||||
segment_len = is_single_line ? (end->col - start->col)
|
||||
: (int)STRLEN(start_ptr);
|
||||
if (ga_grow(&ga, segment_len + 1) != OK)
|
||||
if (ga_grow(&ga, segment_len + 2) != OK)
|
||||
return FAIL;
|
||||
|
||||
ga_concat_len(&ga, start_ptr, segment_len);
|
||||
if (!is_single_line)
|
||||
ga_append(&ga, '\n');
|
||||
{
|
||||
if (exacttext)
|
||||
ga_concat_len(&ga, (char_u *)"\\n", 2);
|
||||
else
|
||||
ga_append(&ga, '\n');
|
||||
}
|
||||
|
||||
// Append full lines between start and end
|
||||
if (!is_single_line)
|
||||
@ -4659,10 +4665,13 @@ copy_substring_from_pos(pos_T *start, pos_T *end, char_u **match,
|
||||
for (lnum = start->lnum + 1; lnum < end->lnum; lnum++)
|
||||
{
|
||||
line = ml_get(lnum);
|
||||
if (ga_grow(&ga, ml_get_len(lnum) + 1) != OK)
|
||||
if (ga_grow(&ga, ml_get_len(lnum) + 2) != OK)
|
||||
return FAIL;
|
||||
ga_concat(&ga, line);
|
||||
ga_append(&ga, '\n');
|
||||
if (exacttext)
|
||||
ga_concat_len(&ga, (char_u *)"\\n", 2);
|
||||
else
|
||||
ga_append(&ga, '\n');
|
||||
}
|
||||
}
|
||||
|
||||
@ -4783,6 +4792,7 @@ expand_pattern_in_buf(
|
||||
int compl_started = FALSE;
|
||||
int search_flags;
|
||||
char_u *match, *full_match;
|
||||
int exacttext = vim_strchr(p_wop, WOP_EXACTTEXT) != NULL;
|
||||
|
||||
#ifdef FEAT_SEARCH_EXTRA
|
||||
has_range = search_first_line != 0;
|
||||
@ -4871,26 +4881,31 @@ expand_pattern_in_buf(
|
||||
&word_end_pos))
|
||||
break;
|
||||
|
||||
// Construct a new match from completed word appended to pattern itself
|
||||
match = concat_pattern_with_buffer_match(pat, pat_len, &end_match_pos,
|
||||
FALSE);
|
||||
|
||||
// The regex pattern may include '\C' or '\c'. First, try matching the
|
||||
// buffer word as-is. If it doesn't match, try again with the lowercase
|
||||
// version of the word to handle smartcase behavior.
|
||||
if (match == NULL || !is_regex_match(match, full_match))
|
||||
if (exacttext)
|
||||
match = full_match;
|
||||
else
|
||||
{
|
||||
vim_free(match);
|
||||
match = concat_pattern_with_buffer_match(pat, pat_len,
|
||||
&end_match_pos, TRUE);
|
||||
// Construct a new match from completed word appended to pattern itself
|
||||
match = concat_pattern_with_buffer_match(pat, pat_len, &end_match_pos,
|
||||
FALSE);
|
||||
|
||||
// The regex pattern may include '\C' or '\c'. First, try matching the
|
||||
// buffer word as-is. If it doesn't match, try again with the lowercase
|
||||
// version of the word to handle smartcase behavior.
|
||||
if (match == NULL || !is_regex_match(match, full_match))
|
||||
{
|
||||
vim_free(match);
|
||||
vim_free(full_match);
|
||||
continue;
|
||||
match = concat_pattern_with_buffer_match(pat, pat_len,
|
||||
&end_match_pos, TRUE);
|
||||
if (match == NULL || !is_regex_match(match, full_match))
|
||||
{
|
||||
vim_free(match);
|
||||
vim_free(full_match);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
vim_free(full_match);
|
||||
}
|
||||
vim_free(full_match);
|
||||
|
||||
// Include this match if it is not a duplicate
|
||||
for (int i = 0; i < ga.ga_len; ++i)
|
||||
|
@ -375,8 +375,9 @@ typedef enum {
|
||||
// flags for the 'wildoptions' option
|
||||
// each defined char should be unique over all values.
|
||||
#define WOP_FUZZY 'z'
|
||||
#define WOP_TAGFILE 't'
|
||||
#define WOP_TAGFILE 'g'
|
||||
#define WOP_PUM 'p'
|
||||
#define WOP_EXACTTEXT 'x'
|
||||
|
||||
// arguments for can_bs()
|
||||
// each defined char should be unique over all values
|
||||
|
@ -103,7 +103,7 @@ static char *(p_ttym_values[]) = {"xterm", "xterm2", "dec", "netterm", "jsbterm"
|
||||
static char *(p_ve_values[]) = {"block", "insert", "all", "onemore", "none", "NONE", NULL};
|
||||
// Note: Keep this in sync with check_opt_wim()
|
||||
static char *(p_wim_values[]) = {"full", "longest", "list", "lastused", "noselect", NULL};
|
||||
static char *(p_wop_values[]) = {"fuzzy", "tagfile", "pum", NULL};
|
||||
static char *(p_wop_values[]) = {"fuzzy", "tagfile", "pum", "exacttext", NULL};
|
||||
#ifdef FEAT_WAK
|
||||
static char *(p_wak_values[]) = {"yes", "menu", "no", NULL};
|
||||
#endif
|
||||
|
@ -4472,6 +4472,7 @@ func Test_search_complete()
|
||||
" Match case correctly
|
||||
%d
|
||||
call setline(1, ["foobar", "Foobar", "fooBAr", "FooBARR"])
|
||||
|
||||
call feedkeys("gg/f\<tab>\<f9>", 'tx')
|
||||
call assert_equal(['fooBAr', 'foobar'], g:compl_info.matches)
|
||||
call feedkeys("gg/Fo\<tab>\<f9>", 'tx')
|
||||
@ -4480,6 +4481,7 @@ func Test_search_complete()
|
||||
call assert_equal({}, g:compl_info)
|
||||
call feedkeys("gg/\\cFo\<tab>\<f9>", 'tx')
|
||||
call assert_equal(['\cFoobar', '\cFooBAr', '\cFooBARR'], g:compl_info.matches)
|
||||
|
||||
set ignorecase
|
||||
call feedkeys("gg/f\<tab>\<f9>", 'tx')
|
||||
call assert_equal(['foobar', 'fooBAr', 'fooBARR'], g:compl_info.matches)
|
||||
@ -4489,6 +4491,7 @@ func Test_search_complete()
|
||||
call assert_equal(['FOobar', 'FOoBAr', 'FOoBARR'], g:compl_info.matches)
|
||||
call feedkeys("gg/\\Cfo\<tab>\<f9>", 'tx')
|
||||
call assert_equal(['\CfooBAr', '\Cfoobar'], g:compl_info.matches)
|
||||
|
||||
set smartcase
|
||||
call feedkeys("gg/f\<tab>\<f9>", 'tx')
|
||||
call assert_equal(['foobar', 'fooBAr', 'foobarr'], g:compl_info.matches)
|
||||
@ -4496,16 +4499,42 @@ func Test_search_complete()
|
||||
call assert_equal(['Foobar', 'FooBARR'], g:compl_info.matches)
|
||||
call feedkeys("gg/FO\<tab>\<f9>", 'tx')
|
||||
call assert_equal({}, g:compl_info)
|
||||
call feedkeys("gg/foob\<tab>\<f9>", 'tx')
|
||||
call assert_equal(['foobar', 'foobarr'], g:compl_info.matches)
|
||||
call feedkeys("gg/\\Cfo\<tab>\<f9>", 'tx')
|
||||
call assert_equal(['\CfooBAr', '\Cfoobar'], g:compl_info.matches)
|
||||
call feedkeys("gg/\\cFo\<tab>\<f9>", 'tx')
|
||||
call assert_equal(['\cFoobar', '\cFooBAr', '\cFooBARR'], g:compl_info.matches)
|
||||
|
||||
set wildoptions+=exacttext ignorecase& smartcase&
|
||||
call feedkeys("gg/F\<tab>\<f9>", 'tx')
|
||||
call assert_equal(['Foobar', 'FooBARR'], g:compl_info.matches)
|
||||
call feedkeys("gg/foob\<tab>\<f9>", 'tx')
|
||||
call assert_equal([], g:compl_info.matches)
|
||||
call feedkeys("gg/r\\n.\<tab>\<f9>", 'tx')
|
||||
call assert_equal(['r\nFoobar', 'r\nfooBAr', 'r\nFooBARR'], g:compl_info.matches)
|
||||
|
||||
set ignorecase
|
||||
call feedkeys("gg/F\<tab>\<f9>", 'tx')
|
||||
call assert_equal(['Foobar', 'fooBAr', 'FooBARR', 'foobar'], g:compl_info.matches)
|
||||
call feedkeys("gg/R\\n.\<tab>\<f9>", 'tx')
|
||||
call assert_equal(['r\nFoobar', 'r\nfooBAr', 'r\nFooBARR'], g:compl_info.matches)
|
||||
|
||||
set smartcase
|
||||
call feedkeys("gg/f\<tab>\<f9>", 'tx')
|
||||
call assert_equal(['Foobar', 'fooBAr', 'FooBARR', 'foobar'], g:compl_info.matches)
|
||||
call feedkeys("gg/foob\<tab>\<f9>", 'tx')
|
||||
call assert_equal(['Foobar', 'fooBAr', 'FooBARR', 'foobar'], g:compl_info.matches)
|
||||
call feedkeys("gg/R\\n.\<tab>\<f9>", 'tx')
|
||||
call assert_equal({}, g:compl_info)
|
||||
call feedkeys("gg/r\\n.*\\n\<tab>\<f9>", 'tx')
|
||||
call assert_equal(['r\nFoobar\nfooBAr', 'r\nfooBAr\nFooBARR'], g:compl_info.matches)
|
||||
|
||||
bw!
|
||||
call test_override("char_avail", 0)
|
||||
delfunc GetComplInfo
|
||||
unlet! g:compl_info
|
||||
set wildcharm=0 incsearch& ignorecase& smartcase&
|
||||
set wildcharm=0 incsearch& ignorecase& smartcase& wildoptions&
|
||||
endfunc
|
||||
|
||||
func Test_search_wildmenu_screendump()
|
||||
|
@ -719,6 +719,8 @@ static char *(features[]) =
|
||||
|
||||
static int included_patches[] =
|
||||
{ /* Add new patch number below this line */
|
||||
/**/
|
||||
1526,
|
||||
/**/
|
||||
1525,
|
||||
/**/
|
||||
|
Reference in New Issue
Block a user