mirror of
https://github.com/vim/vim
synced 2025-07-15 16:51:57 +00:00
patch 9.1.1490: 'wildchar' does not work in search contexts
Problem: 'wildchar' does not work in search contexts Solution: implement search completion when 'wildchar' is typed (Girish Palya). This change enhances Vim's command-line completion by extending 'wildmode' behavior to search pattern contexts, including: - '/' and '?' search commands - ':s', ':g', ':v', and ':vim' commands Completions preserve the exact regex pattern typed by the user, appending the completed word directly to the original input. This ensures that all regex elements — such as '<', '^', grouping brackets '()', wildcards '\*', '.', and other special characters — remain intact and in their original positions. --- **Use Case** While searching (using `/` or `?`) for lines containing a pattern like `"foobar"`, you can now type a partial pattern (e.g., `/f`) followed by a trigger key (`wildchar`) to open a **popup completion menu** showing all matching words. This offers two key benefits: 1. **Precision**: Select the exact word you're looking for without typing it fully. 2. **Memory aid**: When you can’t recall a full function or variable name, typing a few letters helps you visually identify and complete the correct symbol. --- **What’s New** Completion is now supported in the following contexts: - `/` and `?` search commands - `:s`, `:g`, `:v`, and `:vimgrep` ex-commands --- **Design Notes** - While `'wildchar'` (usually `<Tab>`) triggers completion, you'll have to use `<CTRL-V><Tab>` or "\t" to search for a literal tab. - **Responsiveness**: Search remains responsive because it checks for user input frequently. --- **Try It Out** Basic setup using the default `<Tab>` as the completion trigger: ```vim set wim=noselect,full wop=pum wmnu ``` Now type: ``` /foo<Tab> ``` This opens a completion popup for matches containing "foo". For matches beginning with "foo" type `/\<foo<Tab>`. --- **Optional: Autocompletion** For automatic popup menu completion as you type in search or `:` commands, include this in your `.vimrc`: ```vim vim9script set wim=noselect:lastused,full wop=pum wcm=<C-@> wmnu autocmd CmdlineChanged [:/?] CmdComplete() def CmdComplete() var [cmdline, curpos, cmdmode] = [getcmdline(), getcmdpos(), expand('<afile>') == ':'] var trigger_char = '\%(\w\|[*/:.-]\)$' var not_trigger_char = '^\%(\d\|,\|+\|-\)\+$' # Exclude numeric range if getchar(1, {number: true}) == 0 # Typehead is empty, no more pasted input && !wildmenumode() && curpos == cmdline->len() + 1 && (!cmdmode || (cmdline =~ trigger_char && cmdline !~ not_trigger_char)) SkipCmdlineChanged() feedkeys("\<C-@>", "t") timer_start(0, (_) => getcmdline()->substitute('\%x00', '', 'ge')->setcmdline()) # Remove <C-@> endif enddef def SkipCmdlineChanged(key = ''): string set ei+=CmdlineChanged timer_start(0, (_) => execute('set ei-=CmdlineChanged')) return key == '' ? '' : ((wildmenumode() ? "\<C-E>" : '') .. key) enddef **Optional: Preserve history recall behavior** cnoremap <expr> <Up> SkipCmdlineChanged("\<Up>") cnoremap <expr> <Down> SkipCmdlineChanged("\<Down>") **Optional: Customize popup height** autocmd CmdlineEnter : set bo+=error | exec $'set ph={max([10, winheight(0) - 4])}' autocmd CmdlineEnter [/?] set bo+=error | set ph=8 autocmd CmdlineLeave [:/?] set bo-=error ph& ``` closes: #17570 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
1fa3f0c215
commit
6b49fba8c8
@ -1,4 +1,4 @@
|
||||
*cmdline.txt* For Vim version 9.1. Last change: 2025 Mar 08
|
||||
*cmdline.txt* For Vim version 9.1. Last change: 2025 Jun 28
|
||||
|
||||
|
||||
VIM REFERENCE MANUAL by Bram Moolenaar
|
||||
@ -415,7 +415,7 @@ CTRL-D List names that match the pattern in front of the cursor.
|
||||
to the end.
|
||||
The 'wildoptions' option can be set to "tagfile" to list the
|
||||
file of matching tags.
|
||||
*c_CTRL-I* *c_wildchar* *c_<Tab>*
|
||||
*c_CTRL-I* *c_wildchar* *c_<Tab>* */_<Tab>*
|
||||
'wildchar' option
|
||||
A match is done on the pattern in front of the cursor. The
|
||||
match (if there are several, the first match) is inserted
|
||||
@ -425,6 +425,10 @@ CTRL-D List names that match the pattern in front of the cursor.
|
||||
again and there were multiple matches, the next
|
||||
match is inserted. After the last match, the first is used
|
||||
again (wrap around).
|
||||
|
||||
In search context use <CTRL-V><Tab> or "\t" to search for a
|
||||
literal <Tab> instead of triggering completion.
|
||||
|
||||
The behavior can be changed with the 'wildmode' option.
|
||||
*c_<S-Tab>*
|
||||
<S-Tab> Like 'wildchar' or <Tab>, but begin with the last match and
|
||||
@ -458,7 +462,7 @@ CTRL-G When 'incsearch' is set, entering a search pattern for "/" or
|
||||
"?" and the current match is displayed then CTRL-G will move
|
||||
to the next match (does not take |search-offset| into account)
|
||||
Use CTRL-T to move to the previous match. Hint: on a regular
|
||||
keyboard T is above G.
|
||||
keyboard G is below T.
|
||||
*c_CTRL-T* */_CTRL-T*
|
||||
CTRL-T When 'incsearch' is set, entering a search pattern for "/" or
|
||||
"?" and the current match is displayed then CTRL-T will move
|
||||
|
@ -9752,7 +9752,10 @@ A jump table for the options with a short description can be found at |Q_op|.
|
||||
:set wc=X
|
||||
:set wc=^I
|
||||
:set wc=<Tab>
|
||||
< NOTE: This option is set to the Vi default value when 'compatible' is
|
||||
< '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".
|
||||
NOTE: This option is set to the Vi default value when 'compatible' is
|
||||
set and to the Vim default value when 'compatible' is reset.
|
||||
|
||||
*'wildcharm'* *'wcm'*
|
||||
|
@ -1815,6 +1815,7 @@ $quote eval.txt /*$quote*
|
||||
/\{- pattern.txt /*\/\\{-*
|
||||
/\~ pattern.txt /*\/\\~*
|
||||
/^ pattern.txt /*\/^*
|
||||
/_<Tab> cmdline.txt /*\/_<Tab>*
|
||||
/_CTRL-G cmdline.txt /*\/_CTRL-G*
|
||||
/_CTRL-L cmdline.txt /*\/_CTRL-L*
|
||||
/_CTRL-T cmdline.txt /*\/_CTRL-T*
|
||||
|
@ -1,4 +1,4 @@
|
||||
*version9.txt* For Vim version 9.1. Last change: 2025 Jun 27
|
||||
*version9.txt* For Vim version 9.1. Last change: 2025 Jun 28
|
||||
|
||||
|
||||
VIM REFERENCE MANUAL by Bram Moolenaar
|
||||
@ -41632,6 +41632,8 @@ Completion: ~
|
||||
- add ":filetype" command completion
|
||||
- add "filetypecmd" completion type for |getcompletion()|
|
||||
- 'smartcase' applies to completion filtering
|
||||
- 'wildchar' enables completion in search contexts using |/|, |?|, |:g|, |:v|
|
||||
and |:vimgrep| commands
|
||||
|
||||
Options: ~
|
||||
- the default for 'commentstring' contains whitespace padding to have
|
||||
|
336
src/cmdexpand.c
336
src/cmdexpand.c
@ -14,6 +14,8 @@
|
||||
#include "vim.h"
|
||||
|
||||
static int cmd_showtail; // Only show path tail in lists ?
|
||||
static int may_expand_pattern = FALSE;
|
||||
static pos_T pre_incsearch_pos; // Cursor position when incsearch started
|
||||
|
||||
static void set_context_for_wildcard_arg(exarg_T *eap, char_u *arg, int usefilter, expand_T *xp, int *complp);
|
||||
static int ExpandFromContext(expand_T *xp, char_u *, char_u ***, int *, int);
|
||||
@ -24,6 +26,7 @@ static int expand_shellcmd(char_u *filepat, char_u ***matches, int *numMatches,
|
||||
static int ExpandUserDefined(char_u *pat, expand_T *xp, regmatch_T *regmatch, char_u ***matches, int *numMatches);
|
||||
static int ExpandUserList(expand_T *xp, char_u ***matches, int *numMatches);
|
||||
#endif
|
||||
static int expand_pattern_in_buf(char_u *pat, int dir, char_u ***matches, int *numMatches);
|
||||
|
||||
// "compl_match_array" points the currently displayed list of entries in the
|
||||
// popup menu. It is NULL when there is no popup menu.
|
||||
@ -233,6 +236,8 @@ nextwild(
|
||||
|
||||
if (xp->xp_numfiles == -1)
|
||||
{
|
||||
may_expand_pattern = options & WILD_MAY_EXPAND_PATTERN;
|
||||
pre_incsearch_pos = xp->xp_pre_incsearch_pos;
|
||||
#ifdef FEAT_EVAL
|
||||
if (ccline->input_fn && ccline->xp_context == EXPAND_COMMANDS)
|
||||
{
|
||||
@ -277,8 +282,9 @@ nextwild(
|
||||
}
|
||||
else
|
||||
{
|
||||
if (cmdline_fuzzy_completion_supported(xp))
|
||||
// If fuzzy matching, don't modify the search string
|
||||
if (cmdline_fuzzy_completion_supported(xp)
|
||||
|| xp->xp_context == EXPAND_PATTERN_IN_BUF)
|
||||
// Don't modify the search string
|
||||
p1 = vim_strnsave(xp->xp_pattern, xp->xp_pattern_len);
|
||||
else
|
||||
p1 = addstar(xp->xp_pattern, xp->xp_pattern_len, xp->xp_context);
|
||||
@ -292,12 +298,11 @@ nextwild(
|
||||
WILD_HOME_REPLACE|WILD_ADD_SLASH|WILD_SILENT;
|
||||
if (use_options & WILD_KEEP_SOLE_ITEM)
|
||||
use_options &= ~WILD_KEEP_SOLE_ITEM;
|
||||
|
||||
if (escape)
|
||||
use_options |= WILD_ESCAPE;
|
||||
|
||||
if (p_wic)
|
||||
use_options += WILD_ICASE;
|
||||
|
||||
p2 = ExpandOne(xp, p1,
|
||||
vim_strnsave(&ccline->cmdbuff[i], xp->xp_pattern_len),
|
||||
use_options, type);
|
||||
@ -495,12 +500,14 @@ cmdline_compl_is_fuzzy(void)
|
||||
|
||||
/*
|
||||
* Return the number of characters that should be skipped in a status match.
|
||||
* These are backslashes used for escaping. Do show backslashes in help tags.
|
||||
* These are backslashes used for escaping. Do show backslashes in help tags
|
||||
* and in search pattern completion matches.
|
||||
*/
|
||||
static int
|
||||
skip_status_match_char(expand_T *xp, char_u *s)
|
||||
{
|
||||
if ((rem_backslash(s) && xp->xp_context != EXPAND_HELP)
|
||||
if ((rem_backslash(s) && xp->xp_context != EXPAND_HELP
|
||||
&& xp->xp_context != EXPAND_PATTERN_IN_BUF)
|
||||
#ifdef FEAT_MENU
|
||||
|| ((xp->xp_context == EXPAND_MENUS
|
||||
|| xp->xp_context == EXPAND_MENUNAMES)
|
||||
@ -1598,23 +1605,40 @@ addstar(
|
||||
* names in expressions, eg :while s^I
|
||||
* EXPAND_ENV_VARS Complete environment variable names
|
||||
* EXPAND_USER Complete user names
|
||||
* EXPAND_PATTERN_IN_BUF Complete pattern in '/', '?', ':s', ':g', etc.
|
||||
*/
|
||||
void
|
||||
set_expand_context(expand_T *xp)
|
||||
{
|
||||
cmdline_info_T *ccline = get_cmdline_info();
|
||||
cmdline_info_T *ccline = get_cmdline_info();
|
||||
|
||||
// only expansion for ':', '>' and '=' command-lines
|
||||
// Handle search commands: '/' or '?'
|
||||
if ((ccline->cmdfirstc == '/' || ccline->cmdfirstc == '?')
|
||||
&& may_expand_pattern)
|
||||
{
|
||||
xp->xp_context = EXPAND_PATTERN_IN_BUF;
|
||||
xp->xp_search_dir = (ccline->cmdfirstc == '/') ? FORWARD : BACKWARD;
|
||||
xp->xp_pattern = ccline->cmdbuff;
|
||||
xp->xp_pattern_len = ccline->cmdpos;
|
||||
#ifdef FEAT_SEARCH_EXTRA
|
||||
search_first_line = 0; // Search entire buffer
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
|
||||
// Only handle ':', '>', or '=' command-lines, or expression input
|
||||
if (ccline->cmdfirstc != ':'
|
||||
#ifdef FEAT_EVAL
|
||||
&& ccline->cmdfirstc != '>' && ccline->cmdfirstc != '='
|
||||
&& !ccline->input_fn
|
||||
#endif
|
||||
)
|
||||
)
|
||||
{
|
||||
xp->xp_context = EXPAND_NOTHING;
|
||||
return;
|
||||
}
|
||||
|
||||
// Fallback to command-line expansion
|
||||
set_cmd_context(xp, ccline->cmdbuff, ccline->cmdlen, ccline->cmdpos, TRUE);
|
||||
}
|
||||
|
||||
@ -2206,6 +2230,34 @@ set_context_in_filetype_cmd(expand_T *xp, char_u *arg)
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
* Sets the completion context for commands that involve a search pattern
|
||||
* and a line range (e.g., :s, :g, :v).
|
||||
*/
|
||||
static void
|
||||
set_context_with_pattern(expand_T *xp)
|
||||
{
|
||||
int skiplen = 0;
|
||||
cmdline_info_T *ccline = get_cmdline_info();
|
||||
#ifdef FEAT_SEARCH_EXTRA
|
||||
int dummy, patlen, retval;
|
||||
|
||||
++emsg_off;
|
||||
retval = parse_pattern_and_range(&pre_incsearch_pos, &dummy, &skiplen,
|
||||
&patlen);
|
||||
--emsg_off;
|
||||
|
||||
// Check if cursor is within search pattern
|
||||
if (!retval || ccline->cmdpos <= skiplen
|
||||
|| ccline->cmdpos > skiplen + patlen)
|
||||
return;
|
||||
#endif
|
||||
|
||||
xp->xp_pattern = ccline->cmdbuff + skiplen;
|
||||
xp->xp_pattern_len = ccline->cmdpos - skiplen;
|
||||
xp->xp_context = EXPAND_PATTERN_IN_BUF;
|
||||
xp->xp_search_dir = FORWARD;
|
||||
}
|
||||
|
||||
/*
|
||||
* Set the completion context in 'xp' for command 'cmd' with index 'cmdidx'.
|
||||
@ -2225,6 +2277,8 @@ set_context_by_cmdname(
|
||||
int compl,
|
||||
int forceit)
|
||||
{
|
||||
char_u *nextcmd;
|
||||
|
||||
switch (cmdidx)
|
||||
{
|
||||
case CMD_find:
|
||||
@ -2307,10 +2361,18 @@ set_context_by_cmdname(
|
||||
|
||||
case CMD_global:
|
||||
case CMD_vglobal:
|
||||
return find_cmd_after_global_cmd(arg);
|
||||
nextcmd = find_cmd_after_global_cmd(arg);
|
||||
if (!nextcmd && may_expand_pattern)
|
||||
set_context_with_pattern(xp);
|
||||
return nextcmd;
|
||||
|
||||
case CMD_and:
|
||||
case CMD_substitute:
|
||||
return find_cmd_after_substitute_cmd(arg);
|
||||
nextcmd = find_cmd_after_substitute_cmd(arg);
|
||||
if (!nextcmd && may_expand_pattern)
|
||||
set_context_with_pattern(xp);
|
||||
return nextcmd;
|
||||
|
||||
case CMD_isearch:
|
||||
case CMD_dsearch:
|
||||
case CMD_ilist:
|
||||
@ -3318,6 +3380,9 @@ ExpandFromContext(
|
||||
return ExpandPackAddDir(pat, numMatches, matches);
|
||||
if (xp->xp_context == EXPAND_RUNTIME)
|
||||
return expand_runtime_cmd(pat, numMatches, matches);
|
||||
if (xp->xp_context == EXPAND_PATTERN_IN_BUF)
|
||||
return expand_pattern_in_buf(pat, xp->xp_search_dir,
|
||||
matches, numMatches);
|
||||
|
||||
// When expanding a function name starting with s:, match the <SNR>nr_
|
||||
// prefix.
|
||||
@ -4241,6 +4306,11 @@ wildmenu_cleanup(cmdline_info_T *cclp UNUSED)
|
||||
RedrawingDisabled = 0;
|
||||
#endif
|
||||
|
||||
#if defined(FEAT_SEARCH_EXTRA) || defined(PROTO)
|
||||
// Clear highlighting applied during wildmenu activity
|
||||
set_no_hlsearch(TRUE);
|
||||
#endif
|
||||
|
||||
if (wild_menu_showing == WM_SCROLLED)
|
||||
{
|
||||
// Entered command line, move it up
|
||||
@ -4438,3 +4508,247 @@ f_cmdcomplete_info(typval_T *argvars UNUSED, typval_T *rettv)
|
||||
}
|
||||
}
|
||||
#endif // FEAT_EVAL
|
||||
|
||||
/*
|
||||
* Copy a substring from the current buffer (curbuf), spanning from the given
|
||||
* 'start' position to the word boundary after 'end' position.
|
||||
* The copied string is stored in '*match', and the actual end position of the
|
||||
* matched text is returned in '*match_end'.
|
||||
*/
|
||||
static int
|
||||
copy_substring_from_pos(pos_T *start, pos_T *end, char_u **match,
|
||||
pos_T *match_end)
|
||||
{
|
||||
char_u *word_end;
|
||||
char_u *line, *start_line, *end_line;
|
||||
int segment_len;
|
||||
linenr_T lnum;
|
||||
garray_T ga;
|
||||
|
||||
if (start->lnum > end->lnum
|
||||
|| (start->lnum == end->lnum && start->col >= end->col))
|
||||
return FAIL; // invalid range
|
||||
|
||||
// Get line pointers
|
||||
start_line = ml_get(start->lnum);
|
||||
end_line = ml_get(end->lnum);
|
||||
|
||||
// Use a growable string (ga)
|
||||
ga_init2(&ga, 1, 128);
|
||||
|
||||
// Append start line from start->col to end
|
||||
char_u *start_ptr = start_line + start->col;
|
||||
int is_single_line = start->lnum == end->lnum;
|
||||
|
||||
segment_len = is_single_line ? (end->col - start->col)
|
||||
: (int)STRLEN(start_ptr);
|
||||
if (ga_grow(&ga, segment_len + 1) != OK)
|
||||
return FAIL;
|
||||
ga_concat_len(&ga, start_ptr, segment_len);
|
||||
if (!is_single_line)
|
||||
ga_append(&ga, '\n');
|
||||
|
||||
// Append full lines between start and end
|
||||
if (!is_single_line)
|
||||
for (lnum = start->lnum + 1; lnum < end->lnum; lnum++)
|
||||
{
|
||||
line = ml_get(lnum);
|
||||
if (ga_grow(&ga, ml_get_len(lnum) + 1) != OK)
|
||||
return FAIL;
|
||||
ga_concat(&ga, line);
|
||||
ga_append(&ga, '\n');
|
||||
}
|
||||
|
||||
// Append partial end line (up to word end)
|
||||
word_end = find_word_end(end_line + end->col);
|
||||
segment_len = (int)(word_end - end_line);
|
||||
if (ga_grow(&ga, segment_len) != OK)
|
||||
return FAIL;
|
||||
ga_concat_len(&ga, end_line + (is_single_line ? end->col : 0),
|
||||
segment_len - (is_single_line ? end->col : 0));
|
||||
|
||||
// Null-terminate
|
||||
if (ga_grow(&ga, 1) != OK)
|
||||
return FAIL;
|
||||
ga_append(&ga, NUL);
|
||||
|
||||
*match = (char_u *)ga.ga_data;
|
||||
match_end->lnum = end->lnum;
|
||||
match_end->col = segment_len;
|
||||
|
||||
return OK;
|
||||
}
|
||||
|
||||
/*
|
||||
* Search for strings matching "pat" in the specified range and return them.
|
||||
* Returns OK on success, FAIL otherwise.
|
||||
*/
|
||||
static int
|
||||
expand_pattern_in_buf(
|
||||
char_u *pat, // pattern to match
|
||||
int dir, // direction: FORWARD or BACKWARD
|
||||
char_u ***matches, // return: array with matched strings
|
||||
int *numMatches) // return: number of matches
|
||||
{
|
||||
pos_T cur_match_pos, prev_match_pos, end_match_pos, word_end_pos;
|
||||
garray_T ga;
|
||||
int found_new_match;
|
||||
int looped_around = FALSE;
|
||||
int pat_len, match_len;
|
||||
int has_range = FALSE;
|
||||
int compl_started = FALSE;
|
||||
int search_flags, i;
|
||||
char_u *match, *line, *word_end;
|
||||
regmatch_T regmatch;
|
||||
|
||||
#ifdef FEAT_SEARCH_EXTRA
|
||||
has_range = search_first_line != 0;
|
||||
#endif
|
||||
|
||||
*matches = NULL;
|
||||
*numMatches = 0;
|
||||
|
||||
if (pat == NULL || *pat == NUL)
|
||||
return FAIL;
|
||||
|
||||
pat_len = (int)STRLEN(pat);
|
||||
CLEAR_FIELD(cur_match_pos);
|
||||
CLEAR_FIELD(prev_match_pos);
|
||||
#ifdef FEAT_SEARCH_EXTRA
|
||||
if (has_range)
|
||||
cur_match_pos.lnum = search_first_line;
|
||||
else
|
||||
#endif
|
||||
cur_match_pos = pre_incsearch_pos;
|
||||
|
||||
search_flags = SEARCH_OPT | SEARCH_NOOF | SEARCH_PEEK | SEARCH_NFMSG
|
||||
| (has_range ? SEARCH_START : 0);
|
||||
|
||||
regmatch.regprog = vim_regcomp(pat, RE_MAGIC + RE_STRING);
|
||||
if (regmatch.regprog == NULL)
|
||||
return FAIL;
|
||||
regmatch.rm_ic = p_ic;
|
||||
|
||||
ga_init2(&ga, sizeof(char_u *), 10); // Use growable array of char_u*
|
||||
|
||||
for (;;)
|
||||
{
|
||||
++emsg_off;
|
||||
++msg_silent;
|
||||
found_new_match = searchit(NULL, curbuf, &cur_match_pos,
|
||||
&end_match_pos, dir, pat, pat_len, 1L,
|
||||
search_flags, RE_LAST, NULL);
|
||||
--msg_silent;
|
||||
--emsg_off;
|
||||
|
||||
if (found_new_match == FAIL)
|
||||
break;
|
||||
|
||||
#ifdef FEAT_SEARCH_EXTRA
|
||||
// If in range mode, check if match is within the range
|
||||
if (has_range && (cur_match_pos.lnum < search_first_line
|
||||
|| cur_match_pos.lnum > search_last_line))
|
||||
break;
|
||||
#endif
|
||||
|
||||
if (compl_started)
|
||||
{
|
||||
// If we've looped back to an earlier match, stop
|
||||
if ((dir == FORWARD
|
||||
&& (cur_match_pos.lnum < prev_match_pos.lnum
|
||||
|| (cur_match_pos.lnum == prev_match_pos.lnum
|
||||
&& cur_match_pos.col <= prev_match_pos.col)))
|
||||
|| (dir == BACKWARD
|
||||
&& (cur_match_pos.lnum > prev_match_pos.lnum
|
||||
|| (cur_match_pos.lnum == prev_match_pos.lnum
|
||||
&& cur_match_pos.col >= prev_match_pos.col))))
|
||||
{
|
||||
if (looped_around)
|
||||
break;
|
||||
else
|
||||
looped_around = TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
compl_started = TRUE;
|
||||
prev_match_pos = cur_match_pos;
|
||||
|
||||
// Abort if user typed a character or interrupted
|
||||
if (char_avail() || got_int)
|
||||
{
|
||||
if (got_int)
|
||||
{
|
||||
(void)vpeekc(); // Remove <C-C> from input stream
|
||||
got_int = FALSE; // Don't abandon the command line
|
||||
}
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
// searchit() can return line number +1 past the last line when
|
||||
// searching for "foo\n" if "foo" is at end of buffer.
|
||||
if (end_match_pos.lnum > curbuf->b_ml.ml_line_count)
|
||||
{
|
||||
cur_match_pos.lnum = 1;
|
||||
cur_match_pos.col = 0;
|
||||
cur_match_pos.coladd = 0;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Extract the matching text prepended to completed word
|
||||
if (!copy_substring_from_pos(&cur_match_pos, &end_match_pos, &match,
|
||||
&word_end_pos))
|
||||
break;
|
||||
|
||||
// Verify that the constructed match actually matches the pattern with
|
||||
// correct case sensitivity
|
||||
if (!vim_regexec_nl(®match, match, (colnr_T)0))
|
||||
{
|
||||
vim_free(match);
|
||||
continue;
|
||||
}
|
||||
vim_free(match);
|
||||
|
||||
// Construct a new match from completed word appended to pattern itself
|
||||
line = ml_get(end_match_pos.lnum);
|
||||
word_end = find_word_end(line + end_match_pos.col); // col starts from 0
|
||||
match_len = (int)(word_end - (line + end_match_pos.col));
|
||||
match = alloc(match_len + pat_len + 1); // +1 for NUL
|
||||
if (match == NULL)
|
||||
goto cleanup;
|
||||
mch_memmove(match, pat, pat_len);
|
||||
if (match_len > 0)
|
||||
mch_memmove(match + pat_len, line + end_match_pos.col, match_len);
|
||||
match[pat_len + match_len] = NUL;
|
||||
|
||||
// Include this match if it is not a duplicate
|
||||
for (i = 0; i < ga.ga_len; ++i)
|
||||
{
|
||||
if (STRCMP(match, ((char_u **)ga.ga_data)[i]) == 0)
|
||||
{
|
||||
VIM_CLEAR(match);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (match != NULL)
|
||||
{
|
||||
if (ga_grow(&ga, 1) == FAIL)
|
||||
goto cleanup;
|
||||
((char_u **)ga.ga_data)[ga.ga_len++] = match;
|
||||
if (ga.ga_len > TAG_MANY)
|
||||
break;
|
||||
}
|
||||
if (has_range)
|
||||
cur_match_pos = word_end_pos;
|
||||
}
|
||||
|
||||
vim_regfree(regmatch.regprog);
|
||||
|
||||
*matches = (char_u **)ga.ga_data;
|
||||
*numMatches = ga.ga_len;
|
||||
return OK;
|
||||
|
||||
cleanup:
|
||||
vim_regfree(regmatch.regprog);
|
||||
ga_clear_strings(&ga);
|
||||
return FAIL;
|
||||
}
|
||||
|
192
src/ex_getln.c
192
src/ex_getln.c
@ -204,67 +204,55 @@ set_search_match(pos_T *t)
|
||||
}
|
||||
|
||||
/*
|
||||
* Return TRUE when 'incsearch' highlighting is to be done.
|
||||
* Sets search_first_line and search_last_line to the address range.
|
||||
* May change the last search pattern.
|
||||
* Parses the :[range]s/foo like commands and returns details needed for
|
||||
* incsearch and wildmenu completion.
|
||||
* Returns TRUE if pattern is valid.
|
||||
* Sets skiplen, patlen, search_first_line, and search_last_line.
|
||||
*/
|
||||
static int
|
||||
do_incsearch_highlighting(
|
||||
int firstc,
|
||||
int *search_delim,
|
||||
incsearch_state_T *is_state,
|
||||
int *skiplen,
|
||||
int *patlen)
|
||||
int
|
||||
parse_pattern_and_range(
|
||||
pos_T *incsearch_start,
|
||||
int *search_delim,
|
||||
int *skiplen,
|
||||
int *patlen)
|
||||
{
|
||||
char_u *cmd;
|
||||
char_u *cmd, *p, *end;
|
||||
cmdmod_T dummy_cmdmod;
|
||||
char_u *p;
|
||||
int delim_optional = FALSE;
|
||||
int delim;
|
||||
char_u *end;
|
||||
char *dummy;
|
||||
exarg_T ea;
|
||||
pos_T save_cursor;
|
||||
int delim_optional = FALSE;
|
||||
int delim;
|
||||
int use_last_pat;
|
||||
int retval = FALSE;
|
||||
magic_T magic = 0;
|
||||
char *dummy;
|
||||
|
||||
*skiplen = 0;
|
||||
*patlen = ccline.cmdlen;
|
||||
|
||||
if (!p_is || cmd_silent)
|
||||
return FALSE;
|
||||
|
||||
// by default search all lines
|
||||
// Default range
|
||||
search_first_line = 0;
|
||||
search_last_line = MAXLNUM;
|
||||
|
||||
if (firstc == '/' || firstc == '?')
|
||||
{
|
||||
*search_delim = firstc;
|
||||
return TRUE;
|
||||
}
|
||||
if (firstc != ':')
|
||||
return FALSE;
|
||||
|
||||
++emsg_off;
|
||||
CLEAR_FIELD(ea);
|
||||
ea.line1 = 1;
|
||||
ea.line2 = 1;
|
||||
ea.cmd = ccline.cmdbuff;
|
||||
ea.addr_type = ADDR_LINES;
|
||||
|
||||
// Skip over command modifiers
|
||||
parse_command_modifiers(&ea, &dummy, &dummy_cmdmod, TRUE);
|
||||
|
||||
// Skip over the range to find the command.
|
||||
cmd = skip_range(ea.cmd, TRUE, NULL);
|
||||
if (vim_strchr((char_u *)"sgvl", *cmd) == NULL)
|
||||
goto theend;
|
||||
|
||||
// Skip over "substitute" to find the pattern separator.
|
||||
if (vim_strchr((char_u *)"sgvl", *cmd) == NULL)
|
||||
return FALSE;
|
||||
|
||||
// Skip over command name to find pattern separator
|
||||
for (p = cmd; ASCII_ISALPHA(*p); ++p)
|
||||
;
|
||||
if (*skipwhite(p) == NUL)
|
||||
goto theend;
|
||||
return FALSE;
|
||||
|
||||
if (STRNCMP(cmd, "substitute", p - cmd) == 0
|
||||
|| STRNCMP(cmd, "smagic", p - cmd) == 0
|
||||
@ -285,83 +273,113 @@ do_incsearch_highlighting(
|
||||
while (ASCII_ISALPHA(*(p = skipwhite(p))))
|
||||
++p;
|
||||
if (*p == NUL)
|
||||
goto theend;
|
||||
return FALSE;
|
||||
}
|
||||
else if (STRNCMP(cmd, "vimgrep", MAX(p - cmd, 3)) == 0
|
||||
|| STRNCMP(cmd, "vimgrepadd", MAX(p - cmd, 8)) == 0
|
||||
|| STRNCMP(cmd, "lvimgrep", MAX(p - cmd, 2)) == 0
|
||||
|| STRNCMP(cmd, "lvimgrepadd", MAX(p - cmd, 9)) == 0
|
||||
|| STRNCMP(cmd, "global", p - cmd) == 0)
|
||||
|| STRNCMP(cmd, "vimgrepadd", MAX(p - cmd, 8)) == 0
|
||||
|| STRNCMP(cmd, "lvimgrep", MAX(p - cmd, 2)) == 0
|
||||
|| STRNCMP(cmd, "lvimgrepadd", MAX(p - cmd, 9)) == 0
|
||||
|| STRNCMP(cmd, "global", p - cmd) == 0)
|
||||
{
|
||||
// skip over "!"
|
||||
// skip optional "!"
|
||||
if (*p == '!')
|
||||
{
|
||||
p++;
|
||||
if (*skipwhite(p) == NUL)
|
||||
goto theend;
|
||||
return FALSE;
|
||||
}
|
||||
if (*cmd != 'g')
|
||||
delim_optional = TRUE;
|
||||
}
|
||||
else
|
||||
goto theend;
|
||||
return FALSE;
|
||||
|
||||
p = skipwhite(p);
|
||||
delim = (delim_optional && vim_isIDc(*p)) ? ' ' : *p++;
|
||||
*search_delim = delim;
|
||||
end = skip_regexp_ex(p, delim, magic_isset(), NULL, NULL, &magic);
|
||||
|
||||
end = skip_regexp_ex(p, delim, magic_isset(), NULL, NULL, &magic);
|
||||
use_last_pat = end == p && *end == delim;
|
||||
|
||||
if (end == p && !use_last_pat)
|
||||
goto theend;
|
||||
return FALSE;
|
||||
|
||||
// Don't do 'hlsearch' highlighting if the pattern matches everything.
|
||||
// Skip if the pattern matches everything (e.g., for 'hlsearch')
|
||||
if (!use_last_pat)
|
||||
{
|
||||
char c = *end;
|
||||
int empty;
|
||||
int empty;
|
||||
|
||||
*end = NUL;
|
||||
empty = empty_pattern_magic(p, (size_t)(end - p), magic);
|
||||
*end = c;
|
||||
if (empty)
|
||||
goto theend;
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// found a non-empty pattern or //
|
||||
// Found a non-empty pattern or //
|
||||
*skiplen = (int)(p - ccline.cmdbuff);
|
||||
*patlen = (int)(end - p);
|
||||
|
||||
// parse the address range
|
||||
// Parse the address range
|
||||
save_cursor = curwin->w_cursor;
|
||||
curwin->w_cursor = is_state->search_start;
|
||||
curwin->w_cursor = *incsearch_start;
|
||||
|
||||
parse_cmd_address(&ea, &dummy, TRUE);
|
||||
|
||||
if (ea.addr_count > 0)
|
||||
{
|
||||
// Allow for reverse match.
|
||||
if (ea.line2 < ea.line1)
|
||||
{
|
||||
search_first_line = ea.line2;
|
||||
search_last_line = ea.line1;
|
||||
}
|
||||
else
|
||||
{
|
||||
search_first_line = ea.line1;
|
||||
search_last_line = ea.line2;
|
||||
}
|
||||
int reverse_match = ea.line2 < ea.line1;
|
||||
search_first_line = reverse_match ? ea.line2 : ea.line1;
|
||||
search_last_line = reverse_match ? ea.line1 : ea.line2;
|
||||
}
|
||||
else if (cmd[0] == 's' && cmd[1] != 'o')
|
||||
{
|
||||
// :s defaults to the current line
|
||||
search_first_line = curwin->w_cursor.lnum;
|
||||
search_last_line = curwin->w_cursor.lnum;
|
||||
}
|
||||
search_first_line = search_last_line = curwin->w_cursor.lnum;
|
||||
|
||||
curwin->w_cursor = save_cursor;
|
||||
retval = TRUE;
|
||||
theend:
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/*
|
||||
* Return TRUE when 'incsearch' highlighting is to be done.
|
||||
* Sets search_first_line and search_last_line to the address range.
|
||||
* May change the last search pattern.
|
||||
*/
|
||||
static int
|
||||
do_incsearch_highlighting(
|
||||
int firstc,
|
||||
int *search_delim,
|
||||
incsearch_state_T *is_state,
|
||||
int *skiplen,
|
||||
int *patlen)
|
||||
{
|
||||
int retval = FALSE;
|
||||
|
||||
*skiplen = 0;
|
||||
*patlen = ccline.cmdlen;
|
||||
|
||||
if (!p_is || cmd_silent)
|
||||
return FALSE;
|
||||
|
||||
// By default search all lines
|
||||
search_first_line = 0;
|
||||
search_last_line = MAXLNUM;
|
||||
|
||||
if (firstc == '/' || firstc == '?')
|
||||
{
|
||||
*search_delim = firstc;
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
if (firstc != ':')
|
||||
return FALSE;
|
||||
|
||||
++emsg_off;
|
||||
retval = parse_pattern_and_range(&is_state->search_start, search_delim,
|
||||
skiplen, patlen);
|
||||
--emsg_off;
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
@ -905,7 +923,8 @@ cmdline_wildchar_complete(
|
||||
int *did_wild_list,
|
||||
int *wim_index_p,
|
||||
expand_T *xp,
|
||||
int *gotesc)
|
||||
int *gotesc,
|
||||
pos_T *pre_incsearch_pos)
|
||||
{
|
||||
int wim_index = *wim_index_p;
|
||||
int res;
|
||||
@ -938,6 +957,14 @@ cmdline_wildchar_complete(
|
||||
}
|
||||
else // typed p_wc first time
|
||||
{
|
||||
if (c == p_wc || c == p_wcm)
|
||||
{
|
||||
options |= WILD_MAY_EXPAND_PATTERN;
|
||||
if (pre_incsearch_pos)
|
||||
xp->xp_pre_incsearch_pos = *pre_incsearch_pos;
|
||||
else
|
||||
xp->xp_pre_incsearch_pos = curwin->w_cursor;
|
||||
}
|
||||
wim_index = 0;
|
||||
j = ccline.cmdpos;
|
||||
// if 'wildmode' first contains "longest", get longest
|
||||
@ -1927,6 +1954,10 @@ getcmdline_int(
|
||||
{
|
||||
trigger_cmd_autocmd(cmdline_type, EVENT_CMDLINELEAVEPRE);
|
||||
event_cmdlineleavepre_triggered = TRUE;
|
||||
#if defined(FEAT_SEARCH_EXTRA) || defined(PROTO)
|
||||
if ((c == ESC || c == Ctrl_C) && (wim_flags[0] & WIM_LIST))
|
||||
set_no_hlsearch(TRUE);
|
||||
#endif
|
||||
}
|
||||
|
||||
// The wildmenu is cleared if the pressed key is not used for
|
||||
@ -2021,7 +2052,13 @@ getcmdline_int(
|
||||
if ((c == p_wc && !gotesc && KeyTyped) || c == p_wcm)
|
||||
{
|
||||
res = cmdline_wildchar_complete(c, firstc != '@', &did_wild_list,
|
||||
&wim_index, &xpc, &gotesc);
|
||||
&wim_index, &xpc, &gotesc,
|
||||
#ifdef FEAT_SEARCH_EXTRA
|
||||
&is_state.search_start
|
||||
#else
|
||||
NULL
|
||||
#endif
|
||||
);
|
||||
if (res == CMDLINE_CHANGED)
|
||||
goto cmdline_changed;
|
||||
}
|
||||
@ -2056,6 +2093,16 @@ getcmdline_int(
|
||||
// further.
|
||||
if (wild_type == WILD_CANCEL || wild_type == WILD_APPLY)
|
||||
{
|
||||
#ifdef FEAT_SEARCH_EXTRA
|
||||
// Apply search highlighting
|
||||
if (wild_type == WILD_APPLY)
|
||||
{
|
||||
if (is_state.winid != curwin->w_id)
|
||||
init_incsearch_state(&is_state);
|
||||
if (KeyTyped || vpeekc() == NUL)
|
||||
may_do_incsearch_highlighting(firstc, count, &is_state);
|
||||
}
|
||||
#endif
|
||||
wild_type = 0;
|
||||
goto cmdline_not_changed;
|
||||
}
|
||||
@ -2527,6 +2574,8 @@ cmdline_changed:
|
||||
// If the window changed incremental search state is not valid.
|
||||
if (is_state.winid != curwin->w_id)
|
||||
init_incsearch_state(&is_state);
|
||||
if (xpc.xp_context == EXPAND_NOTHING && (KeyTyped || vpeekc() == NUL))
|
||||
may_do_incsearch_highlighting(firstc, count, &is_state);
|
||||
#endif
|
||||
// Trigger CmdlineChanged autocommands.
|
||||
if (trigger_cmdlinechanged)
|
||||
@ -2539,11 +2588,6 @@ cmdline_changed:
|
||||
prev_cmdpos = ccline.cmdpos;
|
||||
}
|
||||
|
||||
#ifdef FEAT_SEARCH_EXTRA
|
||||
if (xpc.xp_context == EXPAND_NOTHING && (KeyTyped || vpeekc() == NUL))
|
||||
may_do_incsearch_highlighting(firstc, count, &is_state);
|
||||
#endif
|
||||
|
||||
#ifdef FEAT_RIGHTLEFT
|
||||
if (cmdmsg_rl
|
||||
# ifdef FEAT_ARABIC
|
||||
|
@ -45,4 +45,5 @@ char *did_set_cedit(optset_T *args);
|
||||
int is_in_cmdwin(void);
|
||||
char_u *script_get(exarg_T *eap, char_u *cmd);
|
||||
void get_user_input(typval_T *argvars, typval_T *rettv, int inputdialog, int secret);
|
||||
int parse_pattern_and_range(pos_T *is_start, int *search_delim, int *skiplen, int *patlen);
|
||||
/* vim: set ft=c : */
|
||||
|
@ -655,6 +655,8 @@ typedef struct expand
|
||||
char_u *xp_line; // text being completed
|
||||
#define EXPAND_BUF_LEN 256
|
||||
char_u xp_buf[EXPAND_BUF_LEN]; // buffer for returned match
|
||||
int xp_search_dir; // Direction of search
|
||||
pos_T xp_pre_incsearch_pos; // Cursor position before incsearch
|
||||
} expand_T;
|
||||
|
||||
/*
|
||||
|
10
src/testdir/dumps/Test_search_wildmenu_1.dump
Normal file
10
src/testdir/dumps/Test_search_wildmenu_1.dump
Normal file
@ -0,0 +1,10 @@
|
||||
|t+0&#ffffff0|h|e| @71
|
||||
|t|h|e|s|e| @69
|
||||
|t|h|e| @71
|
||||
|f|o@1|b|a|r| @68
|
||||
|t|h|e|t|h|e| @68
|
||||
|t|h|e|t|h|e|r|e| @66
|
||||
|~+0#4040ff13&| @73
|
||||
|~| @73
|
||||
|e+0#0000001#ffff4012|\|n|f|o@1|b|a|r| +3#0000000#ffffff0@1|e|\|n|t|h|e|t|h|e|r|e| @1|e|\|n|t|h|e|s|e| @1|e|\|n|t|h|e| @34
|
||||
|/+0&&|e|\|n|f|o@1|b|a|r> @64
|
10
src/testdir/dumps/Test_search_wildmenu_2.dump
Normal file
10
src/testdir/dumps/Test_search_wildmenu_2.dump
Normal file
@ -0,0 +1,10 @@
|
||||
|t+0&#ffffff0|h|e| @71
|
||||
|t|h|e|s|e| @69
|
||||
|t|h|e| @71
|
||||
|f|o@1|b|a|r| @68
|
||||
|t|h|e|t|h|e| @68
|
||||
|t|h|e|t|h|e|r|e| @66
|
||||
|~+0#4040ff13&| @73
|
||||
|~| @73
|
||||
|~| @73
|
||||
|/+0#0000000&|t|h|e> @70
|
10
src/testdir/dumps/Test_search_wildmenu_3.dump
Normal file
10
src/testdir/dumps/Test_search_wildmenu_3.dump
Normal file
@ -0,0 +1,10 @@
|
||||
|t+0&#ffffff0|h|e| @71
|
||||
|f|o@1|b|a|r| @68
|
||||
|t|h|e|t|h|e| @68
|
||||
|t|h|e|t|h|e|r|e| @66
|
||||
|~+0#4040ff13&| @73
|
||||
|~| @73
|
||||
|~| @73
|
||||
|/+0#0000000&|t| @72
|
||||
|t|h|e|s|e| @4|t|h|e| @6|t|h|e|t|h|e| @3|t|h|e|t|h|e|r|e| @1|t|h|e|r|e| @29
|
||||
|/|t> @72
|
10
src/testdir/dumps/Test_search_wildmenu_4.dump
Normal file
10
src/testdir/dumps/Test_search_wildmenu_4.dump
Normal file
@ -0,0 +1,10 @@
|
||||
|t+0&#ffffff0|h|e| @71
|
||||
|t|h|e|s|e| @69
|
||||
|t|h|e| @71
|
||||
|f|o@1|b|a|r| @68
|
||||
|t|h|e|t|h|e| @68
|
||||
|t|h|e|t|h|e|r|e| @66
|
||||
|~+0#4040ff13&| @73
|
||||
|~| @73
|
||||
|t+3#0000000&|h|e|s|e| @1|t|h|e| @1|t|h|e|t|h|e| @1|t|h|e|t|h|e|r|e| @1|t|h|e|r|e| @39
|
||||
|/+0&&|t> @72
|
10
src/testdir/dumps/Test_search_wildmenu_5.dump
Normal file
10
src/testdir/dumps/Test_search_wildmenu_5.dump
Normal file
@ -0,0 +1,10 @@
|
||||
|t+0&#ffffff0|h|e| @71
|
||||
|t|h|e|s|e| @69
|
||||
|t|h|e| @71
|
||||
|f|o@1|b|a|r| @68
|
||||
|t|h|e|t|h|e| @68
|
||||
|t|h|e|t|h|e|r|e| @66
|
||||
|~+0#4040ff13&| @73
|
||||
|~| @73
|
||||
|t+3#0000000&|.|*|\|n|.|*|\|n|.|o@1|b|a|r| @1|t|.|*|\|n|.|*|\|n|.|h|e|t|h|e| @1|t|.|*|\|n|.|*|\|n|.|h|e| @28
|
||||
|/+0&&|t|.|*|\|n|.|*|\|n|.> @63
|
@ -1651,8 +1651,10 @@ func Test_cmdline_complete_various()
|
||||
" completion after a :global command
|
||||
call feedkeys(":g/a/chist\t\<C-B>\"\<CR>", 'xt')
|
||||
call assert_equal('"g/a/chistory', @:)
|
||||
set wildchar=0
|
||||
call feedkeys(":g/a\\/chist\t\<C-B>\"\<CR>", 'xt')
|
||||
call assert_equal("\"g/a\\/chist\t", @:)
|
||||
set wildchar&
|
||||
|
||||
" use <Esc> as the 'wildchar' for completion
|
||||
set wildchar=<Esc>
|
||||
@ -3094,12 +3096,14 @@ endfunc
|
||||
" Test for completion after a :substitute command followed by a pipe (|)
|
||||
" character
|
||||
func Test_cmdline_complete_substitute()
|
||||
set wildchar=0
|
||||
call feedkeys(":s | \t\<C-B>\"\<CR>", 'xt')
|
||||
call assert_equal("\"s | \t", @:)
|
||||
call feedkeys(":s/ | \t\<C-B>\"\<CR>", 'xt')
|
||||
call assert_equal("\"s/ | \t", @:)
|
||||
call feedkeys(":s/one | \t\<C-B>\"\<CR>", 'xt')
|
||||
call assert_equal("\"s/one | \t", @:)
|
||||
set wildchar&
|
||||
call feedkeys(":s/one/ | \t\<C-B>\"\<CR>", 'xt')
|
||||
call assert_equal("\"s/one/ | \t", @:)
|
||||
call feedkeys(":s/one/two | \t\<C-B>\"\<CR>", 'xt')
|
||||
@ -4350,4 +4354,239 @@ func Test_redrawtabpanel_error()
|
||||
call assert_fails(':redrawtabpanel', 'E1547:')
|
||||
endfunc
|
||||
|
||||
" Test wildcharm completion for '/' and '?' search
|
||||
func Test_search_complete()
|
||||
CheckOption incsearch
|
||||
set wildcharm=<c-z>
|
||||
|
||||
" Disable char_avail so that expansion of commandline works
|
||||
call test_override("char_avail", 1)
|
||||
|
||||
func GetComplInfo()
|
||||
let g:compl_info = cmdcomplete_info()
|
||||
return ''
|
||||
endfunc
|
||||
|
||||
new
|
||||
cnoremap <buffer><expr> <F9> GetComplInfo()
|
||||
|
||||
" Pressing <Tab> inserts tab character
|
||||
set wildchar=0
|
||||
call setline(1, "x\t")
|
||||
call feedkeys("/x\t\r", "tx")
|
||||
call assert_equal("x\t", @/)
|
||||
set wildchar&
|
||||
|
||||
call setline(1, ['the', 'these', 'thethe', 'thethere', 'foobar'])
|
||||
|
||||
for trig in ["\<tab>", "\<c-z>"]
|
||||
" Test menu first item and order
|
||||
call feedkeys($"gg2j/t{trig}\<f9>", 'tx')
|
||||
call assert_equal(['the', 'thethere', 'there', 'these', 'thethe'], g:compl_info.matches)
|
||||
call feedkeys($"gg2j?t{trig}\<f9>", 'tx')
|
||||
call assert_equal(['these', 'the', 'there', 'thethere', 'thethe'], g:compl_info.matches)
|
||||
|
||||
" <c-n> and <c-p> cycle through menu items
|
||||
call feedkeys($"gg/the{trig}\<cr>", 'tx')
|
||||
call assert_equal('these', getline('.'))
|
||||
call feedkeys($"gg/the{trig}\<c-n>\<cr>", 'tx')
|
||||
call assert_equal('thethe', getline('.'))
|
||||
call feedkeys($"gg/the{trig}".repeat("\<c-n>", 5)."\<cr>", 'tx')
|
||||
call assert_equal('these', getline('.'))
|
||||
call feedkeys($"G?the{trig}\<cr>", 'tx')
|
||||
call assert_equal('thethere', getline('.'))
|
||||
call feedkeys($"G?the{trig}".repeat("\<c-p>", 5)."\<cr>", 'tx')
|
||||
call assert_equal('thethere', getline('.'))
|
||||
|
||||
" Beginning of word pattern (<) retains '<'
|
||||
call feedkeys($"gg2j/\\<t{trig}\<f9>", 'tx')
|
||||
call assert_equal(['\<thethere', '\<the', '\<these', '\<thethe'], g:compl_info.matches)
|
||||
call feedkeys($"gg2j?\\<t{trig}\<f9>", 'tx')
|
||||
call assert_equal(['\<these', '\<the', '\<thethere', '\<thethe'], g:compl_info.matches)
|
||||
call feedkeys($"gg2j/\\v<t{trig}\<f9>", 'tx')
|
||||
call assert_equal(['\v<thethere', '\v<the', '\v<these', '\v<thethe'], g:compl_info.matches)
|
||||
call feedkeys($"gg2j?\\v<th{trig}\<f9>", 'tx')
|
||||
call assert_equal(['\v<these', '\v<the', '\v<thethere', '\v<thethe'], g:compl_info.matches)
|
||||
endfor
|
||||
|
||||
" Ctrl-G goes from one match to the next, after menu is opened
|
||||
set incsearch
|
||||
" first match
|
||||
call feedkeys("gg/the\<c-z>\<c-n>\<c-g>\<cr>", 'tx')
|
||||
call assert_equal('thethe', getline('.'))
|
||||
" second match
|
||||
call feedkeys("gg/the\<c-z>\<c-n>\<c-g>\<c-g>\<cr>", 'tx')
|
||||
call assert_equal('thethere', getline('.'))
|
||||
call assert_equal([0, 0, 0, 0], getpos('"'))
|
||||
|
||||
" CTRL-T goes to the previous match
|
||||
" first match
|
||||
call feedkeys("G?the\<c-z>".repeat("\<c-n>", 2)."\<c-t>\<cr>", 'tx')
|
||||
call assert_equal('thethere', getline('.'))
|
||||
" second match
|
||||
call feedkeys("G?the\<c-z>".repeat("\<c-n>", 2).repeat("\<c-t>", 2)."\<cr>", 'tx')
|
||||
call assert_equal('thethe', getline('.'))
|
||||
|
||||
" wild menu is cleared properly
|
||||
call feedkeys("/the\<c-z>\<esc>/\<f9>", 'tx')
|
||||
call assert_equal({}, g:compl_info)
|
||||
call feedkeys("/the\<c-z>\<c-e>\<f9>", 'tx')
|
||||
call assert_equal([], g:compl_info.matches)
|
||||
|
||||
" Do not expand if offset is present (/pattern/offset and ?pattern?offset)
|
||||
for pat in ["/", "/2", "/-3", "\\/"]
|
||||
call feedkeys("/the" . pat . "\<c-z>\<f9>", 'tx')
|
||||
call assert_equal({}, g:compl_info)
|
||||
endfor
|
||||
for pat in ["?", "?2", "?-3", "\\\\?"]
|
||||
call feedkeys("?the" . pat . "\<c-z>\<f9>", 'tx')
|
||||
call assert_equal({}, g:compl_info)
|
||||
endfor
|
||||
|
||||
" Last letter of match is multibyte
|
||||
call setline('$', ['theΩ'])
|
||||
call feedkeys("gg/th\<c-z>\<f9>", 'tx')
|
||||
call assert_equal(['these', 'thethe', 'the', 'thethere', 'there', 'theΩ'],
|
||||
\ g:compl_info.matches)
|
||||
|
||||
" Identical words
|
||||
call setline(1, ["foo", "foo", "foo", "foobar"])
|
||||
call feedkeys("gg/f\<c-z>\<f9>", 'tx')
|
||||
call assert_equal(['foo', 'foobar'], g:compl_info.matches)
|
||||
|
||||
" Exact match
|
||||
call feedkeys("/foo\<c-z>\<f9>", 'tx')
|
||||
call assert_equal(['foo', 'foobar'], g:compl_info.matches)
|
||||
|
||||
" 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')
|
||||
call assert_equal(['Foobar', 'FooBARR'], g:compl_info.matches)
|
||||
call feedkeys("gg/FO\<tab>\<f9>", 'tx')
|
||||
call assert_equal({}, g:compl_info)
|
||||
set ignorecase
|
||||
call feedkeys("gg/f\<tab>\<f9>", 'tx')
|
||||
call assert_equal(['foobar', 'fooBAr', 'fooBARR'], g:compl_info.matches)
|
||||
call feedkeys("gg/Fo\<tab>\<f9>", 'tx')
|
||||
call assert_equal(['Foobar', 'FooBAr', 'FooBARR'], g:compl_info.matches)
|
||||
call feedkeys("gg/FO\<tab>\<f9>", 'tx')
|
||||
call assert_equal(['FOobar', 'FOoBAr', 'FOoBARR'], g:compl_info.matches)
|
||||
set smartcase
|
||||
call feedkeys("gg/f\<tab>\<f9>", 'tx')
|
||||
call assert_equal(['foobar', 'fooBAr', 'fooBARR'], g:compl_info.matches)
|
||||
call feedkeys("gg/Fo\<tab>\<f9>", 'tx')
|
||||
call assert_equal(['Foobar', 'FooBARR'], g:compl_info.matches)
|
||||
call feedkeys("gg/FO\<tab>\<f9>", 'tx')
|
||||
call assert_equal({}, g:compl_info)
|
||||
|
||||
bw!
|
||||
call test_override("char_avail", 0)
|
||||
delfunc GetComplInfo
|
||||
unlet! g:compl_info
|
||||
set wildcharm=0 incsearch& ignorecase& smartcase&
|
||||
endfunc
|
||||
|
||||
func Test_search_wildmenu_screendump()
|
||||
CheckScreendump
|
||||
|
||||
let lines =<< trim [SCRIPT]
|
||||
set wildmenu wildcharm=<f5>
|
||||
call setline(1, ['the', 'these', 'the', 'foobar', 'thethe', 'thethere'])
|
||||
[SCRIPT]
|
||||
call writefile(lines, 'XTest_search_wildmenu', 'D')
|
||||
let buf = RunVimInTerminal('-S XTest_search_wildmenu', {'rows': 10})
|
||||
|
||||
" Pattern has newline at EOF
|
||||
call term_sendkeys(buf, "gg2j/e\\n\<f5>")
|
||||
call VerifyScreenDump(buf, 'Test_search_wildmenu_1', {})
|
||||
|
||||
" longest:full
|
||||
call term_sendkeys(buf, "\<esc>:set wim=longest,full\<cr>")
|
||||
call term_sendkeys(buf, "gg/t\<f5>")
|
||||
call VerifyScreenDump(buf, 'Test_search_wildmenu_2', {})
|
||||
|
||||
" list:full
|
||||
call term_sendkeys(buf, "\<esc>:set wim=list,full\<cr>")
|
||||
call term_sendkeys(buf, "gg/t\<f5>")
|
||||
call VerifyScreenDump(buf, 'Test_search_wildmenu_3', {})
|
||||
|
||||
" noselect:full
|
||||
call term_sendkeys(buf, "\<esc>:set wim=noselect,full\<cr>")
|
||||
call term_sendkeys(buf, "gg/t\<f5>")
|
||||
call VerifyScreenDump(buf, 'Test_search_wildmenu_4', {})
|
||||
|
||||
" Multiline
|
||||
call term_sendkeys(buf, "\<esc>gg/t.*\\n.*\\n.\<tab>")
|
||||
call VerifyScreenDump(buf, 'Test_search_wildmenu_5', {})
|
||||
|
||||
call term_sendkeys(buf, "\<esc>")
|
||||
call StopVimInTerminal(buf)
|
||||
endfunc
|
||||
|
||||
" Test wildcharm completion for :s and :g with range
|
||||
func Test_range_complete()
|
||||
set wildcharm=<c-z>
|
||||
|
||||
" Disable char_avail so that expansion of commandline works
|
||||
call test_override("char_avail", 1)
|
||||
|
||||
func GetComplInfo()
|
||||
let g:compl_info = cmdcomplete_info()
|
||||
return ''
|
||||
endfunc
|
||||
new
|
||||
cnoremap <buffer><expr> <F9> GetComplInfo()
|
||||
|
||||
call setline(1, ['ab', 'ba', 'ca', 'af'])
|
||||
|
||||
for trig in ["\<tab>", "\<c-z>"]
|
||||
call feedkeys($":%s/a{trig}\<f9>", 'xt')
|
||||
call assert_equal(['ab', 'a', 'af'], g:compl_info.matches)
|
||||
call feedkeys($":vim9cmd :%s/a{trig}\<f9>", 'xt')
|
||||
call assert_equal(['ab', 'a', 'af'], g:compl_info.matches)
|
||||
endfor
|
||||
|
||||
call feedkeys(":%s/\<c-z>\<f9>", 'xt')
|
||||
call assert_equal({}, g:compl_info)
|
||||
|
||||
for cmd in ['s', 'g']
|
||||
call feedkeys(":1,2" . cmd . "/a\<c-z>\<f9>", 'xt')
|
||||
call assert_equal(['ab', 'a'], g:compl_info.matches)
|
||||
endfor
|
||||
|
||||
1
|
||||
call feedkeys(":.,+2s/a\<c-z>\<f9>", 'xt')
|
||||
call assert_equal(['ab', 'a'], g:compl_info.matches)
|
||||
|
||||
/f
|
||||
call feedkeys(":1,s/b\<c-z>\<f9>", 'xt')
|
||||
call assert_equal(['b', 'ba'], g:compl_info.matches)
|
||||
|
||||
/c
|
||||
call feedkeys(":\\?,4s/a\<c-z>\<f9>", 'xt')
|
||||
call assert_equal(['a', 'af'], g:compl_info.matches)
|
||||
|
||||
%s/c/c/
|
||||
call feedkeys(":1,\\&s/a\<c-z>\<f9>", 'xt')
|
||||
call assert_equal(['ab', 'a'], g:compl_info.matches)
|
||||
|
||||
3
|
||||
normal! ma
|
||||
call feedkeys(":'a,$s/a\<c-z>\<f9>", 'xt')
|
||||
call assert_equal(['a', 'af'], g:compl_info.matches)
|
||||
|
||||
" Line number followed by a search pattern ([start]/pattern/[command])
|
||||
call feedkeys("3/a\<c-z>\<f9>", 'xt')
|
||||
call assert_equal(['a', 'af', 'ab'], g:compl_info.matches)
|
||||
|
||||
bw!
|
||||
call test_override("char_avail", 0)
|
||||
delfunc GetComplInfo
|
||||
unlet! g:compl_info
|
||||
set wildcharm=0
|
||||
endfunc
|
||||
|
||||
" vim: shiftwidth=2 sts=2 expandtab
|
||||
|
@ -719,6 +719,8 @@ static char *(features[]) =
|
||||
|
||||
static int included_patches[] =
|
||||
{ /* Add new patch number below this line */
|
||||
/**/
|
||||
1490,
|
||||
/**/
|
||||
1489,
|
||||
/**/
|
||||
|
@ -859,6 +859,7 @@ extern int (*dyn_libintl_wputenv)(const wchar_t *envstring);
|
||||
#define EXPAND_FINDFUNC 61
|
||||
#define EXPAND_HIGHLIGHT_GROUP 62
|
||||
#define EXPAND_FILETYPECMD 63
|
||||
#define EXPAND_PATTERN_IN_BUF 64
|
||||
|
||||
|
||||
// Values for exmode_active (0 is no exmode)
|
||||
@ -894,6 +895,7 @@ extern int (*dyn_libintl_wputenv)(const wchar_t *envstring);
|
||||
#define WILD_BUFLASTUSED 0x1000
|
||||
#define BUF_DIFF_FILTER 0x2000
|
||||
#define WILD_KEEP_SOLE_ITEM 0x4000
|
||||
#define WILD_MAY_EXPAND_PATTERN 0x8000
|
||||
|
||||
// Flags for expand_wildcards()
|
||||
#define EW_DIR 0x01 // include directory names
|
||||
|
Reference in New Issue
Block a user