vim-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: vim/vim#17570

6b49fba8c8

Co-authored-by: Girish Palya <girishji@gmail.com>
This commit is contained in:
zeertzjq
2025-07-04 13:09:02 +08:00
parent cc83854036
commit dd707246fd
11 changed files with 717 additions and 49 deletions

View File

@ -41,12 +41,14 @@
#include "nvim/highlight.h"
#include "nvim/highlight_defs.h"
#include "nvim/highlight_group.h"
#include "nvim/insexpand.h"
#include "nvim/keycodes.h"
#include "nvim/lua/executor.h"
#include "nvim/macros_defs.h"
#include "nvim/mapping.h"
#include "nvim/mbyte.h"
#include "nvim/mbyte_defs.h"
#include "nvim/memline.h"
#include "nvim/memory.h"
#include "nvim/menu.h"
#include "nvim/message.h"
@ -83,6 +85,8 @@ typedef void *(*user_expand_func_T)(const char *, int, typval_T *);
#endif
static bool cmd_showtail; ///< Only show path tail in lists ?
static bool may_expand_pattern = false;
static pos_T pre_incsearch_pos; ///< Cursor position when incsearch started
/// "compl_match_array" points the currently displayed list of entries in the
/// popup menu. It is NULL when there is no popup menu.
@ -245,6 +249,8 @@ int nextwild(expand_T *xp, int type, int options, bool escape)
char *p;
if (xp->xp_numfiles == -1) {
may_expand_pattern = options & WILD_MAY_EXPAND_PATTERN;
pre_incsearch_pos = xp->xp_pre_incsearch_pos;
if (ccline->input_fn && ccline->xp_context == EXPAND_COMMANDS) {
// Expand commands typed in input() function
set_cmd_context(xp, ccline->cmdbuff, ccline->cmdlen, ccline->cmdpos, false);
@ -284,8 +290,9 @@ int nextwild(expand_T *xp, int type, int options, bool escape)
p = ExpandOne(xp, NULL, NULL, 0, type);
} else {
char *tmp;
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
tmp = xstrnsave(xp->xp_pattern, xp->xp_pattern_len);
} else {
tmp = addstar(xp->xp_pattern, xp->xp_pattern_len, xp->xp_context);
@ -430,10 +437,12 @@ bool cmdline_compl_is_fuzzy(void)
}
/// Return the number of characters that should be skipped in the wildmenu
/// 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_wildmenu_char(expand_T *xp, char *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)
|| ((xp->xp_context == EXPAND_MENUS || xp->xp_context == EXPAND_MENUNAMES)
&& (s[0] == '\t' || (s[0] == '\\' && s[1] != NUL)))) {
#ifndef BACKSLASH_IN_FILENAME
@ -1398,17 +1407,31 @@ char *addstar(char *fname, size_t len, int context)
/// 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)
{
CmdlineInfo *const 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 = (size_t)ccline->cmdpos;
search_first_line = 0; // Search entire buffer
return;
}
// Only handle ':', '>', or '=' command-lines, or expression input
if (ccline->cmdfirstc != ':'
&& ccline->cmdfirstc != '>' && ccline->cmdfirstc != '='
&& !ccline->input_fn) {
xp->xp_context = EXPAND_NOTHING;
return;
}
// Fallback to command-line expansion
set_cmd_context(xp, ccline->cmdbuff, ccline->cmdlen, ccline->cmdpos, true);
}
@ -1891,6 +1914,29 @@ static const char *set_context_in_filetype_cmd(expand_T *xp, const char *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)
{
CmdlineInfo *ccline = get_cmdline_info();
emsg_off++;
int skiplen = 0;
int dummy, patlen;
int 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;
}
xp->xp_pattern = ccline->cmdbuff + skiplen;
xp->xp_pattern_len = (size_t)(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".
/// The argument to the command is "arg" and the argument flags is "argt".
/// For user-defined commands and for environment variables, "context" has the
@ -1900,6 +1946,8 @@ static const char *set_context_in_filetype_cmd(expand_T *xp, const char *arg)
static const char *set_context_by_cmdname(const char *cmd, cmdidx_T cmdidx, expand_T *xp,
const char *arg, uint32_t argt, int context, bool forceit)
{
const char *nextcmd;
switch (cmdidx) {
case CMD_find:
case CMD_sfind:
@ -1978,10 +2026,20 @@ static const char *set_context_by_cmdname(const char *cmd, cmdidx_T cmdidx, expa
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:
@ -2913,6 +2971,9 @@ static int ExpandFromContext(expand_T *xp, char *pat, char ***matches, int *numM
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.
@ -3630,6 +3691,9 @@ void wildmenu_cleanup(CmdlineInfo *cclp)
RedrawingDisabled = 0;
}
// Clear highlighting applied during wildmenu activity
set_no_hlsearch(true);
if (wild_menu_showing == WM_SCROLLED) {
// Entered command line, move it up
cmdline_row--;
@ -3800,3 +3864,219 @@ void f_cmdcomplete_info(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
}
}
}
/// 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 **match, pos_T *match_end)
{
if (start->lnum > end->lnum
|| (start->lnum == end->lnum && start->col >= end->col)) {
return FAIL; // invalid range
}
// Get line pointers
char *start_line = ml_get(start->lnum);
char *end_line = ml_get(end->lnum);
// Use a growable string (ga)
garray_T ga;
ga_init(&ga, 1, 128);
// Append start line from start->col to end
char *start_ptr = start_line + start->col;
bool is_single_line = start->lnum == end->lnum;
int segment_len = is_single_line ? (int)(end->col - start->col)
: (int)strlen(start_ptr);
ga_grow(&ga, segment_len + 1);
ga_concat_len(&ga, start_ptr, (size_t)segment_len);
if (!is_single_line) {
ga_append(&ga, '\n');
}
// Append full lines between start and end
if (!is_single_line) {
for (linenr_T lnum = start->lnum + 1; lnum < end->lnum; lnum++) {
char *line = ml_get(lnum);
ga_grow(&ga, ml_get_len(lnum) + 1);
ga_concat(&ga, line);
ga_append(&ga, '\n');
}
}
// Append partial end line (up to word end)
char *word_end = find_word_end(end_line + end->col);
segment_len = (int)(word_end - end_line);
ga_grow(&ga, segment_len);
ga_concat_len(&ga, end_line + (is_single_line ? end->col : 0),
(size_t)(segment_len - (is_single_line ? end->col : 0)));
// Null-terminate
ga_grow(&ga, 1);
ga_append(&ga, NUL);
*match = (char *)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.
///
/// @param pat pattern to match
/// @param dir FORWARD or BACKWARD
/// @param[out] matches array with matched string
/// @param[out] numMatches number of matches
static int expand_pattern_in_buf(char *pat, Direction dir, char ***matches, int *numMatches)
{
bool has_range = search_first_line != 0;
*matches = NULL;
*numMatches = 0;
if (pat == NULL || *pat == NUL) {
return FAIL;
}
int pat_len = (int)strlen(pat);
pos_T cur_match_pos = { 0 }, prev_match_pos = { 0 };
if (has_range) {
cur_match_pos.lnum = search_first_line;
} else {
cur_match_pos = pre_incsearch_pos;
}
int search_flags = SEARCH_OPT | SEARCH_NOOF | SEARCH_PEEK | SEARCH_NFMSG
| (has_range ? SEARCH_START : 0);
regmatch_T regmatch;
regmatch.regprog = vim_regcomp(pat, RE_MAGIC + RE_STRING);
if (regmatch.regprog == NULL) {
return FAIL;
}
regmatch.rm_ic = p_ic;
garray_T ga;
ga_init(&ga, sizeof(char *), 10); // Use growable array of char *
pos_T end_match_pos, word_end_pos;
bool looped_around = false;
bool compl_started = false;
char *match;
while (true) {
emsg_off++;
msg_silent++;
int found_new_match = searchit(NULL, curbuf, &cur_match_pos,
&end_match_pos, dir, pat, (size_t)pat_len, 1L,
search_flags, RE_LAST, NULL);
msg_silent--;
emsg_off--;
if (found_new_match == FAIL) {
break;
}
// 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;
}
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(&regmatch, match, (colnr_T)0)) {
xfree(match);
continue;
}
xfree(match);
// Construct a new match from completed word appended to pattern itself
char *line = ml_get(end_match_pos.lnum);
char *word_end = find_word_end(line + end_match_pos.col); // col starts from 0
int match_len = (int)(word_end - (line + end_match_pos.col));
match = xmalloc((size_t)match_len + (size_t)pat_len + 1); // +1 for NUL
memmove(match, pat, (size_t)pat_len);
if (match_len > 0) {
memmove(match + (size_t)pat_len, line + end_match_pos.col, (size_t)match_len);
}
match[pat_len + match_len] = NUL;
// Include this match if it is not a duplicate
for (int i = 0; i < ga.ga_len; i++) {
if (strcmp(match, ((char **)ga.ga_data)[i]) == 0) {
XFREE_CLEAR(match);
break;
}
}
if (match != NULL) {
ga_grow(&ga, 1);
((char **)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 **)ga.ga_data;
*numMatches = ga.ga_len;
return OK;
cleanup:
vim_regfree(regmatch.regprog);
ga_clear_strings(&ga);
return FAIL;
}

View File

@ -41,6 +41,7 @@ enum {
WILD_BUFLASTUSED = 0x1000,
BUF_DIFF_FILTER = 0x2000,
WILD_KEEP_SOLE_ITEM = 0x4000,
WILD_MAY_EXPAND_PATTERN = 0x8000,
};
#ifdef INCLUDE_GENERATED_DECLARATIONS

View File

@ -4,6 +4,8 @@
#include <stddef.h>
#include "nvim/eval/typval_defs.h"
#include "nvim/pos_defs.h"
#include "nvim/vim_defs.h"
typedef enum {
XP_PREFIX_NONE, ///< prefix not used
@ -35,6 +37,8 @@ typedef struct {
char **xp_files; ///< list of files
char *xp_line; ///< text being completed
char xp_buf[EXPAND_BUF_LEN]; ///< buffer for returned match
Direction xp_search_dir; ///< Direction of search
pos_T xp_pre_incsearch_pos; ///< Cursor position before incsearch
} expand_T;
/// values for xp_backslash
@ -109,6 +113,7 @@ enum {
EXPAND_SHELLCMDLINE,
EXPAND_FINDFUNC,
EXPAND_FILETYPECMD,
EXPAND_PATTERN_IN_BUF,
EXPAND_CHECKHEALTH,
EXPAND_LUA,
};

View File

@ -282,38 +282,25 @@ static void 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.
static bool do_incsearch_highlighting(int firstc, int *search_delim, incsearch_state_T *s,
int *skiplen, int *patlen)
/// 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.
bool parse_pattern_and_range(pos_T *incsearch_start, int *search_delim, int *skiplen, int *patlen)
FUNC_ATTR_NONNULL_ALL
{
char *p;
bool delim_optional = false;
const char *dummy;
bool retval = false;
magic_T magic = 0;
*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++;
exarg_T ea = {
.line1 = 1,
.line2 = 1,
@ -322,17 +309,19 @@ static bool do_incsearch_highlighting(int firstc, int *search_delim, incsearch_s
};
cmdmod_T dummy_cmdmod;
// Skip over command modifiers
parse_command_modifiers(&ea, &dummy, &dummy_cmdmod, true);
// Skip over the range to find the command.
char *cmd = skip_range(ea.cmd, NULL);
if (vim_strchr("sgvl", (uint8_t)(*cmd)) == NULL) {
goto theend;
return false;
}
// Skip over "substitute" to find the pattern separator.
// 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", (size_t)(p - cmd)) == 0
@ -354,70 +343,105 @@ static bool do_incsearch_highlighting(int firstc, int *search_delim, incsearch_s
p++;
}
if (*p == NUL) {
goto theend;
return false;
}
} else if (strncmp(cmd, "vimgrep", (size_t)MAX(p - cmd, 3)) == 0
|| strncmp(cmd, "vimgrepadd", (size_t)MAX(p - cmd, 8)) == 0
|| strncmp(cmd, "lvimgrep", (size_t)MAX(p - cmd, 2)) == 0
|| strncmp(cmd, "lvimgrepadd", (size_t)MAX(p - cmd, 9)) == 0
|| strncmp(cmd, "global", (size_t)(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);
int delim = (delim_optional && vim_isIDc((uint8_t)(*p))) ? ' ' : *p++;
*search_delim = delim;
char *end = skip_regexp_ex(p, delim, magic_isset(), NULL, NULL, &magic);
char *end = skip_regexp_ex(p, delim, magic_isset(), NULL, NULL, &magic);
bool 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;
*end = NUL;
bool 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
pos_T save_cursor = curwin->w_cursor;
curwin->w_cursor = s->search_start;
curwin->w_cursor = *incsearch_start;
parse_cmd_address(&ea, &dummy, true);
if (ea.addr_count > 0) {
// Allow for reverse match.
search_first_line = MIN(ea.line2, ea.line1);
search_last_line = MAX(ea.line2, ea.line1);
} 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 bool do_incsearch_highlighting(int firstc, int *search_delim, incsearch_state_T *is_state,
int *skiplen, int *patlen)
{
bool 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;
}
@ -1114,6 +1138,10 @@ static int command_line_wildchar_complete(CommandLineState *s)
res = OK; // don't insert 'wildchar' now
}
} else { // typed p_wc first time
if (s->c == p_wc || s->c == p_wcm) {
options |= WILD_MAY_EXPAND_PATTERN;
s->xpc.xp_pre_incsearch_pos = s->is_state.search_start;
}
s->wim_index = 0;
int j = ccline.cmdpos;
@ -1320,6 +1348,9 @@ static int command_line_execute(VimState *state, int key)
|| s->c == Ctrl_C) {
trigger_cmd_autocmd(s->cmdline_type, EVENT_CMDLINELEAVEPRE);
s->event_cmdlineleavepre_triggered = true;
if ((s->c == ESC || s->c == Ctrl_C) && (wim_flags[0] & kOptWimFlagList)) {
set_no_hlsearch(true);
}
}
// The wildmenu is cleared if the pressed key is not used for
@ -1435,6 +1466,15 @@ static int command_line_execute(VimState *state, int key)
// If already used to cancel/accept wildmenu, don't process the key further.
if (wild_type == WILD_CANCEL || wild_type == WILD_APPLY) {
return command_line_not_changed(s);
// Apply search highlighting
if (wild_type == WILD_APPLY) {
if (s->is_state.winid != curwin->handle) {
init_incsearch_state(&s->is_state);
}
if (KeyTyped || vpeekc() == NUL) {
may_do_incsearch_highlighting(s->firstc, s->count, &s->is_state);
}
}
}
return command_line_handle_key(s);

View File

@ -10101,7 +10101,9 @@ local options = {
:set wc=X
:set wc=^I
set wc=<Tab>
<
< '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".
]=],
full_name = 'wildchar',
scope = { 'global' },