patch 9.1.1490: 'wildchar' does not work in search contexts

Commit: 
https://github.com/vim/vim/commit/6b49fba8c8b97b178ddf81a9ca0c6f36c66f942f
Author: Girish Palya <giris...@gmail.com>
Date:   Sat Jun 28 19:47:34 2025 +0200

    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 " " 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 <giris...@gmail.com>
    Signed-off-by: Christian Brabandt <c...@256bit.org>

diff --git a/runtime/doc/cmdline.txt b/runtime/doc/cmdline.txt
index 93431f8fc..a15debe5e 100644
--- a/runtime/doc/cmdline.txt
+++ b/runtime/doc/cmdline.txt
@@ -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 "        " 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
diff --git a/runtime/doc/options.txt b/runtime/doc/options.txt
index df4842f34..a7dd6a9e5 100644
--- a/runtime/doc/options.txt
+++ b/runtime/doc/options.txt
@@ -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 "  ".
+       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'*
diff --git a/runtime/doc/tags b/runtime/doc/tags
index c96a8b6ff..30eb99162 100644
--- a/runtime/doc/tags
+++ b/runtime/doc/tags
@@ -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*
diff --git a/runtime/doc/version9.txt b/runtime/doc/version9.txt
index f2618e5cd..f8ddb7f0b 100644
--- a/runtime/doc/version9.txt
+++ b/runtime/doc/version9.txt
@@ -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
diff --git a/src/cmdexpand.c b/src/cmdexpand.c
index 4077fb341..2a2360722 100644
--- a/src/cmdexpand.c
+++ b/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();
+
+    // 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 expansion for ':', '>' and '=' command-lines
+    // 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, '
');
+
+    // 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, '
');
+       }
+
+    // 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
" 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))
+       {
+           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;
+}
diff --git a/src/ex_getln.c b/src/ex_getln.c
index 36775ba6b..324f492db 100644
--- a/src/ex_getln.c
+++ b/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;
+       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", 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
diff --git a/src/proto/ex_getln.pro b/src/proto/ex_getln.pro
index cc8723ad7..6c93ac755 100644
--- a/src/proto/ex_getln.pro
+++ b/src/proto/ex_getln.pro
@@ -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 : */
diff --git a/src/structs.h b/src/structs.h
index 423757adb..2108c2161 100644
--- a/src/structs.h
+++ b/src/structs.h
@@ -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;
 
 /*
diff --git a/src/testdir/dumps/Test_search_wildmenu_1.dump 
b/src/testdir/dumps/Test_search_wildmenu_1.dump
new file mode 100644
index 000000000..46ab9a56b
--- /dev/null
+++ b/src/testdir/dumps/Test_search_wildmenu_1.dump
@@ -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
diff --git a/src/testdir/dumps/Test_search_wildmenu_2.dump 
b/src/testdir/dumps/Test_search_wildmenu_2.dump
new file mode 100644
index 000000000..52889cced
--- /dev/null
+++ b/src/testdir/dumps/Test_search_wildmenu_2.dump
@@ -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
diff --git a/src/testdir/dumps/Test_search_wildmenu_3.dump 
b/src/testdir/dumps/Test_search_wildmenu_3.dump
new file mode 100644
index 000000000..69e38f890
--- /dev/null
+++ b/src/testdir/dumps/Test_search_wildmenu_3.dump
@@ -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
diff --git a/src/testdir/dumps/Test_search_wildmenu_4.dump 
b/src/testdir/dumps/Test_search_wildmenu_4.dump
new file mode 100644
index 000000000..512f87e90
--- /dev/null
+++ b/src/testdir/dumps/Test_search_wildmenu_4.dump
@@ -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
diff --git a/src/testdir/dumps/Test_search_wildmenu_5.dump 
b/src/testdir/dumps/Test_search_wildmenu_5.dump
new file mode 100644
index 000000000..533644f14
--- /dev/null
+++ b/src/testdir/dumps/Test_search_wildmenu_5.dump
@@ -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
diff --git a/src/testdir/test_cmdline.vim b/src/testdir/test_cmdline.vim
index 9a3fe20df..59c25db75 100644
--- a/src/testdir/test_cmdline.vim
+++ b/src/testdir/test_cmdline.vim
@@ -1651,8 +1651,10 @@ func Test_cmdline_complete_various()
   " completion after a :global command
   call feedkeys(":g/a/chist    \<C-B>\"\<CR>", 'xt')
   call assert_equal('"g/a/chistory', @:)
+  set wildchar=0
   call feedkeys(":g/a\/chist   \<C-B>\"\<CR>", 'xt')
   call assert_equal("\"g/a\/chist      ", @:)
+  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 |  \<C-B>\"\<CR>", 'xt')
   call assert_equal("\"s |     ", @:)
   call feedkeys(":s/ |         \<C-B>\"\<CR>", 'xt')
   call assert_equal("\"s/ |    ", @:)
   call feedkeys(":s/one |      \<C-B>\"\<CR>", 'xt')
   call assert_equal("\"s/one |         ", @:)
+  set wildchar&
   call feedkeys(":s/one/ |     \<C-B>\"\<CR>", 'xt')
   call assert_equal("\"s/one/ |        ", @:)
   call feedkeys(":s/one/two |  \<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   ")
+  call feedkeys("/x    
", "tx")
+  call assert_equal("x ", @/)
+  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([' <thethere', ' <the', ' <these', ' <thethe'], 
g:compl_info.matches)
+    call feedkeys($"gg2j?\v<th{trig}\<f9>", 'tx')
+    call assert_equal([' <these', ' <the', ' <thethere', ' <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
diff --git a/src/version.c b/src/version.c
index e91207032..ff3c34856 100644
--- a/src/version.c
+++ b/src/version.c
@@ -719,6 +719,8 @@ static char *(features[]) =
 
 static int included_patches[] =
 {   /* Add new patch number below this line */
+/**/
+    1490,
 /**/
     1489,
 /**/
diff --git a/src/vim.h b/src/vim.h
index 7f79e6855..b8569d85e 100644
--- a/src/vim.h
+++ b/src/vim.h
@@ -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

-- 
-- 
You received this message from the "vim_dev" maillist.
Do not top-post! Type your reply below the text you are replying to.
For more information, visit http://www.vim.org/maillist.php

--- 
You received this message because you are subscribed to the Google Groups 
"vim_dev" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to vim_dev+unsubscr...@googlegroups.com.
To view this discussion visit 
https://groups.google.com/d/msgid/vim_dev/E1uVa4t-008qom-R3%40256bit.org.

Raspunde prin e-mail lui